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 NamespaceSe 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 PropertyNulla 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 NamespaceA 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:
![]()
- KryptonManager, già presente
- GlobalPaletteMode = Office 2007 - Blue
- KryptonPanel, già presente
- eliminato
- FrmClientiSedi
- BackColor = (Web) LightBlue
- ControlBox = False
- FormBorderStyle = FixedSingle
- KeyPreview = True
- MaximizeBox = False
- Size = 483; 264
- StartPosition = CenterScreen
- Text = Inserimento/Modifica sedi clienti
- Trasciniamo un controllo ToolStrip
- Name = TbrStrumenti
- ImageScalingSize = 32; 32 (dimensione in pixel delle immagini che utilizzeremo)
a esso aggiungiamo nell'ordine:
- un Button
- Name = BtnEsci
- Image = Pulsanti_Uscita dalle risorse del progetto
- Text = Esci
- un Separator
- un Button
- Name = BtnSvuota
- Image = Pulsanti_Svuota2 dalle risorse del progetto
- Text = Svuota criteri e griglia
- un Separator
- un Button
- Name = BtnConferma
- Image = Pulsanti_InserisciRiga dalle risorse del progetto
- Text = Conferma
- sulla form trasciniamo
- una Label
- AutoSize = True
- Font Size = 10
- ForeColor = Black
copiamola, incolliamola 8 volte, così non abbiamo da impostare queste tre proprietà comuni,
ma solo testo e posizione (sfruttando anche le facilitazioni offerte dall'IDE per allineare i controlli):
Text LocationRagione Sociale 4; 55Indirizzo 4; 84Città 4; 113CAP 4; 142Pr. 168; 142Stato 227; 142Telefono 4; 171Fax 283; 171Cellulare 4; 200- un'altra Label
- BackColor = White
- BorderStyle = Fixed3D
- Font Size = 10
- Location = 319; 197
- Name = LblCodice
- Size = 70; 23
- Visible = False
- una TextBox
- BackColor = (Web) White
- Font Size = 10
copiamola, incolliamola 7 volte, disponiamo tutte le TextBox accanto alle Label (tranne quella con testo 'Stato'); impostiamo quindi le proprietà specifiche di ciascuna TextBox:
Name Location MaxLength SizeTxtNome 121; 52 50 347; 23TxtInd 121; 81 50 347; 23TxtCitta 121; 110 50 347; 23TxtCap 121; 139 5 41; 23TxtProv 196; 139 4 25; 23TxtTel 86; 258 25 149; 23TxtFax 319; 168 25 149; 23TxtCel 121; 168 25 149; 23
- una ComboBox
- Name = CmbStato
- AutoCompleteMode = SuggestAppend
- AutoCompleteSource = ListItems
- Font Size = 10
- Location = 273; 139
- Size = 195; 24
- Infine assegniamo l'ordine di tabulazione: selezioniamo il menu Visualizza/Ordine di tabulazione. Clicchiamo sui controlli nell'ordine seguente: ToolStrip, TxtNome, TxtInd, TxtCitta, TxtCap, TxtProv, CmbStato, TxtTel, TxtFax, TxtCel e infine premiamo il tasto Esc sulla tastiera per uscire dall'operazione con le modifiche impostate.
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 FrmClientiSediFaremo 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 ClientiSediIl 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 SubAssegnati 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 SubDopo 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 PropertyLa 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:![]()
- KryptonManager, già presente
- GlobalPaletteMode = Office 2007 - Blue
- KryptonPanel, già presente
- eliminato
- FrmClientiRif
- BackColor = (Web) LightBlue
- ControlBox = False
- FormBorderStyle = FixedSingle
- KeyPreview = True
- MaximizeBox = False
- Size = 452; 206
- StartPosition = CenterScreen
- Text = Inserimento/Modifica riferimenti clienti
- Trasciniamo un controllo ToolStrip
- Name = TbrStrumenti
- ImageScalingSize = 32; 32 (dimensione in pixel delle immagini che utilizzeremo)
a esso aggiungiamo nell'ordine:
- un Button
- Name = BtnEsci
- Image = Pulsanti_Uscita dalle risorse del progetto
- Text = Esci
- un Separator
- un Button
- Name = BtnSvuota
- Image = Pulsanti_Svuota2 dalle risorse del progetto
- Text = Svuota criteri e griglia
- un Separator
- un Button
- Name = BtnConferma
- Image = Pulsanti_InserisciRiga dalle risorse del progetto
- Text = Conferma
- sulla form trasciniamo
- una Label
- AutoSize = True
- Font Size = 10
- ForeColor = Black
copiamola, incolliamola 4 volte, così non abbiamo da impostare queste tre proprietà comuni,
ma solo testo e posizione (sfruttando anche le facilitazioni offerte dall'IDE per allineare i controlli):
Text LocationRiferimento 4; 55Telefono 4; 84Fax 252; 84Cellulare 4; 113 4; 142- un'altra Label
- BackColor = White
- BorderStyle = Fixed3D
- Font Size = 10
- Location = 288; 110
- Name = LblCodice
- Size = 70; 23
- Visible = False
- una TextBox
- BackColor = (Web) White
- Font Size = 10
copiamola, incolliamola 4 volte, disponiamo tutte le TextBox accanto alle Label; impostiamo quindi le proprietà specifiche di ciascuna TextBox:
Name Location MaxLength SizeTxtRif 90; 52 50 347; 23TxtTel 90; 81 25 149; 23TxtFax 288; 81 25 149; 23TxtCel 90; 110 25 149; 23TxtMail 90; 139 50 347; 23
- Infine assegniamo l'ordine di tabulazione: selezioniamo il menu Visualizza/Ordine di tabulazione. Clicchiamo sui controlli nell'ordine seguente: ToolStrip, TxtRif, TxtTel, TxtFax, TxtCel, TxtMail e infine premiamo il tasto Esc sulla tastiera per uscire dall'operazione con le modifiche impostate.
Nel codice, le differenze sono costituite da un campo:
Private mClientiRif As ClientiRiferimentidalla 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 PropertyIl 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.