Le avventure in VB.Net di un principiante ex-VB6 - 7
a cura di Oscar Zanin e Diego Cattaruzza (requisiti: Visual Basic Express e SqlServer Express )Premessa
In seguito a quanto esposto da Diego nella puntata precedente, ho apportato alcune modifiche per allinearmi alle sue indicazioni.
La prima parte di questo articolo illustra queste modifiche, la seconda parte tratterà il disegno della form principale del nostro progetto, la FrmMain.Classe Program
La classe "di accesso" all'applicativo si arricchisce di un metodo e di altro codice con l'obiettivo di controllare subito se il programma può avviarsi o meno.Si deve controllare l'esistenza del file crittato dei parametri di connessione, quindi la validità di questi ultimi. Se tali controlli hanno esito positivo, viene avviata la FrmMain, altrimenti viene proposta la FrmParametriConnessione, che deve restituire un valore di tipo Dialogresult in base al quale il programma procede con la FrmMain oppure si conclude. Il tutto con le dovute informazioni all'utente.
Poiché si ha bisogno di metodi e costanti posti in progetti satelliti, bisogna inserire direttive Imports:
Imports System.Data.SqlClient Imports APP.Data.SqlHelper Imports APP.Base.Crittografia Imports APP.UINell'ordine, permettono di istanziare la connessione, di testarla, di decrittare i parametri, di esporre messaggi all'utente.
Nel metodo Main, l'ultima riga Application.Run(New FrmMain) viene sostituita da:
If Not TestParametriConnessione() Then Dim dlg As New FrmParametriConnessione("Program") If dlg.ShowDialog = DialogResult.OK Then Application.Run(New FrmMain) End If Else Application.Run(New FrmMain) End IfSi chiama il metodo TestParametriConnessione, che controlla esistenza e validità dei parametri di connessione. Se il controllo ha esito negativo, si istanzia la FrmParametriConnessione passando il nome del chiamante.
Questa novità è resa necessaria dal fatto che la FrmParametriConnessione deve restituire un DialogResult diverso a seconda del chiamante (infatti può essere chiamata anche da un menu della FrmMain).
Quando il controllo torna a Program, l'applicazione procede solo in caso di DialogResultOk, altrimenti si conclude.Nel metodo TestParametriConnessione vengono definite alcune costanti per la stringa di connessione e per i messaggi:
Private Shared Function TestParametriConnessione() As Boolean Try Const FMT_ConnectionString As String = "Data Source={0}\SQLEXPRESS;Initial Catalog=" & _ FrmParametriConnessione.CATALOG & _ ";Persist Security Info=True;User ID={1};Password={2}" Const WAR_FileParametriMancante As String = _ "Il file dei parametri di connessione al database non esiste ancora. " & _ "Verrà ora aperta la form di salvataggio degli stessi." Const WAR_ParametriNonCorretti As String = _ I parametri di connessione al database sono mancanti o non corretti. " & _ "Verrà ora aperta la form di salvataggio degli stessi."Quindi si verifica l'esistenza del file crittato:
Dim serverName As String = DeserializeConnString("Server") If serverName = "File non creato" Then Messaggi.Avviso(WAR_FileParametriMancante, "Test Connessione") Return False End IfSe non esiste, si avvisa l'utente e viene restituito il fallimento del controllo. Altrimenti si procede con la lettura degli altri parametri e con il test della connessione:
Dim userID As String = DeserializeConnString("UserID") Dim password As String = DeserializeConnString("Password") If TestConnection(String.Format(FMT_ConnectionString, serverName, userID, password)) ThenSe il test ha esito positivo, viene istanziata la connessione e viene restituito il successo del controllo, altrimenti si avvisa l'utente e viene restituito il fallimento.
ConnessioneDatabase = New SqlConnection(String.Format(FMT_ConnectionString, serverName, _ userID, password)) Return True Else Messaggi.Avviso(WAR_ParametriNonCorretti, "Test Connessione") Return False End IfForm FrmParametriConnessione
Le modifiche apportate a questa form hanno l'intento di eliminare l'errore rilevato da Diego (la chiusura dell'applicazione da una form secondaria).
La form deve restituire un appropriato DialogResult al chiamante, in modo che questo possa adottare i comportamenti del caso.
Benché il metodo ShowDialog abbia un overload con un parametro owner, questo è di tipo IWin32Window, quindi non si può passare Program.
E' stato quindi aggiunto un costruttore che richiede come argomento il nome della classe chiamante, con il quale viene valorizzato un apposito campo:Public Sub New(ByVal caller As String) InitializeComponent() mCaller = caller End SubLa varietà delle possibili situazioni che possono scaturire durante e alla fine dell'esecuzione della form hanno imposto l'aggiunta di nuove costanti e nuovi campi.
Le costanti sono state denominate a seconda del chiamante e del luogo in cui vengono usate (la lettura dei loro valori permette di rendersi conto delle situazioni in cui vengono usate):Public Const CATALOG As String = "APP-P" Private Const FMT_ConnectionString As String = "Data Source={0}\SQLEXPRESS;Initial Catalog=" & _ CATALOG & _ ";Persist Security Info=True;User ID={1};Password={2}" Private Const FMP_ConnectionKo As String = "Impossibile connettersi al database {0} su server {1}" Private Const FMP_ConnectionKo_Salva As String = _ "Test di connessione al database {0} su server {1} fallita." Private Const FMP_ConnectionOk As String = "Connessione a database {0} su server {1} OK" Private Const WAR_DatiModificati As String = _ "I dati sono stati modificati, si desidera salvare la " & _ "configurazione prima di testare la connessione ?" Private Const WAR_Chiusura_App_Program As String = _ "I parametri di accesso al database non sono stati" & _ " indicati pertanto l'applicazione verrà terminata." Private Const WAR_TestKo_Program As String = "I parametri indicati non hanno superato " & _ "il test di connessione al database. " & _ "Si desidera modificare i parametri? " & _ "Rispondendo di no l'applicazione verrà terminata." Private Const WAR_TestKo_Main As String = "I parametri indicati non hanno superato " & _ "il test di connessione al database. " & _ "Si desidera modificare i parametri? " & _ "Rispondendo di no verranno mantenuti i parametri iniziali." Private Const WAR_SaveOnExit_Program As String = "Ci sono state modifiche ai dati, " & _ "si desidera salvarle prima di uscire ? " & _ "Rispondendo di no l'applicazione verrà terminata." Private Const WAR_SaveOnExit_Main As String = "Ci sono state modifiche ai dati, " & _ "si desidera salvarle prima di uscire ? " & _ "Rispondendo di no verranno mantenuti i parametri iniziali." Private Const WAR_Salva As String = "Salvo i parametri ?" Private Const WAR_Salvato As String = "Parametri salvati con successo."Sono stati aggiunti cinque nuovi campi. Il campo relativo al chiamante, tre per mantenere i valori iniziali dei parametri, l'ultimo per discriminare tra i possibili esiti del test di connessione, per i quali viene creato un enumerato:
Private mCaller As String = String.Empty Private mServerIniz As String = String.Empty Private mUserIdIniz As String = String.Empty Private mPasswordIniz As String = String.Empty Private mTestOK As esitoTest = esitoTest.NoTest Private Enum esitoTest NoTest ' il test di connessione non è andato a buon fine TestOkNoSaved ' è andato a buon fine ma non sono stati salvati i parametri TestOkSaved ' è andato a buon fine e sono stati salvati i parametri End EnumDepositare i parametri iniziali serve se il chiamante è FrmMain. Infatti, se il chiamante è Program e i parametri non sono impostati e salvati correttamente dall'utente all'uscita dalla form. il programma viene terminato. Invece, se il chamante è FrmMain e i parametri non sono correttamente impostati e salvati, bisogna ripristinare i parametri iniziali, letti all'apertura della form. Si parte dal presupposto che i valori dei parametri siano corretti, se la FrmMain è stata aperta.
Il campo mTestOK può assumere tre valori:
- NoTest - il test di connessione non è stato effettuato o non è andato a buon fine
- TestOkNoSaved - il test di connessione è andato a buon fine ma i parametri non sono stati salvati
- TestOkSaved - il test di connessione è andato a buon fine e i parametri sono stati salvati.
In fase di chiusura, il DialogResult da restituire dal chiamante viene deciso in base al chiamante e al valore di mTestOK, come si vedrà nel seguito.
Nel metodo FrmParametriConnessione_Load, dopo la valorizzazione dei campi 'prec', vengono valorizzati i campi 'iniz':
mServerIniz = mServerPrec mUserIdIniz = mUserIdPrec mPasswordIniz = mPasswordPrecIl codice del metodo FrmParametriConnessione_FormClosing viene completamente sostituito con il seguente, nel quale, in base al fatto che i parametri siano stati modificati, al chiamante e al valore di mTestOK, vengono assunti i comportamenti del caso, visualizzando i messaggi adeguati alle situazioni e impostando il DialogResult adatto:
Private Sub FrmParametriConnessione_FormClosing(ByVal sender As System.Object, _ ByVal e As System.Windows.Forms.FormClosingEventArgs) _ Handles MyBase.FormClosing If Not (e.CloseReason = CloseReason.ApplicationExitCall _ OrElse e.CloseReason = CloseReason.WindowsShutDown _ OrElse e.CloseReason = CloseReason.TaskManagerClosing) Then If mDatiModificati Then If mCaller = "Program" Then Select Case mTestOK Case esitoTest.NoTest ' I parametri indicati dall'utente non sono corretti o non è stato effettuato il test ' di connessione. Viene chiesto all'utente se desidera modificare i parametri. ' Se risponde di no viene avvisato che l'applicazione verrà terminata If Messaggi.SiNo(WAR_TestKo_Program, "Salvataggio parametri") = DialogResult.Yes Then e.Cancel = True Else Me.DialogResult = Windows.Forms.DialogResult.Cancel End If Case esitoTest.TestOkNoSaved ' Il test di connessione è andato a buon fine ma l'utente non ha ancora salvato i ' parametri. Viene quindi chiesto se prima di uscire dalla form si desidera salvarli. ' Se risponde di no viene avvisato che l'applicazione verrà terminata If Messaggi.SiNo(WAR_SaveOnExit_Program, _ "Salvataggio parametri") = DialogResult.Yes Then Salva() Me.DialogResult = Windows.Forms.DialogResult.OK Else Me.DialogResult = Windows.Forms.DialogResult.Cancel End If Case esitoTest.TestOkSaved ' Il test di connessione è andato a buon fine e i parametri sono stati salvati. ' In questo caso la form verrà chiusa e verrà avviata la form Main Me.DialogResult = Windows.Forms.DialogResult.OK End Select Else ' mCaller = "FrmMain" Select Case mTestOK Case esitoTest.NoTest ' I parametri indicati dall'utente non sono corretti o non è stato effettuato il test ' di connessione. viene chiesto all'utente se desidera modificare i parametri. ' Se risponde di no vengono ripristinati i parametri iniziali If Messaggi.SiNo(WAR_TestKo_Main, "Salvataggio parametri") = DialogResult.Yes Then e.Cancel = True Else txtServer.Text = mServerIniz txtUserID.Text = mUserIdIniz txtPassword.Text = mPasswordIniz Salva() End If Case esitoTest.TestOkNoSaved ' Il test di connessione è andato a buon fine ma l'utente non ha ancora salvato i ' parametri. Viene quindi chiesto se prima di uscire dalla form si desidera salvarli. ' Se risponde di no vengono ripristinati i parametri iniziali If Messaggi.SiNo(WAR_TestKo_Main, "Salvataggio parametri") = DialogResult.Yes Then e.Cancel = True Else txtServer.Text = mServerIniz txtUserID.Text = mUserIdIniz txtPassword.Text = mPasswordIniz Salva() End If Case esitoTest.TestOkSaved ' Il test di connessione è andato a buon fine e i parametri sono stati salvati. ' In questo caso la form verrà chiusa Me.DialogResult = Windows.Forms.DialogResult.OK End Select End If ' mCaller = "Program" Else ' not mDatiModificati If mCaller = "Program" Then Messaggi.Avviso(WAR_Chiusura_App_Program, "Parametri connessione database") Me.DialogResult = Windows.Forms.DialogResult.Cancel Else Me.DialogResult = Windows.Forms.DialogResult.OK End If End If ' mDatiModificati End If ' e.CloseReason End SubNel metodo btnAnnulla_Click, dopo avere impostato mDatiModificati a False, viene aggiunta l'impostazione della variabile mTestOK a esitoTest.NoTest:
mTestOK = esitoTest.NoTestAnalogamente,vengono aggiunte impostazioni di mTestOK nel metodo btnTestaCon_Click, a seconda del caso:
If mDatiModificati Then If Messaggi.SiNo(WAR_DatiModificati, "Test connessione") = DialogResult.Yes Then Salva() End If End If 'Testo la connessione del DB principale If SqlHelper.TestConnection(txtStringaConN.Text) Then Messaggi.Info(String.Format(FMP_ConnectionOk, CATALOG, txtServer.Text), "Risultato test") mTestOK = esitoTest.TestOkNoSaved Else Messaggi.Avviso(String.Format(FMP_ConnectionKo, CATALOG, txtServer.Text), "Risultato test") mTestOK = esitoTest.NoTest End IfApportando queste modifiche ho corretto il secondo parametro fornito per la costruzione della stringa del messaggio: passavo, invece del nome del database (APP-P), la stringa "Test connessione". Ricordo che la costante CATALOG contiene il nome del database SQL.
Nel metodo Salva, prima di salvare i parametri crittati, è stato aggiunto un test della conessione con relativa impostazione di mTestOK:
If SqlHelper.TestConnection(txtStringaConN.Text) Then mTestOK = esitoTest.TestOkNoSaved Else Messaggi.Avviso(String.Format(FMP_ConnectionKo_Salva, CATALOG, txtServer.Text), _ "Risultato test") mTestOK = esitoTest.NoTest Exit Sub End IfInfine, l'impostazione a False di mDatiModificati è stata sostituita dall'impostazione di mTestOK:
mTestOK = esitoTest.TestOkSavedCorrezione
Prima di procedere al collaudo, c'è da correggere una riga, nel codice del metodo CartellaWinDatiApp della classe accessoria GestoreFiles, che deve restituire:Return Path.Combine(My.Computer.FileSystem.SpecialDirectories.AllUsersApplicationData, nomeFile)Collaudo
Compiliamo e lanciamo la nostra applicazione. Se tutto va bene, compare il messaggio che ci informa della mancanza del file dei parametri di connessione al database e dell'apertura della form di salvataggio degli stessi.
Quindi viene aperta la form:
![]()
Nella casella per il nome del server è sufficiente digitare il nome della macchina, la scritta "\SQLEXPRESS" è aggiunta automaticamente dal programma (non ve ne eravate accorti, vero? :o)). Naturalmente, questi parametri sono quelli di Diego, la cui macchina si chiama CDSOFTDIEGO, sulla quale all'istanza SQLEXPRESS è stato aggiunto un account programmi con una password pwdprogrammi. Voi metterete il nome della vostra macchina e i dati dell'account che avete destinato all'uso del database APP-P, quello creato nella quinta puntata.
Se tutto va senza intoppi, dovreste arrivare all'apertura della FrmMain provvisoria, che fra poco elimineremo, per sostituirla con quella definitiva.
Creazione della FrmMain
In Esplora soluzioni (Solution Explorer) facciamo 'clic-destro' sul file FrmMain.vb e cancelliamolo.
Facciamo 'clic-destro' sul nome del progetto PrimiPassi e aggiungiamo un nuovo elemento, scegliendo Kripton Form nella sezione Modelli personali e assegnando il nome FrmMain.![]()
Facendo riferimento alla figura precedente, disegnamo la form:
- KryptonManager (già presente di default)
- GlobalPaletteMode = Office 2007 - Blue
- FrmMain
- BackColor = (Web) SkyBlue
- Icon = Lurker.ico (dalla nostra cartella immagini)
- IsMdiContainer = True (è normale che sparisca il colore di sfondo scelto in precedenza)
- Text = Primi Passi
- WindowState = Maximized
- KryptonPanel (già presente di default): eliminiamolo
- Trasciniamo un controllo MenuStrip (MenuStrip1)
- Man mano che aggiungiamo le varie voci di menu, il cui nome verrà impostato automaticamente, ne cambieremo solo la proprietà Font Size = 10
- &Esci (come dovrebbe essere noto la & non viene visualizzata e contrassegna il carattere successivo come scorciatoia di tastiera per il menu in combinazione con il tasto Alt)
- &Anagrafiche
- Stati
- &Utilità (dopo averlo inserito, Diego consiglia di sostituire nel nome la a accentata con una normale)
- Connessione Database
- &Finestra
- Affianca orrizzontalmente
- Affianca verticalmente
- Chiudi tutte
- Disponi icone
- Sovrapponi
- Trasciniamo un controllo StatusStrip (StatusStrip1)
- RenderMode = ManagerRenderMode
- aggiungiamo una StatusLabel
- Name = tssInfo
- Text = Info
Il sistema di comunicazione delle informazioni tra le form figlie e la FrmMain
L'ultimo controllo disegnato ha la funzione di informare l'utente sull'operazione in corso quando un controllo di una form figlia riceve il focus.Le informazioni da visualizzare vengono passate dalla form figlia alla FrmMain tramite lo scatenamento di un evento, cui risponde un apposito gestore nel codice della FrmMain, che imposta una proprietà, la quale valorizza il contenuto della label di stato.
[Diego] Per implementare questo sistema serve una classe per l'argomento dell'evento (le informazioni da visualizzare). Perché il sistema funzioni non è indispensabile questa classe (infatti Oscar non ci ha pensato), ma una programmazione corretta lo impone: la firma normale di qualsiasi evento in .Net richiede due parametri: l'oggetto origine dell'evento e gli argomenti dell'evento, che ci siano oppure no:
Public Event nomeEvento(ByVal sender As Object, ByVal e As EventArgs)Gli argomenti, che siano zero, o uno o più, sono esposti da un oggetto di tipo EventArgs, cioè definito da EventArgs o da una classe che eredita da EventArgs.
Il nome di questa classe riprende il nome dell'evento. Posto che il nome dell'evento - dato che esso riguarda il cambiamento del controllo attivo, poiché si vuole scatenarlo quando un controllo ottiene il focus - sia ActiveControlChanged, e che le conseguenze di questo evento riguardano essenzialmente l'interfaccia utente, facciamo clic-destro sul progetto APP.UI e aggiungiamo una classe, denominandola ActiveControlChangedEventArgs:Public Class ActiveControlChangedEventArgs Inherits EventArgs Private mMessaggioInfo As String Public ReadOnly Property MessaggioInfo() As String Get Return mMessaggioInfo End Get End Property Public Sub New(ByVal messaggioInfo As String) mMessaggioInfo = messaggioInfo End Sub End ClassLa classe eredita da EventArgs, espone la proprietà di sola lettura MessaggioInfo attraverso il campo mMessaggioInfo che viene valorizzato nel codice del costruttore con l'apposito parametro. E' quasi più difficile decriverlo che implementarlo.
Avendo questa classe a disposizione, possiamo dichiarare un evento in qualsiasi form figlia, a esempio in FrmParametriConnessione:
Public Event ActiveControlChanged(ByVal sender As Object, ByVal e As ActiveControlChangedEventArgs)Possiamo (vogliamo) scatenarlo laddove qualche controllo riceve il focus:
Private Sub txtPassword_Enter(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles txtPassword.Enter RaiseEvent ActiveControlChanged(Me, New ActiveControlChangedEventArgs( _ "Info : " & "Password di accesso al database")) End Sub Private Sub txtServer_Enter(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles txtServer.Enter RaiseEvent ActiveControlChanged(Me, New ActiveControlChangedEventArgs( _ "Info : " & "Nome della macchina su cui risiede l'istanza SQLEXPRESS")) End Sub Private Sub txtUserID_Enter(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles txtUserID.Enter RaiseEvent ActiveControlChanged(Me, New ActiveControlChangedEventArgs( _ "Info : " & "Nome dell'utente abilitato ad accedere al database")) End SubL'evento viene gestito da un gestore appositamente predisposto in FrmMain, nella quale quindi dobbiamo impostare la direttiva che fa 'vedere' la classe di argomento dell'evento, nonché la proprietà da riferire al contenuto della label di stato tssInfo:
Imports APP.UI Public Class FrmMain Private mInfoMessage As String Public Property InfoMessage() As String Get Return mInfoMessage End Get Set(ByVal value As String) mInfoMessage = value If Not (mInfoMessage = String.Empty) Then tssInfo.Text = mInfoMessage End If End Set End PropertySi è deciso di implementare una proprietà perché potrebbe essere utile per informazioni di altro tipo.
Adesso è tutto pronto per implementare il gestore di evento:Private Sub FormFiglia_ActiveControlChanged(ByVal sender As Object, _ ByVal e As ActiveControlChangedEventArgs) Me.mInfoMessage = e.MessaggioInfo End SubQuesto gestore viene associato a ogni form figlia quando ne viene creata un'istanza, prima di mostrarla (in questo caso si tratta di una form di dialogo, ma la sostanza non cambia):
Private Sub ConnessioneDatabaseToolStripMenuItem_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles ConnessioneDatabaseToolStripMenuItem.Click Dim dlg As New FrmParametriConnessione("Main") AddHandler dlg.ActiveControlChanged, AddressOf FormFiglia_ActiveControlChanged dlg.ShowDialog() End SubDel resto del codice della FrmMain, è interessante illustrare il codice del gestore dell'evento Load:
Private Sub FrmMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load ' scandisco tutti i controlli cercando quelli di tipo MdiClient For Each ctl As Control In Me.Controls Try ' converto il tipo del controllo in MdiClient Dim ctlMDI As MdiClient = CType(ctl, MdiClient) ' cambio il BackColor del controllo ctlMDI.BackColor = Me.BackColor Catch exc As InvalidCastException ' catturo e ignoro l'errore di tipizzazione End Try Next ctl ' aggiungo info nell'area destra della barra del titolo Me.TextExtra = "Nome computer : " & My.Computer.Name & " " & _ "Data : " & DateTime.Today.ToShortDateString End SubViene impostato il colore di fondo della FrmMain con il colore impostato in fase di progettazione al posto di quello impostato di default per le form MDI, sfruttando MDIClient, (che si comporta come una sorta di panel che si sovrappone alla form MDI).
Viene inoltre sfruttata una caratteristica carina delle form Krypton: un testo aggiuntivo a destra.Il resto del codice, al momento, è costituito solo dalla gestione dei menu Finestra ed Esci.
Conclusione
In questo articolo abbiamo corretto qualche errore, disegnato la FrmMain e impostato un comportamento di base sfruttando la comunicazione tramite eventi.
Arrivederci alla prossima puntata, in cui cominceremo a trattare con il database.Il codice a corredo di questo articolo è 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.