Le avventure in VB.Net di un principiante ex-VB6 - 19
a cura di Oscar Zanin e Diego Cattaruzza (requisiti: Visual Basic Express e SqlServer)

Premessa
In questo articolo completeremo la descrizione dello sviluppo della prima form master-details, la FrmClienti, iniziata nell'articolo precedente, in cui sono state illustrate le sue parti 'accessorie', classi dati e form di dialogo.

Un enumerato per le griglie
In questa form sono presenti due griglie e si è reso necessario indicare a quale (quali) griglie riferirsi nel codice. Quindi, prima di cominciare lo sviluppo della FrmClienti, apriamo il file ConvalidaDati.vb e aggiungiamo in fondo il seguente enumerato:

Public Enum GrigliaDaPreparare
  Prima
  Seconda
  Terza
  Tutte
End Enum

Il disegno della FrmClienti
Dopo aver aggiunto una nuova Krypton Form di nome FrmClienti al progetto PrimiPassi, dovete scegliere quale aspetto dare alla form, poiché essa contiene una notevole quantità di controlli. Mentre Oscar ha dovuto - su specifica richiesta del cliente, il quale, per quanto stupido possa essere, ha sempre ragione - disegnare una form piuttosto grande (1265x790) con tutti i dati contemporaneamente visibili, Diego ha distribuito gli stessi controlli su due schede di un controllo a schede, contenendo le dimensioni (800x600).
Nelle seguenti figure, sono mostrate le due versioni (le immagini sono anteprime opportunamente dimensionate, cliccando sulle quali vedrete le form nella dimensione originale).

