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

Premessa
In questo articolo inizieremo a descrivere lo sviluppo della prima form master-details, la FrmClienti, che costituisce l'approccio ideale a questo tipo di form.

Nel nostro database SQL la tabella Clienti è correlata in un rapporto uno a molti alle tabelle Clienti_sedi e Clienti_riferimenti attraverso il campo Codice.
In base a ciò, verranno create, in un nuovo NameSpace, due classi di gestione dei dettagli, e due form accessorie per usarle.

La prima form Master-Details
Nella FrmClienti i dati relativi alle due tabelle di dettaglio saranno visualizzati in due griglie (DataGridView) non connesse direttamente al database. Sfrutteremo infatti finalmente la classe DatiMasterDetails.

I dati contenuti nelle griglie saranno gestiti attraverso form accessorie: una per ogni tabella di dettaglio. Grazie a queste form potremo inserire nuove righe (record) e modificare i dati di quelle già esistenti.
Nulla potrà essere variato direttamente nelle griglie. Questa scelta ci sembra la migliore (consigliata pure dallo Sceppa nel suo libro "ADO.NET 2.0"). Siamo dell'idea che inserire e modificare i dati delle griglie in form esterni richieda probabilmente più codice da scrivere, ma fornisca un'elasticità irragiungibile con l'editing diretto nelle celle della griglia.

Le operazioni di inserimento, modifica e cancellazione delle righe di ogni griglia saranno vincolate all'uso di altrettante classi create ad-hoc, mentre l'utilizzo di proprietà pubbliche renderà possibile l'interscambio dei dati con le form accessorie.

Classi per la gestione dei Details
Per la gestione dei dettagli della tabella Clienti, servono due classi la cui struttura sia speculare a quella delle tabelle Clienti_sedi e Clienti_riferimenti. Si chiameranno ClientiSedi e ClientiRiferimenti, e verranno create in un apposito namespace Dettagli.

Quindi è opportuno intervenire sulla struttura del nostro progetto, per raggruppare tutte le classi di questo tipo (classi di dettaglio).

Per fare ciò clicchiamo con il tasto destro del mouse sul nome del progetto PrimiPassi e scegliamo il menu Aggiungi... Nuova cartella. Il nome da assegnare è Dettagli.

Classe ClientiSedi

Clicchiamo col tasto destro del mouse sul nome della cartella Dettagli e aggiungiamo una classe, impostando il nome Clientisedi.
Immediatamente, racchiudiamo il codice nel nuovo Namespace che abbiamo deciso di usare:

Namespace Dettagli

  Public Class ClientiSedi

  End Class
End Namespace

Se adesso aprite il visualizzatore oggetti, vi vedrete la nuova classe nel nuovo namespace:

Adesso riportiamo diligentemente la struttura (nomi e tipi dei campi) della tabella Clienti_sedi (magari facendo uso dello snippet ottenibile scrivendo 'prop' e poi digitando due volte il tasto Tab):

    Dim mCodice As Integer = 0
    Public Property Codice() As Integer
      Get
        Return mCodice
      End Get
      Set(ByVal value As Integer)
        mCodice = value
      End Set
    End Property

    Dim mRagSoc As String = String.Empty
    Public Property RagSoc() As String
      Get
        Return mRagSoc
      End Get
      Set(ByVal value As String)
        mRagSoc = value
      End Set
    End Property

    Dim mInd As String = String.Empty
    Public Property Ind() As String
      Get
        Return mInd
      End Get
      Set(ByVal value As String)
        mInd = value
      End Set
    End Property

    Dim mCap As String = String.Empty
    Public Property Cap() As String
      Get
        Return mCap
      End Get
      Set(ByVal value As String)
        mCap = value
      End Set
    End Property

    Dim mCitta As String = String.Empty
    Public Property Citta() As String
      Get
        Return mCitta
      End Get
      Set(ByVal value As String)
        mCitta = value
      End Set
    End Property

    Dim mProv As String = String.Empty
    Public Property Prov() As String
      Get
        Return mProv
      End Get
      Set(ByVal value As String)
        mProv = value
      End Set
    End Property

    Dim mStato As String = String.Empty
    Public Property Stato() As String
      Get
        Return mStato
      End Get
      Set(ByVal value As String)
        mStato = value
      End Set
    End Property

    Dim mTel As String = String.Empty
    Public Property Tel() As String
      Get
        Return mTel
      End Get
      Set(ByVal value As String)
        mTel = value
      End Set
    End Property

    Dim mFax As String = String.Empty
    Public Property Fax() As String
      Get
        Return mFax
      End Get
      Set(ByVal value As String)
        mFax = value
      End Set
    End Property

    Dim mCel As String = String.Empty
    Public Property Cel() As String
      Get
        Return mCel
      End Get
      Set(ByVal value As String)
        mCel = value
      End Set
    End Property

