Le avventure in VB.Net di un principiante ex-VB6 - 11
a cura di Oscar Zanin e Diego Cattaruzza (requisiti: Visual Basic Express e SqlServer )Premessa
Con questa puntata, dopo aver preparato tante classi accessorie - tra cui le due fondamentali per il Data Layer della nostra applicazione, Tabella e DatiMasterDetails - cominceremo a creare le form di gestione dei dati: inserimento, modifica, cancellazione e ricerca.Partiremo con la form relativa agli Stati, per la quale abbiamo già creato la voce di menu nella form Main. Contenendo un unico campo, rappresenta una buona scelta per rompere il ghiaccio. Si basa su un'unica tabella e quindi sfrutterà solo la classe Tabella.
Così, prenderemo dimestichezza con la relazione Form-Tabella, prima di cimentarsi con quella Form-DatiMasterDetails.
Form Stati
In Esplora soluzioni clicchiamo con il tasto destro sul progetto PrimiPassi, selezioniamo la voce Aggiungi e quindi Nuovo elemento. Nella finestra che appare, selezioniamo Krypton Form nella sezione Modelli personali, assegniamo come nome FrmStati e clicchiamo sul pulsante Aggiungi.
L'aspetto della form a fine progettazione è il seguente:![]()
Elenco dei controlli e relative proprietà:
- KryptonManager (già presente)
- GlobalPaletteMode = Office 2007 - Blue
- FrmStati
- Eliminiamo il KryptonPanel inserito automaticamente all'interno della form
- BackColor = (Web) LightBlue
- FormBorderStyle = FixedDialog
- Icon = Archivio.ico (dalla cartella Immagini)
- MaximizeBox = False
- Size = 516; 119
- StartPosition = WindowsDefaultLocation
- Text = Anagrafica Stati
- controllo ToolStrip
- Name = TbrStrumenti
- ImageScalingSize = 32; 32 (dimensione in pixel delle immagini che utilizzeremo)
- primo Button
- Name = BtnEsci
- Image = dai File di risorse del progetto selezioniamo l'immagine Pulsanti_Uscita
- Text = Esci
- ToolTipText si aggiorna da sola
- primo Separator
- secondo Button
- Name = BtnNuovo
- Image = dai File di risorse del progetto selezioniamo l'immagine Pulsanti_Nuovo
- Text = Nuovo record
- terzo Button
- Name = BtnModifica
- Image = dai File di risorse del progetto selezioniamo l'immagine Pulsanti_Modifica
- Text = Modifica record
- quarto Button
- Name = BtnCancella
- Image = dai File di risorse del progetto selezioniamo l'immagine Pulsanti_Cancella
- Text = Cancella record
- secondo Separator
- quinto Button
- Name = BtnCerca
- Image = dai File di risorse del progetto selezioniamo l'immagine Pulsanti_Cerca
- Text = Cerca record
- terzo Separator
- sesto Button
- Name = BtnConferma
- Image = dai File di risorse del progetto selezioniamo l'immagine Pulsanti_Conferma
- Text = Conferma operazione
- settimo Button
- Name = BtnAnnulla
- Image = dai File di risorse del progetto selezioniamo l'immagine Pulsanti_Annulla
- Text = Annulla operazione
- quarto Separator
- ottavo Button
- Name = BtnPrimo
- Image = dai File di risorse del progetto selezioniamo l'immagine Pulsanti_Primo
- Text = Sposta sul primo record
- nono Button
- Name = BtnPrec
- Image = dai File di risorse del progetto selezioniamo l'immagine Pulsanti_Precedente
- Text = Sposta sul record precedente
- decimo Button
- Name = BtnSucc
- Image = dai File di risorse del progetto selezioniamo l'immagine Pulsanti_Successivo
- Text = Sposta sul record successivo
- undicesimo Button
- Name = BtnUltimo
- Image = dai File di risorse del progetto selezioniamo l'immagine Pulsanti_Ultimo
- Text = Sposta sull'ultimo record
- quinto Separator
- dodicesimo Button
- Name = BtnAggiorna
- Image = dai File di risorse del progetto selezioniamo l'immagine Pulsanti_Aggiorna
- Text = Allinea i dati con il database
- tredicesimo ed ultimo Button
- Name = BtnSblocca
- Image = dai File di risorse del progetto selezioniamo l'immagine Pulsanti_Sblocca
- Text = Sblocca record
- Panel
- Name = PnlDati
- BackColor = (Web) LightBlue
- Dock in parent container (Fill)
- Label
- AutoSize = True
- Font Size = 10
- Text = Stato
- TextBox
- Name = TxtStato
- BackColor = (Web) White
- Font Size = 10
- ForeColor = Red (tutti i campi facenti parte della chiave univoca li evidenzio all'utente in rosso)
- MaxLength = 50 (massimo numero di caratteri digitabili dall'utente)
- Size = 220; 23
- Visualizziamo, se già non è visibile, la barra Layout, selezioniamo tenendo premuto Shift entrambi i controlli Label e TextBox e facciamo clic sui pulsanti Align Middles, Remove Horizontal Spacing, Increase Horizontal Spacing, Center Vertically. Clicchiamo sul pulsante Tab Order e assicuriamoci che in tale ordine ci sia prima la ToolStrip e poi la TextBox.
Il menu della form Main
Ora che abbiamo creato la form andiamo a scrivere il codice necessario a richiamarla, nella voce di menu Stati della form FrmMain. Entriamo in progettazione della FrmMain, visualizziamo la suddetta voce di menù e facciamoci sopra un doppio click. Ci apparirà la Sub StatiToolStripMenuItem_Click il cui codice è il seguente:Private Sub StatiToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles StatiToolStripMenuItem.Click Try Dim frm As FrmStati = DirectCast(FormHelper.VediSeIlFormEAperto( _ "FrmStati", Me, True, False), FrmStati) If frm Is Nothing Then frm = New FrmStati frm.MdiParent = Me frm.StartPosition = FormStartPosition.Manual frm.Location = New Point(0, 0) ' <omissis> frm.Show() Else frm.WindowState = FormWindowState.Normal frm.BringToFront() End If Catch ex As Exception Messaggi.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubDapprima si dichiara una FrmStati cercando di assegnarle il riferimento a una FrmStati già esistente, tramite il metodo VediSeIlFormEAperto, al quale passiamo come parametri: il nome della nostra form, la form chiamante (FrmMain, ovvero la nostra form MDI, quindi Me), se è MDI e se è posseduta da un'altra form (owned, non form figlia). Il metodo restituisce una form generica che va quindi tipizzata come FrmStati - DirectCast è una istruzione fondamentale, assieme a CType e a TryCast, delle quali vi consigliamo di studiare nella guida le differenze.
Attenzione: non sempre serve usare il metodo VediSeIlFormEAperto: possono esserci situazioni dove aprire più istanze della stessa form può essere necessario e sensato. Non in questo caso.Come si può intuire dalle righe successive, se la form non è aperta il metodo restituisce Nothing e allora ne creiamo una istanza, le assegniamo il riferimento al Parent, la posizioniamo e la apriamo, viceversa la portiamo alle sue dimensioni normali e in primo piano rispetto alle altre.
La riga commentata <omissis> è una riga che andrà aggiunta dopo qualche primo intervento sulla FrmStati.Prime implementazioni per la FrmStati: l'evento ActiveControlChanged
Innanzitutto imponiamo le direttive di importazione, quindi dichiariamo l'evento il cui scatenamento visualizza nella FrmMain e più precisamente nella sua barra di stato un breve messaggio esplicativo del controllo che ha il focus e delle operazioni che si possono compiere:Imports APP.UI Imports APP.Data Imports APP.Data.SqlHelper Imports System.Data.SqlClient Public Class FrmStati Public Event ActiveControlChanged(ByVal sender As Object, ByVal e As ActiveControlChangedEventArgs)Quindi, giusto per non dimenticarcene, scriviamo subito il codice in cui scateniamo questo evento. Per la TextBox gestiremo levento Enter, per i pulsanti gestiremo levento MouseEnter. Si potrebbe scrivere il codice per ciascun pulsante, a esempio (non copiatelo):
Private Sub btnAggiorna_EnabledChanged(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnEsci.EnabledChanged RaiseEvent ActiveControlChanged(Me, New ActiveControlChangedEventArgs("Info : " & _ "Permette di allineare i propri dati con quelli più aggiornati del database.")) End SubPer fare ciò, cerchiamo nella combo degli oggetti il nome del BtnEsci, nella combo degli eventi scegliamo levento MouseEnter, quindi scriviamo il codice. Per ciascuno dei pulsanti. In fondo lunica cosa che cambia è il messaggio di informazione, no? Allora possiamo scrivere una sola procedura per levento MouseEnter di tutti i pulsanti, discriminando il messaggio in base al nome del pulsante. Basta aggiungere eventi alla clausola Handles, e Intellisense ci aiuta a scrivere la maggior parte del codice (questo copiatelo, ovviamente senza gli allineamenti un po' disordinati che Diego usa per ragioni tipografiche):
Private Sub Pulsante_MouseEnter(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles BtnAggiorna.MouseEnter, BtnAnnulla.MouseEnter, _ BtnCancella.MouseEnter, BtnCerca.MouseEnter, _ BtnConferma.MouseEnter, BtnEsci.MouseEnter, BtnModifica.MouseEnter, _ BtnNuovo.MouseEnter, BtnPrec.MouseEnter, BtnPrimo.MouseEnter, _ BtnSblocca.MouseEnter, BtnSucc.MouseEnter, BtnUltimo.MouseEnter Dim m As String = "Info: " Select Case DirectCast(sender, ToolStripButton).Name Case "BtnAggiorna" : m &= _ "Permette di allineare i propri dati con quelli più aggiornati del database." Case "BtnAnnulla" : m &= "Permette di annullare l'operazione in corso." Case "BtnCancella" : m &= "Permette di cancellare l'anagrafica dello stato visualizzato." Case "BtnCerca" : m &= "Permette di cercare l'anagrafica di uno stato." Case "BtnConferma" : m &= "Permette di confermare l'operazione in corso." Case "BtnEsci" : m &= _ "Permette di uscire dalla videata di gestione delle anagrafiche degli stati." Case "BtnModifica" : m &= "Permette di modificare i dati dello stato visualizzato." Case "BtnNuovo" : m &= "Permette di inserire un nuovo stato." Case "BtnPrec" : m &= "Permette di spostarsi sul record precedente." Case "BtnPrimo" : m &= "Permette di spostarsi sul primo record. " & _ "Il più recente se i dati sono ordinati cronologicamente." Case "BtnSblocca" : m &= "Permette di sbloccare un record in stato di modifica. " & _ "DA USARE SOLO NEL CASO IN CUI LO STATO DI BLOCCO SIA RIMASTO " & _ "IMPOSTATO PER UN ERRORE IMPREVISTO" Case "BtnSucc" : m &= "Permette di spostarsi sul record successivo." Case "BtnUltimo" : m &= "Permette di spostarsi sull'ultimo record. " & _ "Il meno recente se i dati sono ordinati cronologicamente." End Select RaiseEvent ActiveControlChanged(Me, New ActiveControlChangedEventArgs(m)) End SubLa pigrizia ci viene in aiuto: se scriviamo 'Case Bt', Intellisense ci propone i nomi dei pulsanti: scegliamo quello che ci serve, poi scriviamo le virgolette prima e dopo. Così il codice si scrive quasi da solo e velocemente.
Naturalmente, se gestiamo l'evento MouseEnter, è opportuno gestire l'evento MouseLeave:Private Sub Pulsante_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles BtnAggiorna.MouseLeave, BtnAnnulla.MouseLeave, _ BtnCancella.MouseLeave, BtnCerca.MouseLeave, BtnConferma.MouseLeave, _ BtnEsci.MouseLeave, BtnModifica.MouseLeave, BtnNuovo.MouseLeave, _ BtnPrec.MouseLeave, BtnPrimo.MouseLeave, BtnSblocca.MouseLeave, _ BtnSucc.MouseLeave, BtnUltimo.MouseLeave RaiseEvent ActiveControlChanged(Me, New ActiveControlChangedEventArgs(" ")) End SubAggiungiamo anche il codice per la TextBox:
Private Sub TxtStato_Enter(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles TxtStato.Enter RaiseEvent ActiveControlChanged(Me, New ActiveControlChangedEventArgs("Info : " & _ "Nome dello stato. Digitazione obbligatoria. Campo chiave.")) End SubAdesso possiamo aggiungere, nel codice della FrmMain:StatiToolStripMenuItem_Click, listruzione che assegna il gestore di evento, al posto della <omissis>:
AddHandler frm.ActiveControlChanged, AddressOf FormFiglia_ActiveControlChangedSe non vi ricordate questo metodo FormFiglia_ToolStripInfoChanged della FrmMain, che scrive nella StatusStrip della FrmMain un messaggio di aiuto all'utente nel momento in cui un controllo della form figlia ottiene il focus, potreste fare un ripasso.
E' con questo sistema che Oscar gestisce l'help dei suoi programmi. Se voi lo gestite diversamente tutto il codice relativo all'evento ActiveControlChanged (scatenamento e gestione), non sono più necessari.I campi
La prima variabile privata, nel codice originale di Oscar, è così:'Può assumere i valori 0 nessuna operazione in corso, 1 nuovo inserimento, 2 modifica Private Operazione As ByteOperazione, come vedete dal commento, indica l'operazione in corso da parte dell'utente e può assumere i valori: 0 nessuna operazione in corso, 1 nuovo inserimento o 2 modifica. A seconda del valore assunto vengono svolte delle porzioni di codice piuttosto che altre.
[Diego] Invece è meglio non usare solo il commento e la propria memoria: apriamo il file ConvalidaDati (scelto perché ha uno scopo vagamente affine), aggiungiamo, in fondo, un enumerato apposito:
Public Enum TipoOperazione Nessuna Inserimento Modifica End EnumAdesso possiamo cambiare la dichiarazione di Oscar in quella di Diego (e vedremo gli altri campi):
Private mOperazione As TipoOperazione Private Dati As Tabella Private mPulisciControlli As SvuotaControlliIn tal modo, avremo codice più leggibile senza doverci ricordare il significato delle costanti numeriche utilizzate (0, 1 e 2).
Dati è un'istanza della classe Tabella analizzata in precedenza. Data la speciale importanza, questa variabile ha il nome che inizia con una maiuscola, senza la m iniziale, come i nomi degli altri campi. Ora vedremo come viene sfruttata. Ricordo che Tabella serve per la gestione delle tabelle singole, non per i rapporti master/details.
L'ultima variabile, mPulisciControlli, è un'istanza della classe SvuotaControlli contenuta nel progetto UI. Ricordo che la classe permette di svuotare tutti i controlli della form.I metodi
Private Sub FrmStati_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles MyBase.Load mOperazione = TipoOperazione.Nessuna mPulisciControlli = New SvuotaControlli Dati = New Tabella(ConnessioneDatabase, "Stati") Dati.NomeCampoModifica = "Modifica" Dati.Ordinamento = "Stato" Dati.CaricaDati() VisualizzaDati() End SubAll'apertura della form svolgo le seguenti operazioni di inizializzazione:
- valorizzo la variabile mOperazione, creo un'istanza della classe di svuotamento dei controlli SvuotaControlli, creo una nuova istanza di Tabella passandole come parametri la connessione con il database e il nome della tabella del database da gestire (ricordo che ConnessioneDatabase è proprietà della classe SqlHelper)
- valorizzo la proprietà relativa al nome del campo indicante lo stato di modifica di un record per la tabella in questione (in questo caso Stati)
- valorizzo la proprietà relativa alla stringa di ordinamento dei dati. L'ordine con il quale i dati ci saranno proposti a video
- richiamo il metodo ci caricamento dei dati. Carica tutti i record della tabella
- infine richiamo il metodo VisualizzaDati che analizzeremo fra poco. Verranno visualizzati i dati del primo record della tabella (rispettando il tipo di ordinamento che abbiamo appena impostato) oppure nessun dato nel caso la tabella non contenga record.Private Sub VisualizzaDati() SvuotaCampi() Dim riga As DataRow = Nothing If Not Dati.RigaCorrente(riga) Then 'Nessun record presente nella tabella PreparaVuoto() Else 'Inserisco i valori nei controlli TxtStato.Text = CStr(riga("Stato")) End If End SubQuesto metodo ha il compito di visualizzare i dati del record corrente. Per prima cosa svuota il contenuto dei controlli della form, tramite il metodo SvuotaCampi (nel seguito). Successivamente, grazie al metodo RigaCorrente di Dati (oggetto Tabella) al quale passiamo come argomento una DataRow vuota, ci facciamo restituire la stessa DataRow compilata con i dati del record corrente. Se la tabella è vuota richiameremo il metodo PreparaVuoto (nel seguito), altrimenti valorizzeremo ogni singolo controllo con il relativo valore contenuto nella DataRow (in FrmStati abbiamo un unico controllo da trattare).
Private Sub SvuotaCampi() mPulisciControlli.PulisciTextBox(Me) End SubRichiamando il metodo PulisciTextBox della classe SvuotaControlli di cui PulisciControlli è un'istanza, svuoto l'unica TextBox presente sul form.
Private Sub PreparaVuoto() BtnEsci.Enabled = True BtnNuovo.Enabled = True BtnModifica.Enabled = False BtnCancella.Enabled = False BtnCerca.Enabled = False BtnPrimo.Enabled = False BtnPrec.Enabled = False BtnSucc.Enabled = False BtnUltimo.Enabled = False BtnAggiorna.Enabled = False BtnConferma.Enabled = False BtnAnnulla.Enabled = False SvuotaCampi() TbrStrumenti.Select() End SubSe la tabella in oggetto, in questo caso Stati, non contiene record, viene richiamato questo metodo che abilita solo i pulsanti di uscita e di inserimento nuovo record, disabilitando tutto il resto. Se non vi sono record nella tabella, infatti, all'utente deve essere permesso solo inserire un nuovo record oppure uscire dalla form.
Vi sono altre situazioni in cui è necessario abilitare o disabilitare pulsanti della barra a seconda della loro funzione. Per questo sono stati implementati due metodi, uno antitetico allaltro:
Private Sub BloccaBarra() AbilitaPulsanti(False) End Sub Private Sub SbloccaBarra() AbilitaPulsanti(True) End Sub Private Sub AbilitaPulsanti(ByVal flag As Boolean) btnEsci.Enabled = flag btnNuovo.Enabled = flag btnModifica.Enabled = flag btnCancella.Enabled = flag btnCerca.Enabled = flag btnPrimo.Enabled = flag btnPrec.Enabled = flag btnSucc.Enabled = flag btnUltimo.Enabled = flag btnAggiorna.Enabled = flag btnConferma.Enabled = Not flag btnAnnulla.Enabled = Not flag PnlDati.Tag = Not Convert.ToBoolean(PnlDati.Tag) End SubQuando la barra è bloccata, sono abilitati solo i pulsanti conferma e annulla, e i controlli sono compilabili. Il contrario accade quando la barra è sbloccata.
Il blocco della compilazione dei controlli viene regolata gestendo l'evento Enter del Panel:Private Sub pnlDati_Enter(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles PnlDati.Enter If Convert.ToBoolean(PnlDati.Tag) = False Then TbrStrumenti.Select() End If End SubQuesto codice è frutto di un suggerimento che mi diede Diego a suo tempo: in VB6 ero abituato a mettere tutti i controlli compilabili dall'utente, esclusi barra dei pulsanti e griglie dati di dettaglio, in un contenitore nascosto; questo mi serviva per evitare di dover abilitare o disabilitare i controlli singolarmente a seconda che l'utente intraprendesse un operazione a video (inserimento o modifica) oppure la concludesse.
Sfruttando la proprietà Tag di un controllo Panel dentro al quale disegno tutti i controlli, anche qui esclusi barra dei pulsanti e griglie dati di dettaglio, ottengo lo stesso risultato: se l'utente ha in corso un'operazione di inserimento o modifica (Tag a vero) permetto di compilare i controlli, viceversa (Tag a falso) quando l'utente entra nel controllo Panel rimando il focus alla barra dei pulsanti.
Tanto semplice quanto efficace.Anche la chiusura della form è regolata:
Private Sub FrmStati_FormClosing(ByVal sender As System.Object, _ ByVal e As System.Windows.Forms.FormClosingEventArgs) _ Handles MyBase.FormClosing If mOperazione <> TipoOperazione.Nessuna Then If mOperazione = 1 Then Messaggi.Avviso("Prima di chiudere salvare o annullare il nuovo inserimento in corso !", _ "Attenzione") Else Messaggi.Avviso("Prima di chiudere salvare o annullare la modifica in corso !", "Attenzione") End If e.Cancel = True End If End SubSe l'utente dovesse tentare di chiudere la form mentre c'è un operazione in corso, lo impediremmo avvisandolo con opportuni messaggi e impostando a True la proprietà Cancel dell'argomento di evento (vedi guida).
I pulsanti
Private Sub BtnEsci_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles BtnEsci.Click Me.Close() End Sub Private Sub BtnNuovo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles BtnNuovo.Click 'Mi salvo la chiave del record visualizzato per poter rivisualizzarlo 'se viene cliccato il pulsante annulla Dati.LeggiRecord() BloccaBarra() SvuotaCampi() TxtStato.Select() mOperazione = TipoOperazione.Inserimento End SubTralasciando la spiegazione del codice di BtnEsci_Click, ecco cosa fa BtnNuovo_Click: come indicato nel commento, sfrutto il metodo LeggiRecord della classe Tabella per salvare i valori dei campi chiave del record corrente (grazie al metodo RiempiValoriColonneChiave, sempre di Tabella, da esso invocato) in modo che, se l'utente decidesse di annullare l'operazione avremmo gli estremi per ripristinare la visualizzazione del record corrente; blocco la barra, svuoto tutti i campi compilabili dall'utente, do il focus alla casella di testo, imposto l'operazione 'inserimento nuovo record.
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.Modificabile Then 'Errore di concorrenza Exit Sub End If 'Lo imposto allo stato di modifica If Not Dati.ImpostaModificaRecord Then 'Errore di concorrenza Exit Sub End If VisualizzaDati() BloccaBarra() TbrStrumenti.Select() TxtStato.ReadOnly = True mOperazione = TipoOperazione.Modifica End SubAl click sul pulsante di modifica da parte dell'utente, sfruttando il metodo Modificabile della classe Tabella, controllo che il record sia modificabile. Se così non fosse, attraverso il metodo stesso, avviso l'utente e termino l'operazione.
Viceversa, con il metodo ImpostaModifcaRecord (sempre della classe Tabella), imposto a vero lo stato del campo di modifica della tabella Stati e di fatto impedisco ad altri utenti di apportare modifiche.
Come avrete iniziato a capire, il lavoro fatto sulle due classi Data Layer è fondamentale. Tanto, tanto codice è centralizzato e demandato a queste due classi. Nelle form mi limito a richiamare i metodi ed eventualmente passare parametri. Quanto lavoro di sviluppo risparmiato... ;o)
[Oscar] Anche in questo caso, dopo aver visulizzato i dati, blocco la barra, le do il focus e imposto a sola lettura il campo chiave Stato, oltre a settare l'operazione in corso a modifica record. Si, lo avrete già intuito: è un'operazione inutile: entrare in modifica per poi non potere modificare alcunché è un nonsenso.
La logica che adotto è quella di non permettere la modifica di campi chiave della tabella sottesa: se di un record si devono modificare i campi che lo identificano, a mio avviso quel record non ha più senso di esistere: lo si elimina e se ne inserisce uno nuovo.
Caso vuole che la tabella Stati sia composta da un solo campo, Stato per l'appunto, che è anche campo chiave.
Mi ritrovo quindi a dare ragione a Diego quando, nella puntata che illustrava la creazione del database, diceva che questa ed altre tabelle necessitavano di un campo identificativo.
A questo punto, perchè non eliminare il pulsante di modifica? Non lo faccio per una questione di uniformità fra le form. Al limite lo si potrebbe sempre lasciare disabilitato. A voi la scelta.[Diego] Per il momento, infatti, ci limitiamo a disabilitare il pulsante e a commentare il metodo sopra illustrato, e lasciato per ragioni didattiche (anche illustrare gli errori è istruttivo, a volte: in questo caso, Oscar è stato costretto a un accrocchio che avrebbe evitato con una maggior cura nello strutturare il database). Quindi correggiamo:
BtnModifica.Enabled = Falsein AbilitaPulsanti.
Private Sub BtnCancella_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles BtnCancella.Click Try Dim risposta As DialogResult = Messaggi.SiNo( _ "Sei sicuro/a di volere eliminare il record visualizzato?", _ "Elimina record") If risposta = Windows.Forms.DialogResult.No Then Exit Sub Dati.LeggiRecord() If Not Dati.CancellaRiga(True) Then Exit Sub 'Errore di concorrenza BtnPrimo.PerformClick() Catch ex As Exception 'Errori non gestiti Messaggi.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubCome prima cosa verifico che l'utente voglia veramente eliminare il record. Poi, con il metodo LeggiRecord della classe Tabella salvo i valori dei campi chiave del record corrente, in modo da ripristinarlo caso mai qualcosa non dovesse andare a buon fine. Invoco quindi il metodo CancellaRiga della classe Tabella passandole come parametro il valore vero che sta ad indicare al metodo stesso che si tratta dell'eliminazione di un record di una tabella non di dettaglio. Il metodo restituisce un valore booleano a seconda della riuscita (true) o meno (false) della cancellazione fisica del record dal database.
Se l'operazione è riuscita, emulo la pressione del pulsante che visualizza i dati del primo record della tabella.La gestione del pulsante BtnCerca richiede la creazione della form di dialogo per la ricerca, che verrà illustrata e discussa nella prossima puntata (ci sono, per il momento, diverse cose che Diego vuole modificare).
Prima di illustrare il codice del pulsante di conferma dell'operazione in corso, devo spiegare il codice di alcuni metodi in esso richiamati.
Private Function DatiValidi() As Boolean If ConvalidaDati.Convalida(TxtStato.Text, "Stato",ConvalidaDati.TipoConv.Testo,True) = False Then Messaggi.Avviso("Il campo Stato non può essere lasciato vuoto !", "Attenzione") Return False End If Return True End FunctionCon questo metodo verifico la correttezza dei dati immessi dall'utente: obbligatorietà di compilazione, tipologia corretta (per esempio numeri in campi numerici), ecc. Se il controllo non va a buon fine il metodo restituisce il valore False o True.
Per la verifica viene sfruttato il metodo Convalida della classe ConvalidaDati del progetto Data, che richiede come parametri: il testo contenuto nella TextBox, il nome del campo o l'etichetta che apparirà negli eventuali messaggi di errore costruiti per l'occasione, il tipo di controllo che deve essere effettuato, l'eventuale obbligatorietà del campo.Private Sub CaricaRiga() Dim riga As DataRow = Nothing If mOperazione = TipoOperazione.Inserimento Then riga = Dati.RigaNuova Else Dati.RigaCorrente(riga) End If riga("Stato") = txtStato.Text.Trim riga("Modifica") = False If mOperazione = TipoOperazione.Inserimento Then If Not Dati.AggiungiRigaNuova(riga) Then 'Errore di concorrenza Exit Sub Else Dati.LeggiRecord(Dati.DataSet.Tables(0).Rows.Count - 1) Dati.TrovaRecord() End If End If End SubDapprima si invoca, a seconda del tipo di operazione, il metodo RigaNuova, oppure il metodo RigaCorrente (della classe Tabella). Entrambi influiscono sulla DataRow riga, della quale si valorizzano i campi. Quindi, se l'operazione è un inserimento, si invoca il metodo AggiungiRigaNuova: se non ha successo l'utente è stato avvisato e si esce dalla procedura; altrimenti si legge l'ultimo record immesso e ci si posiziona su di esso.
Private Sub btnConferma_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles BtnConferma.Click Try If Not DatiValidi() Then Exit Sub End If CaricaRiga() Dati.AggiornaDataBase() TxtStato.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() Catch ex As SqlException Select Case ex.ErrorCode Case -2146232060 Messaggi.Errore(ex.Message, "Attenzione") mOperazione = TipoOperazione.Nessuna Case Else Messaggi.Errore(ex.Errors(0).Message, "Attenzione") End Select Catch ex As Exception 'Errori non gestiti Messaggi.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubControllo la validità dei dati, carico la riga (nuova o corrente), allineo DataSet e database, NON reimposto la casella di testo come compilabile perchè è capo chiave, ricarico i dati nel DataSet, cerco di posizionarmi sul record che ho tentato di salvare; se ci riesco visualizzo i dati, oppure avviso l'utente; reimposto l'operazione corrente (a nessuna operazione), sblocco la barra e vi sposto il focus.
Private Sub btnAnnulla_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles BtnAnnulla.Click 'Controllo se si stava inserendo il primo record in assoluto If Dati.NumeroRighe = 0 Then mOperazione = TipoOperazione.Nessuna PreparaVuoto() TxtStato.ReadOnly = False Exit Sub End If Try 'Tolgo il blocco del record impostato mettendo il campo di modifica della tabella a falso Dati.ImpostaColonnaModifica(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 'Errori non gestiti Messaggi.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) Exit Sub End Try mOperazione = TipoOperazione.Nessuna SbloccaBarra() TbrStrumenti.Select() TxtStato.ReadOnly = False End SubPer prima cosa gestisco un caso limite, ovvero l'annullamento dell'inserimento del primo record in assoluto nella tabella degli Stati. Sfruttando il medoto NumeroRighe della classe Tabella mi faccio restituire il numero di record presenti. Se il numero è pari a zero, riporto la variabile dello stato delle operazioni in corso a nessuna operazione, richiamo il metodo PreparaVuoto già spiegato in precedenza e riporto al suo stato iniziale la TextBox (non è mai cambiato, in realtà, perché non è compilabile, essendo un campo chiave, ma è una spiegazione di quello che si fa con i controlli compilabili).
Se la tabella non era vuota riportiamo a falso il valore del campo relativo allo stato di modifica della tabella Stati attraverso il metodo ImpostaColonnaModifica della classe Tabella. Le operazioni successive sono identiche a quelle descritte per la conferma.Private Sub btnPrimo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles BtnPrimo.Click Dati.MoveFirst() VisualizzaDati() End SubCon i quattro pulsanti di spostamento fra i record non facciamo altro che richiamare i relativi metodi della classe Tabella e visualizzare i dati del nuovo record attuale. Se ne mostra solo uno come esempio.
Private Sub btnAggiorna_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles BtnAggiorna.Click 'Mi salvo la chiave del record visualizzato in modo da potere tornare a visualizzare quel record Dati.LeggiRecord() Dati.CaricaDati() If Not Dati.TrovaRecord() Then Messaggi.Avviso("Errore di posizionamento sul record precedentemente visualizzato !", _ "Annulla operazione") Else VisualizzaDati() End If End SubLo scopo del pulsante di aggiornamento è quello di riallineare il DataSet con il database attraverso il metodo CaricaDati e di riposizionarsi, visualizzandone i dati aggiornati, sul record che si aveva a video, attraverso i metodi LeggiRecord (che si salva i valori dei campi chiave), TrovaRecord (che sposta l'indice dei record su quello corrispondente ai valori dei campi chiave salvati) e VisualizzaDati.
Private Sub btnSblocca_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles BtnSblocca.Click 'Tolgo il blocco del record impostato mettendo il campo di modifica della tabella a vero Dati.ImpostaColonnaModifica(False) End SubQuesto pulsante dovrebbe essere usato dall'utente solamente in casi estremi. Sfruttando il metodo ImpostaColonnaModifica della classe Tabella, riportiamo a falso il valore del campo relativo allo stato di modifica della tabella Stati.
L'intento è quello di dare una via di uscita all'utente in casi tipo... "stavo effettuando una modifica al record, è andata via la corrente ed il record mi è rimasto sullo stato di modifica, pertanto non riesco più ad entrare a modificarlo"... oppure... "ecco, siamo alle solite, il collega dell'altro ufficio ha iniziato la modifica del record e poi è andato a prendersi un caffè, lasciandomi il record bloccato". :o)Oscar ci tiene a segnalare che, nelle form più complesse di questa, è presente anche:
#Region "Metodi di convalida" 'QUI SI CONTROLLA LA CONGRUITA' DEI DATI INSERITI NEI CAMPI ATTRAVERSO IL VALIDATING. 'NON SI FA IL CONTROLLO DELL'OBBLIGATORIETA' DEI CAMPI #End RegionAnche se si tratta solo di un commento vale la pena soffermarsi un attimo. Se vi ricordate, in precedenza, ho descritto il metodo ControlloDati che verifica la correttezza dei dati immessi dall'utente: obbligatorietà di compilazione, tipologia corretta (per esempio numeri in campi numerici), ecc.
Al posto del commento sopra indicato, invece, andranno scritti tutti quei metodi che svolgono operazioni particolari, come per esempio, all'abbandono di una TextBox, il calcolo automatico e la relativa valorizzazione di un'altra.Conclusione
In questa puntata è stata illustrata la FrmStati, la prima delle form che sfruttano una delle classi Data Layer argomento delle scorse puntate. Abbiamo visto anche qualche facilitazione offertaci dall'IDE di Visual Studio e spiegato qualche utile stratagemma di programmazione.Manca un metodo, quello per la ricerca, perché implica la predisposizione di una apposita form.
Di questo parleremo nella 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.