Stavolta, dato l'alto numero di controlli, è preferibile che diate semplicemente una buona occhiata alle immagini, mentre per il disegno, invece di seguire un lunghissimo elenco di impostazioni (che omettiamo proprio per la sua lunghezza), procedete in questo modo:

  1. Salvate la FrmClienti, anche vuota e chiudete la finestra di disegno (file FrmClienti.vb);
  2. aprite il file FrmClienti.Designer.vb;
  3. sostituite completamente il contenuto con quello di uno dei due file di testo forniti (con lo stesso nome più l'estensione .txt): ozFrmClienti.Designer.vb.txt (la form di Oscar) e FrmClienti.Designer.vb.txt (quella di Diego, che è quella adottata nel progetto, dato che non abbiamo un cliente stupido)

In tal modo potrete seguire la successiva implementazione del codice. Il codice della ozFrmClienti di Oscar è quello originale, mentre quello della FrmClienti è quello revisionato da Diego. In tal modo avete la possibilità di fare confronti e cercare di capire il motivo delle differenze (di solito solo cosmetiche).
Nel seguito si fa riferimento alla FrmCllienti, non alla ozFrmClienti.

Il codice della FrmClienti
Premettendo che non sempre si è potuto ri-formattare il codice in modo da renderlo meglio leggibile (quindi aspettatevi qualche punto da leggere con pazienza e attenzione), cominciamo con le direttive di importazione:

#Region "Dichiarazione degli imports"
Imports APP.Data
Imports APP.Data.SqlHelper
Imports APP.Data.ConvalidaDati
Imports APP.Data.ConversioneTipo
Imports APP.UI
Imports APP.UI.Formattazione
Imports APP.PrimiPassi.Dettagli
Imports System.Data.SqlClient
#End Region

Tra le quali si fa notare APP.PrimiPassi.Dettagli, il namespace creato nell'articolo precedente.
Dopo la dichiarazione dell'evento ActiveControlChanged, che ormai dovreste conoscere, ecco i campi:

  Private Dati As DatiMasterDetails
  Private mOperazione As TipoOperazione
  Private mPulisciControlli As SvuotaControlli
  Private mBancaCorrente As String
  Private mSediCliente As New List(Of ClientiSedi) ' nuove Sedi
  Private mRiferimentiCliente As New List(Of ClientiRiferimenti) ' nuovi Riferimenti

Stavolta, il campo Dati è di tipo DatiMasterDetails, non di tipo Tabella; di mOperazione e di mPulisciControlli dovreste ormai sapere tutto.
Il campo mBancaCorrente serve da deposito per indurre uno specifico comportamento: esso viene valorizzato al momento dell'ingresso nella casella combinata relativa al nome della banca (evento Enter); al momento dell'uscita (evento Leave) si controlla se il contenuto è stato modificato, nel qual caso si fa una ricerca nella tabella delle banche: se si trova una sola corrispondenza, si compilano le caselle dell'agenzia, dell'ABI e del CAB; se vengono trovate più occorenze di quella banca, si apre la form di ricerca e si fa selezionare all'utente, fra le sue, l'agenzia che desidera, con conseguente popolamento dei controlli relativi.
I campi mSediCliente e mRiferimentiCliente sono insiemi delle classi dati già esposte nell'articolo precedente che servono a gestire inserimenti, modifiche, cancellazioni di righe nelle due griglie.

  Private Sub frmClienti_Load(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles MyBase.Load
    Try
      mOperazione = TipoOperazione.Nessuna
      mPulisciControlli = New SvuotaControlli

      'Impostazione classe Master-Dettagli
      Dati = New DatiMasterDetails(ConnessioneDatabase, "Clienti")

Tra le prime operazioni svolte, dopo l'inizializzazione di due campi, viene creata l'istanza della DatiMasterdetails (anziché della classe Tabella, come in FrmStati). Ricordiamo che, comunque, DatiMasterdetails usa Tabella. Se ritenete opportuno un veloce ripasso, per comprendere i passi successivi, se ne è parlato nelle puntate 9 (Tabella) e 10 (DatiMasterdetails).

Nellle righe successive si impostano proprietà della tabella master e si aggiungono le tabelle di dettaglio facendosi restituire l'indice per riferirisi a esse, si imposta l'ordinamento dei dettagli e le relazioni tra master e detail.

      'Impostazione Master
      Dati.NomeCampoModificaMaster = "Modifica"
      Dati.OrdinamentoMaster = "Codice"

      'Impostazione Dettagli - Clienti_Sedi
      Dim indice As Integer = Dati.AggiungiTabellaDettaglio("Clienti_Sedi")
      Dati.ImpostaOrdinamentoDettaglio(indice, "Codice, Nome, Citta")
      Dati.AggiungiRelazione(indice, "Codice", "Codice")

      'Impostazione Dettagli - Clienti_Riferimenti
      indice = Dati.AggiungiTabellaDettaglio("Clienti_Riferimenti")
      Dati.ImpostaOrdinamentoDettaglio(indice, "Riferimento")
      Dati.AggiungiRelazione(indice, "Codice", "Codice")

Quindi si caricano i dati e si popolano le griglie (notate il parametro di tipo GrigliaDaPreparare di cui sopra).

      'Caricamento dati
      Dati.CaricaDati()

      'Preparazione delle griglie
      PreparaGriglie(GrigliaDaPreparare.Tutte)

      VisualizzaDati()

Concettualmente, i metodi PreparaGriglie e VisualizzaDati sono analoghi a quelli già incontrati precedentemente, soltanto un po' più laboriosi, dato l'elevato numero di campi. VisualizzaDati si differenzia per la chiamata finale al metodo PopolaGriglie. Ricordiamo, en passant, che in VisualizzaDati si sfrutta il metodo Value della classe ValoreCampo di cui alla puntata 17.
Mentre omettiamo, come ridondante, l'illustrazione dei metodi PreparaGriglie e VisualizzaDati - cosa che faremo anche per altri metodi del tutto simili a quelli analoghi sviluppati per le form a tabella singola - ci sembra il caso di parlare brevemente del metodo PopolaGriglie, di cui segue solo la prima parte, relativa alla griglia DgvSedi (la seconda parte, relativa alla griglia DgvRif, è analoga):

  Private Sub PopolaGriglie(ByVal griglia As GrigliaDaPreparare)

    ' sedi
    If griglia = GrigliaDaPreparare.Prima Or griglia = GrigliaDaPreparare.Tutte Then
      SvuotaGriglie(GrigliaDaPreparare.Prima)

      If mOperazione = TipoOperazione.Nessuna Then

        If Dati.DataSetDettaglio(0).Tables(0).Rows.Count > 0 Then
          For indr As Integer = 0 To Dati.DataSetDettaglio(0).Tables(0).Rows.Count - 1
            Dim rigan As DataGridViewRow = New DataGridViewRow
            rigan.CreateCells(DgvSedi, New Object() _
                              { _
                              Dati.DataSetDettaglio(0).Tables(0).Rows(indr)("Codice"), _
                              Dati.DataSetDettaglio(0).Tables(0).Rows(indr)("Nome"), _
                              Dati.DataSetDettaglio(0).Tables(0).Rows(indr)("Indirizzo"), _
                              Dati.DataSetDettaglio(0).Tables(0).Rows(indr)("CAP"), _
                              Dati.DataSetDettaglio(0).Tables(0).Rows(indr)("Citta"), _
                              Dati.DataSetDettaglio(0).Tables(0).Rows(indr)("Provincia"), _
                              Dati.DataSetDettaglio(0).Tables(0).Rows(indr)("Stato"), _
                              Dati.DataSetDettaglio(0).Tables(0).Rows(indr)("Telefono"), _
                              Dati.DataSetDettaglio(0).Tables(0).Rows(indr)("Fax"), _
                              Dati.DataSetDettaglio(0).Tables(0).Rows(indr)("Cellulare") _
                              })
            DgvSedi.Rows.Add(rigan)
          Next indr
        End If

      Else

        If mSediCliente.Count > 0 Then
          For indr As Integer = 0 To mSediCliente.Count - 1
            Dim rigan As DataGridViewRow = New DataGridViewRow
            rigan.CreateCells(DgvSedi, New Object() _
                              { _
                              mSediCliente(indr).Codice, _
                              mSediCliente(indr).RagSoc, _
                              mSediCliente(indr).Ind, _
                              mSediCliente(indr).Cap, _
                              mSediCliente(indr).Citta, _
                              mSediCliente(indr).Prov, _
                              mSediCliente(indr).Stato, _
                              mSediCliente(indr).Tel, _
                              mSediCliente(indr).Fax, _
                              mSediCliente(indr).Cel _
                              })
            DgvSedi.Rows.Add(rigan)
          Next indr
        End If
      End If
    End If

Come potete vedere, si riceve un parametro che informa su quali griglie bisogna popolare e poi, per ciascuna griglia, si procede al popolamento: a seconda del tipo di operazione in corso (nessuna o qualcuna) si traggono nuove righe per la DataGridView dal dataset o dalla relativa lista di classi-dati.

Per quanto riguarda la form, è appena il caso di accennare che anche stavolta si gestisce l'evento Activated per richiamare il metodo PopolaCombo, e l'evento FormClosing per controllare se sia necessario un salvataggio.
Del metodo PopolaCombo riteniamo di qualche interesse commentare le prime righe:

  Private Sub PopolaCombo()
    Dim dstStato, dstPagam, dstBanca As DataSet
    Dim adp As SqlDataAdapter, cmd As SqlCommand
    Dim valoriPrec As String() = {CmbStato.Text, CmbPagam.Text, CmbBanca.Text}

Dopo la dichiarazione degli oggetti Data, si popola un vettore di stringhe con i valori al momento contenuti nelle caselle combinate, in modo da ripristinarli dopo il caricamento. Un programmatore ex-VB6 potrebbe anche non conoscere questa comoda possibilità di assegnazione in linea.

Tra le gestioni degli eventi dei controlli nelle quali viene scatenato l'evento ActiveControlChanged, hanno qualche rilievo, come già accennato sopra, quelli relativi alla casella combinata CmbBanca:

  Private Sub ElenchiATendina_Enter(ByVal sender As Object, ByVal e As System.EventArgs) _
                                Handles CmbStato.Enter, CmbPagam.Enter, CmbBanca.Enter
    '...
    Select Case DirectCast(sender, ComboBox).Name
      '...
      Case "cmbBanca"
        mBancaCorrente = CmbBanca.Text
        '...
    End Select
    '...
  End Sub

In fase di ingresso, viene depositato nel campo mBancaCorrente il valore attualmente mostrato in CmbBanca. Ad esso si fa poi riferimento in fase di uscita per per ottenere il comportamento già sopra descritto:

  Private Sub CmbBanca_Leave(ByVal sender As System.Object, ByVal e As System.EventArgs)
    If CmbBanca.Text = mBancaCorrente Or CmbBanca.Text = String.Empty Then
      Exit Sub
    End If

    Dim dstAgenzie As DataSet
    Dim adp As SqlDataAdapter
    Dim cmd As SqlCommand

    Try
      'Cerco l'agenzia corrispondente alla banca
      cmd = New SqlCommand()
      cmd.Connection = SqlHelper.ConnessioneDatabase
      cmd.CommandText = "SELECT Agenzia, ABI, CAB FROM Banche WHERE Banca = @Banca ORDER BY Agenzia"

      cmd.Parameters.AddWithValue("@Banca", CmbBanca.Text)
      cmd.Parameters("@Banca").SqlDbType = SqlDbType.VarChar

      adp = New SqlDataAdapter(cmd)
      dstAgenzie = New DataSet
      adp.Fill(dstAgenzie, "Agenzie")

      'Valorizzo l'agenzia della banca
      If dstAgenzie.Tables(0).Rows.Count = 0 Then
        TxtAgenzia.Text = ""
        Exit Sub

      ElseIf dstAgenzie.Tables(0).Rows.Count = 1 Then
        ' se viene trovata una sola agenzia
        TxtAgenzia.Text = dstAgenzie.Tables(0).Rows(0).Item("Agenzia").ToString
        If Not dstAgenzie.Tables(0).Rows(0).Item("ABI") Is DBNull.Value Then
          TxtABI.Text = dstAgenzie.Tables(0).Rows(0).Item("ABI").ToString
        Else
          TxtABI.Text = String.Empty
        End If
        If Not dstAgenzie.Tables(0).Rows(0).Item("CAB") Is DBNull.Value Then
          TxtCAB.Text = dstAgenzie.Tables(0).Rows(0).Item("CAB").ToString
        Else
          TxtCAB.Text = String.Empty
        End If

      ElseIf dstAgenzie.Tables(0).Rows.Count > 1 Then
        ' se vengono trovate più agenzie
        Dim ir As InfoRicerca
        Dim filtro(0) As String

        ir.TitoloForm = "Ricerca Agenzia"
        ir.SorgenteDati = "SELECT Agenzia, ABI, CAB, Indirizzo, Citta, Provincia, Stato FROM Banche"
        ir.Ordinamento = "Agenzia"
        filtro(0) = CmbBanca.Text.ToString
        ir.Criteri = New List(Of Criterio)
        ir.Criteri.Add(New Criterio("Banca", TipoOrigineDati.Filtro, "Banca", "Banca", _
                                    SqlDbType.VarChar, False, TipoConfronto.Uguale, "Banche", _
                                    filtro))
        ir.Colonne = New List(Of Colonna)
        ir.Colonne.Add(New Colonna("Agenzia", "Agenzia", 290, False, True, True, SqlDbType.VarChar))
        ir.Colonne.Add(New Colonna("ABI", "ABI", 50, False, True, False, SqlDbType.VarChar))
        ir.Colonne.Add(New Colonna("CAB", "CAB", 50, False, True, False, SqlDbType.VarChar))
        ir.Colonne.Add(New Colonna("Indirizzo", "Indirizzo", 290, False, True, False, _
                                   SqlDbType.VarChar))
        ir.Colonne.Add(New Colonna("Citta", "Città", 290, False, True, False, SqlDbType.VarChar))
        ir.Colonne.Add(New Colonna("Provincia", "Prov.", 70, False, True, False, SqlDbType.VarChar))
        ir.Colonne.Add(New Colonna("Stato", "Stato", 290, False, True, False, SqlDbType.VarChar))

        Dim frm As New FrmRicerca(ir)
        Dim risposta As DialogResult = frm.ShowDialog()
        Dim campiChiave As List(Of String)

        If risposta = Windows.Forms.DialogResult.OK Then
          campiChiave = frm.CampiChiave
        Else
          Exit Sub
        End If
        If campiChiave.Count = 0 Then
          Exit Sub
        End If

        'Valorizzo i campi dell'agenzia, dell'ABI e del CAB
        TxtAgenzia.Text = campiChiave(1)
        TxtABI.Text = campiChiave(2)
        TxtCAB.Text = campiChiave(3)
      End If

I commenti contenuti nel codice dovrebbero essere sufficienti, della FrmRicerca si è parlato nelle puntate a partire dalla tredicesima (nella dodicesima c'è solo una critica al codice originale, nella quindicesima c'è una correzione).
E' da notare come nel caso si trovino più agenzie la proprietà SorgenteDati dell'oggetto InfoRicerca non venga valorizzata con un nome di tabella del database, ma con una query e come l'unico Criterio passato alla FrmRicerca (per la sua ComboBox) abbia come tipo di origine dati filtro e come sia presente un valore nel vettore che nelle form descritte nelle precedenti puntate veniva lasciato vuoto. Il valore passato è naturalmente il nome della banca che l'utente ha digitato in CmbBanca.

  Private Sub BtnNuovo_Click(ByVal sender As System.Object, _
                             ByVal e As System.EventArgs) Handles BtnNuovo.Click
    'Mi salvo il codice del record visualizzato in modo che
    'se viene cliccato l'annulla torno a visualizzare quel record
    Dati.LeggiRecordMaster()

    'Svuoto gli array di inserimento delle nuove righe e modifica delle esistenti per i dettagli
    mSediCliente.Clear()
    mRiferimentiCliente.Clear()

    BloccaBarra()
    SvuotaCampi()
    'Imposto il codice Cliente
    TxtCodice.Text = NuovoCodice().ToString
    TxtNome.Select()
    mOperazione = TipoOperazione.Inserimento
  End Sub

Il codice per aggiungere un nuovo cliente, dopo aver salvato il record corrente e svuotato le liste di oggetti-dato, bloccato la barra e svuotato i controlli, usa il metodo NuovoCodice per ottenere il prossimo codice cliente usufruibile e proporlo all'utente.

  Private Function NuovoCodice() As Integer

    Try
      'Cerco il codice cliente più alto salvato
      Dim cmdTmp As New SqlCommand
      cmdTmp.Connection = ConnessioneDatabase
      cmdTmp.CommandText = "SELECT Codice FROM Clienti ORDER BY Codice DESC"
      ConnessioneDatabase.Open()
      Dim ris As Object = cmdTmp.ExecuteScalar()
      ConnessioneDatabase.Close()
      If ris Is DBNull.Value Or ris Is Nothing Then
        Return 1
      End If
      Return Convert.ToInt32(ris) + 1

Questo metodo ottiene l'ultimo codice dalla tabella Clienti; se non ci sono record, restituisce 1 (è il primo record), viceversa restituisce il numero successivo. Non è detto che questo sarà realmente il codice che verrà salvato per il nuovo cliente: infatti, per gestire la multiutenza e ridurre al minimo il problema della duplicazione dei record, questo metodo verrà chiamato nuovamente subito prima del salvataggio del record.

L'illustrazione del metodo per la gestione del click sul pulsante BtnModifica richiede un po' di attenzione:

  Private Sub BtnModifica_Click(ByVal sender As System.Object, _
                                ByVal e As System.EventArgs) Handles BtnModifica.Click
    'Controllo se record è già stato variato
    If Not Dati.ImpostaModificaRecordMaster Then
      Exit Sub
    End If

    'Svuoto gli array di inserimento delle nuove righe e modifica delle esistenti per i dettagli
    mSediCliente.Clear()
    mRiferimentiCliente.Clear()

    'Compilo gli array di inserimento delle nuove righe e modifica delle esistenti
    'per i dettagli con i dati precedentemente salvati

    'Sedi
    For indr As Integer = 0 To Dati.DataSetDettaglio(0).Tables(0).Rows.Count - 1
      AggiungiNuovoElementoASedi( _
        ValoreCampo(Of Integer).Value(StringToInteger(Dati.DataSetDettaglio(0).Tables(0).Rows(indr) _
                                      ("Codice").ToString), Integer.MaxValue), _
        ValoreCampo(Of String).Value(Dati.DataSetDettaglio(0).Tables(0).Rows(indr) _
                                     ("Nome"), String.Empty), _
        ValoreCampo(Of String).Value(Dati.DataSetDettaglio(0).Tables(0).Rows(indr) _
                                     ("Indirizzo"), String.Empty), _
        ValoreCampo(Of String).Value(Dati.DataSetDettaglio(0).Tables(0).Rows(indr) _
                                     ("Citta"), String.Empty), _
        ValoreCampo(Of String).Value(Dati.DataSetDettaglio(0).Tables(0).Rows(indr) _
                                     ("CAP"), String.Empty), _
        ValoreCampo(Of String).Value(Dati.DataSetDettaglio(0).Tables(0).Rows(indr) _
                                     ("Provincia"), String.Empty), _
        ValoreCampo(Of String).Value(Dati.DataSetDettaglio(0).Tables(0).Rows(indr) _
                                     ("Stato"), String.Empty), _
        ValoreCampo(Of String).Value(Dati.DataSetDettaglio(0).Tables(0).Rows(indr) _
                                     ("Telefono"), String.Empty), _
        ValoreCampo(Of String).Value(Dati.DataSetDettaglio(0).Tables(0).Rows(indr) _
                                     ("Fax"), String.Empty), _
        ValoreCampo(Of String).Value(Dati.DataSetDettaglio(0).Tables(0).Rows(indr) _
                                     ("Cellulare"), String.Empty))
    Next indr

    'Riferimenti
    For indr As Integer = 0 To Dati.DataSetDettaglio(1).Tables(0).Rows.Count - 1
      AggiungiNuovoElementoARiferimenti( _
        ValoreCampo(Of Integer).Value(StringToInteger(Dati.DataSetDettaglio(1).Tables(0).Rows(indr) _
                                                   ("Codice").ToString), Integer.MaxValue), _
        ValoreCampo(Of String).Value(Dati.DataSetDettaglio(1).Tables(0).Rows(indr) _
                                     ("Riferimento"), String.Empty), _
        ValoreCampo(Of String).Value(Dati.DataSetDettaglio(1).Tables(0).Rows(indr) _
                                     ("Telefono"), String.Empty), _
        ValoreCampo(Of String).Value(Dati.DataSetDettaglio(1).Tables(0).Rows(indr) _
                                     ("Fax"), String.Empty), _
        ValoreCampo(Of String).Value(Dati.DataSetDettaglio(1).Tables(0).Rows(indr) _
                                     ("Cellulare"), String.Empty), _
        ValoreCampo(Of String).Value(Dati.DataSetDettaglio(1).Tables(0).Rows(indr) _
                                     ("EMail"), String.Empty))
    Next indr

    VisualizzaDati()
    BloccaBarra()
    TxtInd.Select()
    TxtCodice.ReadOnly = True
    mOperazione = TipoOperazione.Modifica
  End Sub

Dopo aver verificato la possibilità di effettuare l'operazione e svuotato le liste di oggetti-dato, si passano a queste ultime i valori contenuti nella riga del dataset del dettaglio - preparandoli attraverso il metodo Value della classe ValoreCampo e, nel caso del codice, sfruttando anche il metodo StringToInteger della classe ConversioneTipo (vedi puntata 17).
Per questo scopo (aggiungere oggetti-dato alle liste) sono stati implementati specifici metodi:

  Private Sub AggiungiNuovoElementoASedi(ByVal codiceSede As Integer, _
                                         ByVal ragSocSede As String, _
                                         ByVal indSede As String, _
                                         ByVal cittaSede As String, _
                                         Optional ByVal capSede As String = "", _
                                         Optional ByVal provSede As String = "", _
                                         Optional ByVal statoSede As String = "", _
                                         Optional ByVal telSede As String = "", _
                                         Optional ByVal faxSede As String = "", _
                                         Optional ByVal celSede As String = "")
    'Controllo chiave duplicata
    For i As Integer = 0 To mSediCliente.Count - 1
      If codiceSede = mSediCliente(i).Codice And ragSocSede = mSediCliente(i).RagSoc _
        And cittaSede = mSediCliente(i).Citta Then

        Messaggi.Avviso("Sede già inserita !", "Attenzione")
        Exit Sub
      End If
    Next i

    Dim nuovaSede As New ClientiSedi

    'Aggiungo un nuovo elemento alle sedi durante un nuovo inserimento
    nuovaSede.Codice = codiceSede
    nuovaSede.RagSoc = ragSocSede
    nuovaSede.Ind = indSede
    nuovaSede.Cap = capSede
    nuovaSede.Citta = cittaSede
    nuovaSede.Prov = provSede
    nuovaSede.Stato = statoSede
    nuovaSede.Tel = telSede
    nuovaSede.Fax = faxSede
    nuovaSede.Cel = celSede

    mSediCliente.Add(nuovaSede)
  End Sub

  Private Sub AggiungiNuovoElementoARiferimenti(ByVal codiceRif As Integer, _
                                                ByVal rifRif As String, _
                                                Optional ByVal telRif As String = "", _
                                                Optional ByVal faxRif As String = "", _
                                                Optional ByVal celRif As String = "", _
                                                Optional ByVal mailRif As String = "")
    'Controllo chiave duplicata
    For i As Integer = 0 To mRiferimentiCliente.Count - 1
      If codiceRif = mRiferimentiCliente(i).Codice And rifRif = mRiferimentiCliente(i).Rif Then
        Messaggi.Avviso("Riferimento già inserito !", "Attenzione")
        Exit Sub
      End If
    Next i

    Dim rifNuovo As New ClientiRiferimenti

    'Aggiungo un nuovo elemento ai riferimenti durante un nuovo inserimento
    rifNuovo.Codice = codiceRif
    rifNuovo.Rif = rifRif
    rifNuovo.Tel = telRif
    rifNuovo.Fax = faxRif
    rifNuovo.Cel = celRif
    rifNuovo.Mail = mailRif

    mRiferimentiCliente.Add(rifNuovo)
  End Sub

Questi metodi ricevono i valori da assegnare alle proprietà dell'oggetto dato, sia esso di tipo ClientiSedi o ClientiRiferimenti; alcuni parametri, non essendo per campi a digitazione obbligatoria, sono opzionali.
Dapprima si controlla che l'oggetto non sia già presente nella lista (nel qual caso causerebbe eccezioni al momento di aggiornare il database), quindi, se non lo è, se ne crea uno nuovo, se ne valorizzano le proprietà e lo si aggiunge alla lista.
Questi metodi sono richiamati anche da pulsanti posti sotto le griglie, come vedremo nel seguito).

  Private Sub BtnConferma_Click(ByVal sender As System.Object, _
                                ByVal e As System.EventArgs) Handles BtnConferma.Click
    Try
      If Not DatiValidi() Then Exit Sub

      Dim errori As Boolean = False

      If mOperazione = TipoOperazione.Inserimento Then
        'Imposto il codice Cliente
        TxtCodice.Text = NuovoCodice.ToString
      End If

      CaricaRiga()

      If mOperazione = TipoOperazione.Inserimento Then

        Dati.AggiornaDataBase(DatiMasterDetails.TipoSalvataggio.SoloMaster)

        'Salvo i dati delle Sedi
        If mSediCliente.Count > 0 Then
          errori = False
          Try

            For indRighe As Integer = 0 To mSediCliente.Count - 1
              Dim riga As DataRow = Dati.RigaNuovaDettaglio(0)
              riga("Codice") = Integer.Parse(TxtCodice.Text)
              riga("Nome") = _
                ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).RagSoc, String.Empty)
              riga("Indirizzo") = _
                ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Ind, String.Empty)
              riga("CAP") = _
                ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Cap, String.Empty)
              riga("Citta") = _
                ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Citta, String.Empty)
              riga("Provincia") = _
                ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Prov, String.Empty)
              riga("Stato") = _
                ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Stato, String.Empty)
              riga("Telefono") = _
                ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Tel, String.Empty)
              riga("Fax") = _
                ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Fax, String.Empty)
              riga("Cellulare") = _
                ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Cel, String.Empty)
              'Aggiungo la riga al DataSet di dettaglio delle Sedi
              If Not Dati.AggiungiRigaNuovaDettaglio(0, riga) Then
                Messaggi.Errore(Dati.DescrizioneErrore, "Errore")
                errori = True
              End If
            Next indRighe

          Catch ex As Exception
            Messaggi.Errore(ex.ToString, "Errore")
            errori = True
          End Try
        End If

        If errori = False Then
          'Salvo i dati dei Riferimenti
          If mRiferimentiCliente.Count > 0 Then
            Try

              For indRighe As Integer = 0 To mRiferimentiCliente.Count - 1
                Dim riga As DataRow = Dati.RigaNuovaDettaglio(1)
                riga("Codice") = Integer.Parse(TxtCodice.Text)
                riga("Riferimento") = _
                  ValoreCampo(Of String).TryParseToDBNull(mRiferimentiCliente(indRighe).Rif, _
                                                          String.Empty)
                riga("Telefono") = _
                  ValoreCampo(Of String).TryParseToDBNull(mRiferimentiCliente(indRighe).Tel, _
                                                          String.Empty)
                riga("Fax") = _
                  ValoreCampo(Of String).TryParseToDBNull(mRiferimentiCliente(indRighe).Fax, _
                                                          String.Empty)
                riga("Cellulare") = _
                  ValoreCampo(Of String).TryParseToDBNull(mRiferimentiCliente(indRighe).Cel, _
                                                          String.Empty)
                riga("EMail") = _
                  ValoreCampo(Of String).TryParseToDBNull(mRiferimentiCliente(indRighe).Mail, _
                                                          String.Empty)
                'Aggiungo la riga al DataSet di dettaglio dei Riferimenti
                If Not Dati.AggiungiRigaNuovaDettaglio(1, riga) Then
                  Messaggi.Errore(Dati.DescrizioneErrore, "Errore")
                  errori = True
                End If
              Next indRighe

            Catch ex As Exception
              Messaggi.Errore(ex.ToString, "Errore")
              errori = True
            End Try
          End If
        End If

        'Salvo i dati dei dettagli
        If errori = False Then
          Dati.AggiornaDataBase(DatiMasterDetails.TipoSalvataggio.SoloDettagli)
        End If

      ElseIf mOperazione = TipoOperazione.Modifica Then



        'Svuoto i DataTable precedenti
        Try

          For indRighe As Integer = 0 To Dati.DataSetDettaglio(0).Tables(0).Rows.Count - 1
            Dati.DataSetDettaglio(0).Tables(0).Rows(indRighe).Delete()
          Next indRighe
          For indRighe = 0 To Dati.DataSetDettaglio(1).Tables(0).Rows.Count - 1
            Dati.DataSetDettaglio(1).Tables(0).Rows(indRighe).Delete()
          Next indRighe

        Catch ex As Exception
          Messaggi.Errore(ex.ToString, "Errore")
          errori = True
        End Try

        If errori = False Then
          'Salvo i dati delle Sedi
          If mSediCliente.Count > 0 Then
            errori = False
            Try

              For indRighe As Integer = 0 To mSediCliente.Count - 1
                Dim riga As DataRow = Dati.RigaNuovaDettaglio(0)
                riga("Codice") = Integer.Parse(TxtCodice.Text)
                riga("Nome") = _
                  ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).RagSoc, String.Empty)
                riga("Indirizzo") = _
                  ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Ind, String.Empty)
                riga("CAP") = _
                  ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Cap, String.Empty)
                riga("Citta") = _
                  ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Citta, String.Empty)
                riga("Provincia") = _
                  ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Prov, String.Empty)
                riga("Stato") = _
                  ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Stato, String.Empty)
                riga("Telefono") = _
                  ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Tel, String.Empty)
                riga("Fax") = _
                  ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Fax, String.Empty)
                riga("Cellulare") = _
                  ValoreCampo(Of String).TryParseToDBNull(mSediCliente(indRighe).Cel, String.Empty)
                'Aggiungo la riga al DataSet di dettaglio delle Sedi
                If Not Dati.AggiungiRigaNuovaDettaglio(0, riga) Then
                  Messaggi.Errore(Dati.DescrizioneErrore, "Errore")
                  errori = True
                End If
              Next indRighe

            Catch ex As Exception
              Messaggi.Errore(ex.ToString, "Errore")
              errori = True
            End Try
          End If
        End If

        If errori = False Then
          'Salvo i dati dei Riferimenti
          If mRiferimentiCliente.Count > 0 Then
            Try

              For indRighe As Integer = 0 To mRiferimentiCliente.Count - 1
                Dim riga As DataRow = Dati.RigaNuovaDettaglio(1)
                riga("Codice") = Integer.Parse(TxtCodice.Text)
                riga("Riferimento") = _
                  ValoreCampo(Of String).TryParseToDBNull(mRiferimentiCliente(indRighe).Rif, _
                                                          String.Empty)
                riga("Telefono") = _
                  ValoreCampo(Of String).TryParseToDBNull(mRiferimentiCliente(indRighe).Tel, _
                                                          String.Empty)
                riga("Fax") = _
                  ValoreCampo(Of String).TryParseToDBNull(mRiferimentiCliente(indRighe).Fax, _
                                                          String.Empty)
                riga("Cellulare") = _
                  ValoreCampo(Of String).TryParseToDBNull(mRiferimentiCliente(indRighe).Cel, _
                                                          String.Empty)
                riga("EMail") = _
                  ValoreCampo(Of String).TryParseToDBNull(mRiferimentiCliente(indRighe).Mail, _
                                                          String.Empty)
                'Aggiungo la riga al DataSet di dettaglio dei Riferimenti
                If Not Dati.AggiungiRigaNuovaDettaglio(1, riga) Then
                  Messaggi.Errore(Dati.DescrizioneErrore, "Errore")
                  errori = True
                End If
              Next indRighe

            Catch ex As Exception
              Messaggi.Errore(ex.ToString, "Errore")
              errori = True
            End Try
          End If
        End If

        If errori = False Then
          Dati.AggiornaDataBase(DatiMasterDetails.TipoSalvataggio.Tutto)
        End If
      End If

      TxtCodice.ReadOnly = False

      Dati.CaricaDati()
      If Not Dati.TrovaRecord() Then
        Messaggi.Avviso("Il record appena salvato non è stato trovato !", "Salvataggio record")
      Else
        VisualizzaDati()
      End If
      mOperazione = TipoOperazione.Nessuna
      SbloccaBarra()
      TbrStrumenti.Select()

      'Svuoto gli array di inserimento delle nuove righe e modifica delle esistenti per i dettagli
      mSediCliente.Clear()
      mRiferimentiCliente.Clear()
    Catch ex As SqlException
      Select Case ex.ErrorCode
        Case -2146232060
          Messaggi.Errore(ex.Message, "Attenzione")
          'MessageBox.Show("Qualche campo ha un contenuto di lunghezza superiore a quello consentito")
          mOperazione = TipoOperazione.Nessuna
        Case Else
          Messaggi.Errore(ex.Message, "Attenzione")
      End Select

    Catch ex As Exception
      Messaggi.Errore(ex.Message, "Attenzione")
    End Try
  End Sub