Nulla di particolarmente complicato, vero? Facciamolo un'altra volta, allora.

Classe ClientiRiferimenti
Aggiungiamo una nuova classe di nome ClientiRiferimenti alla cartella Dettagli, specifichiamo il namespace e riportiamo la struttura della tabella Clienti_Riferimenti:

Namespace Dettagli

  ''' <summary>
  ''' Classe di dettaglio relativa alla tabella Clienti_Riferimenti
  ''' </summary>
  Public Class ClientiRiferimenti

    Dim mCodice As Integer
    Public Property Codice() As Integer
      Get
        Return mCodice
      End Get
      Set(ByVal value As Integer)
        mCodice = value
      End Set
    End Property

    Dim mRif As String = String.Empty
    Public Property Rif() As String
      Get
        Return mRif
      End Get
      Set(ByVal value As String)
        mRif = value
      End Set
    End Property

    Dim mTel As String = String.Empty
    Public Property Tel() As String
      Get
        Return mTel
      End Get
      Set(ByVal value As String)
        mTel = value
      End Set
    End Property

    Dim mFax As String = String.Empty
    Public Property Fax() As String
      Get
        Return mFax
      End Get
      Set(ByVal value As String)
        mFax = value
      End Set
    End Property

    Dim mCel As String = String.Empty
    Public Property Cel() As String
      Get
        Return mCel
      End Get
      Set(ByVal value As String)
        mCel = value
      End Set
    End Property

    Dim mMail As String = String.Empty
    Public Property Mail() As String
      Get
        Return mMail
      End Get
      Set(ByVal value As String)
        mMail = value
      End Set
    End Property

  End Class

End Namespace

A questo punto inizieremo a sviluppare le form accessorie, che permetteranno l'interazione con le due griglie, quando si renderanno necessari l'inserimento di una riga nuova o la modifica di una esistente.

La form ClientiSedi
In Esplora soluzioni clicchiamo con il tasto destro sul progetto PrimiPassi e aggiungiamo una nuova Krypton Form di nome FrmClientiSedi.
L'aspetto finale della form è visibile in figura:

Aperta la finestra del codice, definiamo le specifiche di importazione:

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

Public Class FrmClientiSedi

Faremo infatti uso di oggetti SqlAdapter, SqlCommand eccetera, dell'enumerato TipoOperazione, che è uno dei parametri per il costruttore, della classe ConvalidaDati, della classe ClientiSedi.
La classe usa due campi, entrambi ricevuti dal costruttore:

  Private mTipoOperaz As TipoOperazione
  Private mClientiSedi As ClientiSedi

Il costruttore di default viene reso privato, affinché si debba usare forzatamente quello parametrizzato:

  Private Sub New()
    InitializeComponent()
  End Sub

  Public Sub New(ByVal clientisedi As ClientiSedi, ByVal tipooperaz As TipoOperazione)
    InitializeComponent()

    mClientiSedi = clientisedi
    mTipoOperaz = tipooperaz

    PopolaCombo()
    CmbStato.SelectedIndex = -1

    'Inizializzo i campi di inserimento/modifica
    If TipoOperaz = TipoOperazione.Inserimento Then
      'lblCodice.Text = myCodice.ToString
      LblCodice.Text = mClientiSedi.Codice.ToString
    Else
      LblCodice.Text = mClientiSedi.Codice.ToString
      TxtNome.Text = mClientiSedi.RagSoc.ToString
      TxtInd.Text = mClientiSedi.Ind.ToString
      TxtCap.Text = mClientiSedi.Cap.ToString
      TxtCitta.Text = mClientiSedi.Citta.ToString
      TxtProv.Text = mClientiSedi.Prov.ToString
      CmbStato.Text = mClientiSedi.Stato.ToString
      TxtTel.Text = mClientiSedi.Tel.ToString
      TxtFax.Text = mClientiSedi.Fax.ToString
      TxtCel.Text = mClientiSedi.Cel.ToString

      'Disabilito i campi chiave e metto il colore del testo in rosso 
      'per fare capire che sono campi chiave e non possono essere modificati
      TxtNome.ReadOnly = True
      TxtNome.BackColor = Color.White
      TxtNome.ForeColor = Color.Red
      TxtNome.TabStop = False
      TxtCitta.ReadOnly = True
      TxtCitta.BackColor = Color.White
      TxtCitta.ForeColor = Color.Red
      TxtCitta.TabStop = False
    End If
  End Sub

Assegnati ai campi i valori passati al costruttore, si invoca il metodo PopolaCombo che popola la ComboBox degli Stati, quindi si inizializzano i controlli a seconda del tipo di operazione: se si tratta di un nuovo inserimento, si popola solo la LblCodice (infatti l'oggetto ClientiSedi passato contiene solo questo valore), che è invisible dato che non ha alcun significato per l'utente; viceversa si tratta di un'operazione di modifica, nel qual caso si riportano corrispettivamente tutti i valori presenti nella riga da modificare. Infine si bloccano le caselle con i valori che compongono la chiave univoca.

Anche questa form viene usata come form di dialogo, quindi ci si giova della proprietà KeyPreview posta a True per gestire l'evento KeyDown relativamente ai tasti Escape e Enter, onde simulare il comportamento di una form di dialogo, 'eseguendo' il click sui pulsanti BtnEsci e BtnConferma, mentre il pulsante BtnSvuota esegue il metodo SvuotaCampi.
Di tutto questo, poiché si tratta di codice già noto, si mostra solo il metodo BtnConferma_Click, perché ha importanti differenze:

  Private Sub BtnConferma_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnConferma.Click
    'Controllo validità dei campi di inserimento/modifica
    If DatiValidi() = False Then
      Exit Sub
    End If

    Dim RigaIns As New ClientiSedi

    'Aggiungo un nuovo elemento alle sedi durante un nuovo inserimento
    RigaIns.Codice = CInt(LblCodice.Text)
    RigaIns.RagSoc = TxtNome.Text.Trim
    RigaIns.Ind = TxtInd.Text.Trim
    RigaIns.Cap = TxtCap.Text.Trim
    RigaIns.Citta = TxtCitta.Text.Trim
    RigaIns.Prov = TxtProv.Text.Trim
    RigaIns.Stato = CmbStato.Text.Trim
    RigaIns.Tel = TxtTel.Text.Trim
    RigaIns.Fax = TxtFax.Text.Trim
    RigaIns.Cel = TxtCel.Text.Trim

    mCampiSedi.Add(RigaIns)

    If mTipoOperaz = TipoOperazione.Inserimento Then
      'Nuovo inserimento
      SvuotaCampi()
      TxtNome.Select()
    Else
      'Modifica
      Me.DialogResult = Windows.Forms.DialogResult.OK
    End If
  End Sub

Dopo aver controllato la validità dei dati immessi in Ragione Sociale, Indirizzo, Città (tramite il solito metodo DatiValidi), si istanzia un oggetto ClientiSedi, se ne popolano le proprietà e lo si aggiunge a una lista di ClientiSedi. Perché una lista? Lo vediamo nel codice successivo: se l'operazione è un nuovo inserimento, vengono svuotate le caselle e riposizionato il focus su RagioneSociale per permettere di inserire altri record, che vengono quindi aggiunti alla lista; se l'operazione è di sola modifica, si esce, e la lista contiene l'unico oggetto ClientiSedi modificato.

La form chiamante, FrmClienti, leggerà la lista di ClientiSedi attraverso la proprietà:

  Private mCampiSedi As New List(Of ClientiSedi)
  Public ReadOnly Property CampiSedi() As List(Of ClientiSedi)
    Get
      Return mCampiSedi
    End Get
  End Property

La form ClientiRif
L'altra form accessoria è molto simile, dal punto di vista logico, per cui non se ne dirà molto. L'aspetto finale della form è visibile in figura:

Nel codice, le differenze sono costituite da un campo:

  Private mClientiRif As ClientiRiferimenti

dalla proprietà che contiene i valori di ritorno:

  Private mCampiRif As New List(Of ClientiRiferimenti)
  Public ReadOnly Property CampiRif() As List(Of ClientiRiferimenti)
    Get
      Return mCampiRif
    End Get
  End Property

Il resto comporta solo le piccole intuibili differenze dovute alla diversità dei dati trattati.

Conclusione
In questo articolo abbiamo descritto l'implementazione delle prime due classi dati, cosiddette di dettaglio, nel nuovo namespace, e delle due form accessorie della prima form master-details, che sarà l'argomento della prossima puntata.

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.