In fase di salvataggio, dopo aver verificato la validità dei dati (DatiValidi) e ottenuto (come già accennato sopra) il codice (NuovoCodice) dell'eventuale nuovo record, nonché dopo aver caricato una riga (come si vedrà nel seguito) si deve seguire una logica differente a seconda che il salvataggio riguardi un cliente nuovo o la modifica di un cliente esistente.
La logica di funzionamento per il nuovo inserimento è simile a quella seguita nelle form a tabella singola, pur gestendo anche il salvataggio dei dati delle griglie nelle tabelle di dettaglio. Quella per la modifica invece è completamente differente: si eliminano tutti i record di dettaglio relativi al cliente e si inseriscono nuovamente. Sarà poi il DataSet a preoccuparsi dell'aggiornamento corretto dei dati nel database.

Se ci sono oggetti-dato da inserire, si istanziano, si valorizzano e si aggiungono (AggiungiRigaNuovaDettaglio) nuove DataRows al Dataset. Stavolta, si usa il metodo TryParseToDBNull della classe ValoreCampo, per assicurare la congruità dei dati e il rispetto delle relazioni tra master e dettagli. Se durante il salvataggio delle singole righe sorge un'eccezione, si alza il flag errori e si termina la procedura, avvisando della cosa l'utente.

  Private Sub CaricaRiga()
    Dim rigaNuova As DataRow = Nothing

    If mOperazione = TipoOperazione.Inserimento Then
      rigaNuova = Dati.RigaNuovaMaster
    Else
      Dati.RigaCorrenteMaster(rigaNuova)
    End If

    rigaNuova("Codice") = _
      ValoreCampo(Of Integer).TryParseToDBNull(StringToInteger(TxtCodice.Text), Integer.MaxValue)
    rigaNuova("Nome") = ValoreCampo(Of String).TryParseToDBNull(TxtNome.Text, String.Empty)
    rigaNuova("Indirizzo") = ValoreCampo(Of String).TryParseToDBNull(TxtInd.Text, String.Empty)
    rigaNuova("CAP") = ValoreCampo(Of String).TryParseToDBNull(TxtCAP.Text, String.Empty)
    rigaNuova("Citta") = ValoreCampo(Of String).TryParseToDBNull(TxtCitta.Text, String.Empty)
    rigaNuova("Provincia") = ValoreCampo(Of String).TryParseToDBNull(TxtProv.Text, String.Empty)
    rigaNuova("Stato") = ValoreCampo(Of String).TryParseToDBNull(CmbStato.Text, String.Empty)
    rigaNuova("Telefono") = ValoreCampo(Of String).TryParseToDBNull(TxtTel1.Text, String.Empty)
    rigaNuova("Fax") = ValoreCampo(Of String).TryParseToDBNull(TxtFax1.Text, String.Empty)
    rigaNuova("Cellulare") = ValoreCampo(Of String).TryParseToDBNull(TxtCel1.Text, String.Empty)
    rigaNuova("Telefono1") = ValoreCampo(Of String).TryParseToDBNull(TxtTel2.Text, String.Empty)
    rigaNuova("Fax1") = ValoreCampo(Of String).TryParseToDBNull(TxtFax2.Text, String.Empty)
    rigaNuova("Cellulare1") = ValoreCampo(Of String).TryParseToDBNull(TxtCel2.Text, String.Empty)
    rigaNuova("Codice_fiscale") = ValoreCampo(Of String).TryParseToDBNull(TxtCodFis.Text, String.Empty)
    rigaNuova("Partita_iva") = ValoreCampo(Of String).TryParseToDBNull(TxtPIVA.Text, String.Empty)
    rigaNuova("Pagamento") = ValoreCampo(Of String).TryParseToDBNull(CmbPagam.Text, String.Empty)
    rigaNuova("Banca") = ValoreCampo(Of String).TryParseToDBNull(CmbBanca.Text, String.Empty)
    rigaNuova("Agenzia") = ValoreCampo(Of String).TryParseToDBNull(TxtAgenzia.Text, String.Empty)
    rigaNuova("ABI") = ValoreCampo(Of String).TryParseToDBNull(TxtABI.Text, String.Empty)
    rigaNuova("CAB") = ValoreCampo(Of String).TryParseToDBNull(TxtCAB.Text, String.Empty)
    rigaNuova("NumCC") = ValoreCampo(Of String).TryParseToDBNull(TxtNumCC.Text, String.Empty)
    rigaNuova("TitCC") = ValoreCampo(Of String).TryParseToDBNull(TxtTitCC.Text, String.Empty)
    rigaNuova("CIN") = ValoreCampo(Of String).TryParseToDBNull(TxtCIN.Text, String.Empty)
    rigaNuova("IBAN") = ValoreCampo(Of String).TryParseToDBNull(TxtIBAN.Text, String.Empty)
    rigaNuova("Internet") = ValoreCampo(Of String).TryParseToDBNull(TxtInternet.Text, String.Empty)
    rigaNuova("EMail") = ValoreCampo(Of String).TryParseToDBNull(TxtMail.Text, String.Empty)
    rigaNuova("Riclis") = _
      ValoreCampo(Of Single).TryParseToDBNull(StringToSingle(TxtRicLis.Text), Single.MaxValue)
    rigaNuova("Oratecs") = _
      ValoreCampo(Of Decimal).TryParseToDBNull(StringToDecimal(TxtCOTecSpec.Text), Decimal.MaxValue)
    rigaNuova("Oratecsstr") = _
      ValoreCampo(Of Decimal).TryParseToDBNull(StringToDecimal(TxtCOTecSpecStr.Text), Decimal.MaxValue)
    rigaNuova("Oratec") = _
      ValoreCampo(Of Decimal).TryParseToDBNull(StringToDecimal(TxtCOTec.Text), Decimal.MaxValue)
    rigaNuova("Oratecstr") = _
      ValoreCampo(Of Decimal).TryParseToDBNull(StringToDecimal(TxtCOTecStr.Text), Decimal.MaxValue)
    rigaNuova("Oraaiu") = _
      ValoreCampo(Of Decimal).TryParseToDBNull(StringToDecimal(TxtCOAiu.Text), Decimal.MaxValue)
    rigaNuova("Oraaiustr") = _
      ValoreCampo(Of Decimal).TryParseToDBNull(StringToDecimal(TxtCOAiuStr.Text), Decimal.MaxValue)
    rigaNuova("Modifica") = False

    If mOperazione = TipoOperazione.Inserimento Then
      If Not Dati.AggiungiRigaNuovaMaster(rigaNuova) Then
        Messaggi.Errore(Dati.DescrizioneErrore, "Errore")
      Else
        Dati.LeggiRecordMaster(Dati.NumeroRigheMaster - 1)
        Dati.TrovaRecord()
      End If
    End If
  End Sub

Il metodo CaricaRiga richiamato all'inizio di BtnConferma_Click è concettualmente simile a quello per una tabella singola, ma nel popolare la nuova riga ricorre al metodo TryParseToDBNull della classe ValoreCampo.

  Private Sub BtnAnnulla_Click(ByVal sender As System.Object, _
                               ByVal e As System.EventArgs) Handles BtnAnnulla.Click
    Dim precOper As TipoOperazione = mOperazione
    mOperazione = TipoOperazione.Nessuna

    'Controllo se si stava inserendo il primo record in assoluto
    If Dati.NumeroRigheMaster = 0 Then
      PreparaVuoto()
      TxtCodice.ReadOnly = False
      'Svuoto gli array di inserimento delle nuove righe e modifica delle esistenti per i dettagli
      mSediCliente.Clear()
      mRiferimentiCliente.Clear()
      Exit Sub
    End If

    Try
      'Tolgo il blocco
      Dati.ImpostaColonnaModificaMaster(False)
      If Not Dati.TrovaRecord() Then
        Messaggi.Avviso("Errore di posizionamento sul record precedentemente visualizzato !", _
                        "Annulla operazione")
      Else
        VisualizzaDati()
      End If

    Catch ex As Exception
      mOperazione = precOper
      Messaggi.Errore(ex.ToString, "Errore")
      Exit Sub
    End Try

    SbloccaBarra()
    TbrStrumenti.Select()
    TxtCodice.ReadOnly = False

    'Svuoto gli array di inserimento delle nuove righe e modifica delle esistenti per i dettagli
    mSediCliente.Clear()
    mRiferimentiCliente.Clear()
  End Sub

Non molto diversa dalla gestione del BtnAnnulla di una form basata su una sola tabella, tranne che nelle ultime due righe, in cui vengono svuotate le due liste di oggetti-dato per prepararle a una successiva compilazione in caso di inserimento o modifica.

Per i pulsanti posti sotto le griglie, che permettono inserimento e modifica e cancellazione agendo sui dettagli esposti nelle griglie stesse, la spiegazione del codice sarà un po' più dettagliata:

  Private Sub BtnAggrigaS_Click(ByVal sender As System.Object, _
                                ByVal e As System.EventArgs) Handles BtnAggRigaS.Click

    If mOperazione = TipoOperazione.Nessuna Then Exit Sub

Innanzitutto se non è in corso alcuna operazione si esce dalla procedura senza fare alcunché, perché nulla deve essere fatto sulle griglie se non in fase di inserimento o modifica di un record cliente.

    If Me.TxtCodice.Text.Trim = String.Empty Then
      Messaggi.Avviso( _
               "Impossibile aggiungere nuove sedi prima di avere indicato il codice del cliente !", _
               "Attenzione")
      Exit Sub
    End If

Si verifica quindi che sia stato valorizzato il codice cliente e in caso contrario si esce dalla routine, in quanto è necessario alle operazioni seguenti. Essendo compilato in automatico dovrebbe essere sempre valorizzato, ma un controllo in più non fa mai male.

    Dim rigaNuova As New ClientiSedi

Si crea una nuova istanza di ClientiSedi per il passaggio delle informazioni necessarie alla form di dialogo.

    rigaNuova.Codice = Integer.Parse(Me.TxtCodice.Text)

Nel caso di nuovo inserimento l'unica informazione necessaria è il codice del cliente.

    Dim frm As New FrmClientiSedi(rigaNuova, TipoOperazione.Inserimento)
    Dim risposta As DialogResult = frm.ShowDialog()

Si crea una nuova istanza della form di inserimento/modifica delle sedi (illustrata nell'articolo precedente), passando al suo costruttore l'istanza di ClientiSedi e il tipo di operazione in corso (inserimento).
Si visualizza la form di dialogo e se ne attende la risposta, anche se, in questo caso, il valore di risposta è forzatamente ininfluente: questo è dovuto al fatto che l'utente, pur avendo magari inserito più di una sede, per uscire dalla form di dialogo deve comunque usare il pulsante di uscita; pertanto risposta riceve sempre un valore Cancel dalla form di dialogo, ma c'è ugualmente modo di controllare se l'utente ha inserito nuove sedi, verificando il numero degli elementi che compongono la lista/proprietà CampiSedi (ricordate? La proprietà dedicata a fornire i valori delle nuove sedi inserite).

    If frm.CampiSedi.Count > 0 Then

      For indSedi As Integer = 0 To frm.CampiSedi.Count - 1
        AggiungiNuovoElementoASedi( _
          ValoreCampo(Of Integer).Value(StringToInteger( _
                                       frm.CampiSedi(indSedi).Codice.ToString), Integer.MaxValue), _
          ValoreCampo(Of String).Value(frm.CampiSedi(indSedi).RagSoc, String.Empty), _
          ValoreCampo(Of String).Value(frm.CampiSedi(indSedi).Ind, String.Empty), _
          ValoreCampo(Of String).Value(frm.CampiSedi(indSedi).Citta, String.Empty), _
          ValoreCampo(Of String).Value(frm.CampiSedi(indSedi).Cap, String.Empty), _
          ValoreCampo(Of String).Value(frm.CampiSedi(indSedi).Prov, String.Empty), _
          ValoreCampo(Of String).Value(frm.CampiSedi(indSedi).Stato, String.Empty), _
          ValoreCampo(Of String).Value(frm.CampiSedi(indSedi).Tel, String.Empty), _
          ValoreCampo(Of String).Value(frm.CampiSedi(indSedi).Fax, String.Empty), _
          ValoreCampo(Of String).Value(frm.CampiSedi(indSedi).Cel, String.Empty))
      Next

    Else
      frm = Nothing
      Exit Sub
    End If

    frm = Nothing

Se ci sono nuove sedi inserite, si scandisce la lista ricevuta e si aggiungono oggetti-dati (AggiungiNuovoElementoASedi), sfruttando nuovamente il metodo Value della classe ValoreCampo.

    SvuotaGriglie(GrigliaDaPreparare.Prima)
    PopolaGriglie(GrigliaDaPreparare.Prima)

Infine si aggiorna la griglia delle sedi, prima svuotandola e poi ripopolandola con i dati della lista mSediCliente, che nel frattempo sono cambiati.

  Private Sub BtnModrigaS_Click(ByVal sender As System.Object, _
                                ByVal e As System.EventArgs) Handles BtnModRigaS.Click

    If mOperazione = TipoOperazione.Nessuna Then Exit Sub

Anche nella modifica della riga si verifica che non sia in corso alcuna operazione, ma dopo si prosegue diversamente:

    Dim nRiga As Integer = DgvSedi.CurrentRow.Index
    Dim rigaDaModificare As New ClientiSedi

    'Passo i valori della riga da modificare
    rigaDaModificare.Codice = Convert.ToInt32(DgvSedi.Rows(nRiga).Cells("Codice").Value)
    rigaDaModificare.RagSoc = DgvSedi.Rows(nRiga).Cells("Ragione Sociale").Value.ToString
    rigaDaModificare.Ind = DgvSedi.Rows(nRiga).Cells("Indirizzo").Value.ToString
    rigaDaModificare.Cap = DgvSedi.Rows(nRiga).Cells("CAP").Value.ToString
    rigaDaModificare.Citta = DgvSedi.Rows(nRiga).Cells("Citta").Value.ToString
    rigaDaModificare.Prov = DgvSedi.Rows(nRiga).Cells("Prov.").Value.ToString
    rigaDaModificare.Stato = DgvSedi.Rows(nRiga).Cells("Stato").Value.ToString
    rigaDaModificare.Tel = DgvSedi.Rows(nRiga).Cells("Telefono").Value.ToString
    rigaDaModificare.Fax = DgvSedi.Rows(nRiga).Cells("Fax").Value.ToString
    rigaDaModificare.Cel = DgvSedi.Rows(nRiga).Cells("Cellulare").Value.ToString

Si legge l'indice della riga corrente e si crea una nuova istanza di ClientiSedi, di cui valorizzano le proprietà traendo i valori dalla riga stessa. Questa ClientiSedi verrà passata alla form di dialogo.

    Dim frm As New FrmClientiSedi(rigaDaModificare, TipoOperazione.Modifica)
    Dim risposta As DialogResult = frm.ShowDialog()

Anche in questo caso creo una nuova istanza della form di inserimento/modifica delle sedi e passo al suo costruttore l'istanza di ClientiSedi, ma il tipo di operazione in corso è modifica. Stavolta, il valore di risposta è importante e va valutato: se infatti l'utente ha premuto il tasto di uscita, nulla dovrà essere fatto, viceversa i valori attuali della riga della griglia andranno sostituiti da quelli salvati nella lista di oggetti ClientiSedi, che in caso di modifica contiene sempre un solo elemento (quello di indice zero).

    If risposta = Windows.Forms.DialogResult.OK Then
      ModificaElementoSedi(ValoreCampo(Of Integer).Value( _
                           StringToInteger(frm.CampiSedi(0).Codice.ToString), Integer.MaxValue), _
                           ValoreCampo(Of String).Value(frm.CampiSedi(0).RagSoc, String.Empty), _
                           ValoreCampo(Of String).Value(frm.CampiSedi(0).Ind, String.Empty), _
                           ValoreCampo(Of String).Value(frm.CampiSedi(0).Citta, String.Empty), _
                           ValoreCampo(Of String).Value(frm.CampiSedi(0).Cap, String.Empty), _
                           ValoreCampo(Of String).Value(frm.CampiSedi(0).Prov, String.Empty), _
                           ValoreCampo(Of String).Value(frm.CampiSedi(0).Stato, String.Empty), _
                           ValoreCampo(Of String).Value(frm.CampiSedi(0).Tel, String.Empty), _
                           ValoreCampo(Of String).Value(frm.CampiSedi(0).Fax, String.Empty), _
                           ValoreCampo(Of String).Value(frm.CampiSedi(0).Cel, String.Empty))

Naturalmente, in uscita dalla procedura si aggiorna la griglia. Anche stavolta si usa il metodo Value di ValoreCampo per tipizzare i parametri, ma questi vengono passati a un metodo diverso:

  Private Sub ModificaElementoSedi(ByVal codiceSede As Integer, _
                                   ByVal ragSocSede As String, _
                                   ByVal indSede As String, _
                                   ByVal cittaSede As String, _
                                   Optional ByVal capSede As String = "", _
                                   Optional ByVal provSede As String = "", _
                                   Optional ByVal statoSede As String = "", _
                                   Optional ByVal telSede As String = "", _
                                   Optional ByVal faxSede As String = "", _
                                   Optional ByVal celSede As String = "")

    If Not mSediCliente.Count > 0 Then Exit Sub

    'Cerco il record nell'array
    For i As Integer = 0 To mSediCliente.Count - 1
      If mSediCliente(i).Codice = codiceSede And mSediCliente(i).RagSoc = ragSocSede _
        And mSediCliente(i).Citta = cittaSede Then

        mSediCliente(i).Ind = indSede
        mSediCliente(i).Cap = capSede
        mSediCliente(i).Prov = provSede
        mSediCliente(i).Stato = statoSede
        mSediCliente(i).Tel = telSede
        mSediCliente(i).Fax = faxSede
        mSediCliente(i).Cel = celSede
        Exit For
      End If
    Next i
  End Sub

Dopo un primo controllo che la lista contenga elementi, si scandisce la lista per trovare l'elemento da modificare e se ne modificano le proprietà.

  Private Sub BtnCancrigaS_Click(ByVal sender As System.Object, _
                                 ByVal e As System.EventArgs) Handles BtnCancRigaS.Click

    Dim nRiga As Integer = DgvSedi.CurrentRow.Index

    Dim risposta As DialogResult = Messaggi.SiNo( _
                    "Sei sicuro/a di volere eliminare la sede con Ragione Sociale " & _
                    DgvSedi.Rows(nRiga).Cells("Ragione Sociale").Value.ToString & _
                    " e residente a " & DgvSedi.Rows(nRiga).Cells("Citta").Value.ToString & " ?", _
                    "Eliminazione Sede")
    If risposta = Windows.Forms.DialogResult.No Then Exit Sub

    'Elimino la riga dall'array
    mSediCliente.RemoveAt(nRiga)

    'Aggiorno i dati nella griglia
    SvuotaGriglie(GrigliaDaPreparare.Prima)
    PopolaGriglie(GrigliaDaPreparare.Prima)
  End Sub

La cancellazione della riga, invece, è abbastanza semplice: dopo aver letto l'indice della riga corrente, se ne presentano alcuni valori nella richiesta di conferma della cancellazione. Se si ha la conferma, si usa il metodo RemoveAt della lista e, al solito, si aggiorna la griglia.

In modo analogo si procede per i pulsanti posti sotto la griglia riferimenti, per cui non si ritiene di esporli in questo articolo.

Resta ancora da dire che, in alcuni metodi di convalida, viene sfruttato il metodo Formatta della classe Formattazione del progetto APP.UI per la corretta visualizzazione dei dati.

Conclusione
In questo articolo abbiamo completato la descrizione dello sviluppo della prima form master-details, la FrmClienti.

Il codice di PrimiPassi sviluppato fino a questo momento è come al solito disponibile in area download.
Anche per questa puntata, Diego mette a disposizione nel suo blog un post cui scrivere critiche, suggerimenti, richieste di chiarimento.