Le avventure in VB.Net di un principiante ex-VB6 - 4
a cura di Oscar Zanin e Diego Cattaruzza (requisiti: Visual Basic Express e SqlServer Express)Premessa
L'analisi delle esigenze dell'applicazione è stata fatta a suo tempo, quando è stato sviluppato il programma in VB6 di cui si sta illustrando il rifacimento.
La versione .Net ha richiesto una nuova analisi, ma è stato abbastanza agevole distinguere le funzionalità richieste dall'implementazione futura del codice.
Anche sulla falsariga degli articoli della serie "Guarda! Senza mani!", in questo articolo viene illustrata la creazione di alcune classi accessorie.Classe GestoreEccezioni
In "Esplora soluzioni" facciamo click con il tasto destro sul nome del progetto APP.Base, selezioniamo la voce "Aggiungi/Classe", denominandola GestoreEccezioni.
Questa classe ricalca, a parte il nome italianizzato, la classe ExceptionHelper dell'articolo "Guarda! Senza mani! - Prima parte", cui si rimanda per la spiegazione del codice e del suo utilizzo.Classe Messaggi
Aggiungiamo una classe al progetto APP.UI e denominiamola Messaggi.
Questa classe ricalca, a parte il nome italianizzato, la classe Warnings dell'articolo "Guarda! Senza mani! - Prima parte", cui si rimanda per la spiegazione del codice e del suo utilizzo.Rispetto all'originale, è stato aggiunto il parametro titolo ai vari metodi.
Inoltre è stato aggiunto un metodo SiNo, per messaggi con richiesta di conferma:''' <summary> ''' Richiesta conferma Si/No ''' </summary> ''' <param name="messaggio">messaggio</param> ''' <param name="titolo">Titolo della form</param> ''' <returns>DialogResult.Yes o DialogResult.No</returns> Public Shared Function SiNo(ByVal messaggio As String, ByVal titolo As String) As DialogResult Return MessageBox.Show(messaggio, titolo, MessageBoxButtons.YesNo, MessageBoxIcon.Question, _ MessageBoxDefaultButton.Button2) End FunctionClasse Stringhe
Aggiungiamo una classe al progetto APP.Base e denominiamola Stringhe.
E' una classe che vuole raggruppare tutte le funzioni applicabili alle stringhe ed è tratta in parte dalla classe CompareHelper dell'articolo "Guarda! Senza mani! - Seconda parte".
Riporto qualche informazione riguardante il metodo Confronta (StringComparer nell'originale):Ci sono dei casi particolari nella comparazione di stringhe nei quali i due oggetti possono essere vuoti o contenere valori non validi, quali il null, pertanto è necessario gestirli.
Cosa fa il metodo Confronta (definiamo Obj la prima stringa passata, la seconda New):
- suppone per default che la stringa Obj sia maggiore della stringa New
- verifica se la stringa Obj non è nulla
- se Obj non è nulla, e New non è nulla, restituisce il risultato della comparazione tra le due stringhe
- se Obj non è nulla e New è nulla, restituisce il valore preimpostato, quindi Obj è considerata maggiore di Nothing
- se Obj è nulla e New è nulla, restituisce un'uguaglianza, quindi le stringhe nulle sono considerate uguali
- se Obj è nulla e New non è nulla, allora il valore di ritorno indica che New è maggiore di Obj
Facendo qualche esempio pratico:
- CompareHelper.StringComparer( Nothing, Nothing ) restituisce 0 (stringhe uguali)
- CompareHelper.StringComparer( Nothing, "ciao" ) restituisce -1 (la stringa New è maggiore della stringa Obj)
- CompareHelper.StringComparer( "ciao", Nothing ) restituisce 1 (la stringa New è minore della stringa Obj)
- CompareHelper.StringComparer( "ciao", "mandi" ) restituisce 1 (la stringa New è maggiore della stringa Obj)
- CompareHelper.StringComparer( "mandi", "ciao" ) restituisce -1 (la stringa New è minore della stringa Obj)
Con questo metodo sono io a decidere come si comporteranno le comparazioni fra stringhe nelle applicazioni. Infatti, il metodo CompareTo della classe String, quando uno degli elementi è nullo o lo sono entrambi, lancia una eccezione. Invece di gestire le eccezioni in ogni classe, ho deciso una convenzione comparativa per le stringhe nulle e mi sono tolto il pensiero.
Alla classe originale di Diego e Sabrina ho aggiunto un metodo per centralizzare la sostituzione dell'apice singolo con il doppio apice nel caso in cui una stringa debba essere usata come criterio in una query:
Public Shared Function ValQ(ByVal valore As String) As String If valore = String.Empty Then Return valore End If valore = valore.Replace("'", "''") Return valore End FunctionArrivando dal Visual Basic 6 mi sono trovato spiazzato nel non trovare più diverse funzioni applicabili alle stringhe. Per essere più precisi, ci sono ancora, ma, togliendo dai progetti il riferimento a Microsoft.VisualBasic bisogna percorrere vie alternative, cercando tra i metodi forniti dalla classe String.
[Diego] Bisogna rendersi conto delle differenze di impostazione concettuale tra Visual Basic 6 e Visual Basic .Net (e .Net in generale): in VB6 ci sono delle funzioni rese disponibili dal linguaggio, in VB.Net ci sono dei metodi forniti dalle classi.
Laddove in VB6, per esempio, troviamo CInt per convertire in intero un valore di altro tipo, in VB.Net abbiamo tanti altri metodi per effettuare conversioni più specifiche. La classe Int32 espone il metodo Parse per convertire in Int32 una stringa, il metodo TryParse per tentare la conversione (anche altri tipi offorno gli stessi metodi, e altri ancora).
Ecco perché si parla di metodi (e non di funzioni): sono specifici di ciascuna classe (ciascun tipo)
La domanda da farsi, quando ci chiediamo cosa abbiamo a disposizione per ottenere una data funzionalità, non è: "che cosa mi offre Visual Basic per fare questo?", ma: "quale classe di .Net mi fornisce il metodo per fare questo?". Nella stragrande maggioranza la risposta è la stessa a questa domanda: "quale classe è interessata da questa funzionalità?", oppure a questa domanda: "a cosa si appllica questa funzionalità?".
Tornando all'esempio di poco fa, diviene molto facile analizzare che ci interessa ottenere un Int32 e che è nella classe Int32 che troviamo (con l'aiuto di Intellisense) il metodo Parse per convertire in intero una stringa.[Diego] Nel codice di Oscar - a parte le traduzioni in italiano di nomi di classi, di metodi e di parametri (che a mio parere perdono il tempo che trovano, ma che lascio per non rompere troppo la compatibilità con gli articoli che ne parlano) - trovo due stili di codificazione che non mi piacciono e non condivido (e quindi non lascerò comparire, sobbarcandomi l'onere delle modifiche):
i commenti di Oscar sono (erano) alla maniera vecchia di VB6, così:
'Metodo helper per costruire un messaggio coerente per le eccezioni 'Parametri della funzione : 'className = Nome della classe 'method = Dati reflection del metodo corrente 'ex = Eccezione 'Ritorna un messaggio formattatoIo invece considero importante e molto conveniente sfruttare il commento strutturato fornito dall'IDE di Visual Basic, avviabile con la semplice digitazione di tre apici, in seguito alla quale ottengo questo:
''' <summary> ''' Metodo helper per costruire un messaggio coerente per le eccezioni ''' </summary> ''' <param name="className">Nome della classe</param> ''' <param name="method">Dati reflection del metodo corrente</param> ''' <param name="ex">Eccezione</param> ''' <returns>messaggio formattato</returns>La documentazione che si ottiene è molto più chiara e soprattutto è 'leggibile' da Intellisense, che la fornisce nel tooltip che compare quando si seleziona un metodo così documentato di una classe.
La firma dei metodi di Oscar segue convenzioni di denominazione vecchi e da considerare 'bislacchi' già ai tempi del VB6. Essa (la firma) verrà cambiata per quanto possibile, mentre negli articoli, per scopi didattici, verrà mostrato il prima e il dopo e il perché.
Come nel caso della 'funzione' ValQ (questa dovrebbe essere l'ultima volta che leggete il termine 'funzione': in .Net non ci sono 'funzioni di linguaggio', ma 'metodi di classe', ricordate?). L'originale era questa:Public Shared Function ValQ(ByVal Valore As String) As StringIl nome del metodo non descrive chiaramente la funzionalità svolta: è necessario ricordarsi del significato di 'val', nonché dell'abbreviazione 'Q' (query, probabilmente) e ricordarsi anche cosa fa.
E' un sostantivo, il che farebbe pensare a una proprietà della classe Stringhe, ma invece è un metodo che compie una azione di modifica.
Il nome del parametro comincia con una maiuscola, il che non va bene: i nomi dei parametri dovrebbero essere sempre scritti con l'iniziale minuscola. La maiuscola è riservata alle classi e ai membri, non alle variabili e ai parametri.La 'mia' versione, di firma e di codice, sarebbe stata questa:
Public Shared Function RaddoppiaApici(ByVal valore As String) As String Return valore.Replace("'", "''") End FunctionNel codice fornito con la serie troverete che viene mantenuta la firma col nome bislacco, il parametro minuscolo e il codice più snello.
Classe ControlloValorizzazioneStringa
Aggiungiamo una classe al progetto APP.Base e denominiamola ControlloValorizzazioneStringa.
Questa classe ricalca la classe StringHelper esposta in "Guarda! Senza mani! - Ottava parte" e contiene il metodo IsNullOrTrimEmpty per verificare, una volta eliminati gli spazi iniziali e finali di una stringa (Trim), se quest’ultima è vuota o nulla.Classe EccezionePercorsoVuoto
Aggiungiamo una classe al progetto APP.Base e denominiamola EccezionePercorsoVuoto.
Questa classe ricalca la classe EmptyPathException esposta in "Guarda! Senza mani! - Terza parte", cui si rimanda per le spiegazioni e un esempio esaustivo.Classe EccezioneFileNonTrovato
Aggiungiamo una classe al progetto APP.Base e denominiamola EccezioneFileNonTrovato.
Questa classe ricalca la classe NoFileException esposta in "Guarda! Senza mani! - Terza parte", cui si rimanda per le spiegazioni.Classe EccezioneParametroNonValido
Aggiungiamo una classe al progetto APP.Base e denominiamola EccezioneParametroNonValido.
Questa classe ricalca la classe IoHelper esposta in "Guarda! Senza mani! - Terza parte", cui si rimanda per le spiegazioni.Classe GestoreFiles
Aggiungiamo una classe al progetto APP.Base e denominiamola GestoreFiles.
Questa classe contiene metodi che sono la traduzione in italiano dei metodi della classe IoHelper esposta in "Guarda! Senza mani! - Terza parte" e in "Guarda! Senza mani! - Quinta parte", cui si rimanda per le spiegazioni, e un metodo nuovo:
- CheckEmptyPath (ControlloPercorsoVuoto)
- CheckNoFile (ControlloEsistenzaFile)
- GetAppDir e suo overload (CartellaInstalApp)
- CheckCreateDir (ControlloEsistenzaCartella)
- CreateAppConfigFileName (CreaNomeFileConfig)
- CreateUsrConfigFileName (CreaNomeFileConfUtente)
- CartellaWinDatiApp
Nella versione originale dell'articolo "Guarda! Senza mani! - Quinta parte" il file di configurazione viene salvato nella cartella di installazione del programma. Io ho preferito creare la funzione CartellaWinDatiApp che mi permette di salvare il file di configurazione nella cartella Dati applicazioni di Windows. Questo per evitare i fastidiosi problemi di permessi.
Nella documentazione del metodo CreaNomeFileConfUtente troverete che ho copiato un paragrafo dell'articolo "Guarda! Senza mani! - Quinta parte". E' un caso dove serviva una spiegazione più estesa.
Nei limiti del possibile mi piace commentare il codice. Ho la memoria corta e la cosa mi torna utile quando devo riprendere in mano il codice dopo svariato tempo, senza contare che se dovessi passare un mio progetto ad altri magari troverebbero meno difficoltà ad interpretarlo.[Diego] Alla luce di questo passo, penso che Oscar non sapesse della possibilità di commentare le procedure in modo comprensibile a Intellisense. E nemmeno della possibilità di cambiare il nome ValQ con RaddoppiaApici in tutta la soluzione (tenendolo solo perché già deprecabilmente usato nel progetto VB6).
Classe SqlHelper
Aggiungiamo una classe al progetto APP.Data e denominiamola SqlHelper.
Questa classe contiene metodi per lavorare con i database SQL, descritti parte nell'articolo "Guarda! Senza mani! - Sesta parte" (TestConnection) e parte nell'articolo "Guarda! Senza mani! - Nona parte" (gli altri).
Essi si occupano dell'esecuzione di un comando SQL che non fornisce dati, ma esegue operazioni sul database, dell'esecuzione di un comando SQL che fornisce un singolo dato, della verifica dell’esistenza di un database su un server, effettuando il test di una stringa di connessione a un database, fornita come costante.Classe ConvalidaDati
Aggiungiamo una classe al progetto APP.Data e denominiamola ConvalidaDati.
Questa classe l'ho fatta io (tanto per cambiare :o)) ed espone un solo metodo, Convalida, che si occupa di controllare la validità di un contenuto per un campo: se il contenuto è valido ritorna vero altrimenti viene visualizzato un messaggio di errore che descrive il problema e ritorna falso.
In esso si fa uso di un enumerato dei tipi di convalida, anch'esso implementato nella classe:''' <summary> ''' Enumerazione dei tipi di convalida ''' </summary> Public Enum TipoConv Data Percentuale Migliaia_senza_decimali Migliaia_con_decimali Valuta Anno Percentuale_maggiore_di_zero Percentuale_maggiore_o_uguale_a_zero Migliaia_senza_decimali_maggiore_di_zero Migliaia_senza_decimali_maggiore_o_uguale_a_zero Migliaia_con_decimali_maggiore_di_zero Migliaia_con_decimali_maggiore_o_uguale_a_zero Valuta_maggiore_di_zero Valuta_maggiore_o_uguale_a_zero Testo End EnumL'enumerato TipoConv (tipo convalida) permette allo sviluppatore che richiama il metodo Convalida e ne compila i parametri di farsi proporre dall'IDE il tipo di controllo da effettuare sul campo. In alternativa avrei potuto gestire un campo numerico, ma poi mi sarei dovuto ricordare che passare 5 vuol dire controllare se il campo è una valuta valida. Direi che è meglio scegliere da un elenco la parola Valuta.
[Diego] è proprio questa la ragione per cui hanno inventato gli enumerati.
''' <summary> ''' Controlla la validità del contenuto per un campo ''' </summary> ''' <param name="valore">contenuto per il campo</param> ''' <param name="nomeCampo">nome del campo</param> ''' <param name="tipoCampo">tipo del campo e quindi del controllo (tipo di convalida)</param> ''' <param name="obbligatorio">obbligatorietà del campo</param> ''' <returns>Se il contenuto è valido ritorna vero altrimenti falso</returns> ''' <remarks>in caso di non validità viene visualizzato un messaggio di errore che descrive il problema ''' </remarks> Public Shared Function Convalida(ByVal valore As String, ByVal nomeCampo As String, _ ByVal tipoCampo As TipoConv, ByVal obbligatorio As Boolean) As BooleanIl parametro valore è di tipo stringa perché il contenuto di una TextBox è sempre e comunque una stringa. Poi noi lo trasformiamo in quello che vogliamo per salvarlo nel database.
Il parametro nomeCampo serve per costruire debitamente il messaggio di errore.
Il parametro tipoCampo è del tipo enumerato TipoConv e indica il tipo di campo cui è destinato il valore. Di conseguenza indica il tipo di controllo che sarà fatto sul contenuto.
Il parametro obbligatorio indica se il campo è obbligatorio per la tabella nel database. Notate che un campo potrebbe non essere obbligatorio pur dovendo essere controllato. Faccio un esempio: un campo numerico anche se non obbligatorio va controllato perché se valorizzato deve essere un numero e non altro.Il codice implementato nel metodo è una serie di verifiche di possibili risultati False, prima di restituire True. Se ne mostra solo una parte, per commentarla:
'... Select Case tipoCampo Case TipoConv.Data 'Data Dim Datac As New DateTime Try Datac = DateTime.Parse(valore) Catch ex As FormatException 'La data non è in un formato valido Messaggi.Avviso("Il campo " & nomeCampo & " non contiene una data valida !" & _ Environment.NewLine & "Digitarne una nel formato 05/05/2008.", "Attenzione") Return False Catch ex As Exception 'Errore altro tipo 'La data non è in un formato valido Messaggi.Avviso("Errore: " & ex.ToString & " sul campo " & nomeCampo, "Attenzione") Return False End Try Return True '...In un costrutto Select viene scandito l'enumerato. Per ogni tipo di convalida viene dichiarata una variabile conforme (DateTime se è una data, per esempio) e si tenta di valorizzarla con il dato passato usando il metodo Parse del tipo. Il metodo Parse si occupa proprio della conversione di un dato rappresentato in stringa nel tipo equivalente.
Se la conversione non va a buon fine, essendo il comando all'interno di un blocco Try - Catch, l'eccezione viene intercettata e viene mostrato un messaggio di avviso all'utente, prima dell'uscita dal metodo con risultato falso.
Ora non si deve più usare l'Exit Function: con il Return si restituisce un valore e contestualmente si esce dal metodo.Nel codice potrete notare, oltre ai commenti per documentazione, anche l'uso delle Region per raggruppare porzioni di codice logicamente affini. Questo accorgimento rende il codice più pulito e rapidamente gestibile.
[Diego] In questo metodo, inoltre potrete esaminare come sono stati oculatamente distribuiti i salti di riga, in modo da evidenziare le parti salienti del codice. Questo inciso vuole solo sottolineare l'importanza di dedicare quei pochi secondi, mentre si digita il codice, a preoccuparsi di quando si dovrà rileggerlo per manutenerlo.
Classe FormHelper
Aggiungiamo una classe al progetto APP.UI e denominiamola FormHelper.
E' la stessa classe di cui agli articoli "Guarda! Senza mani! - Ottava parte" e "Guarda! Senza mani! - Nona parte" (successiva modifica).
Contiene due metodi per la gestione dei form: il primo (VediSeIlFormEAperto) verifica se un form è già stato aperto, il secondo (CheckFormList) è usato dal primo e cicla fra tutte le form.Classe Formattazione
Aggiungiamo una classe al progetto APP.UI e denominiamola Formattazione.
Anche questa è stata creata da me. Contiene un unico metodo statico (Formatta) che, come si può facilmente intuire, ha il compito di formattare una stringa in un formato da scegliere tra quelli esposti da un apposito tipo enumerato:#Region "Dichiarazione degli imports" Imports APP.Base #End Region Public Class Formattazione Public Enum TipoFor Data Percentuale Migliaia_senza_decimali Migliaia_con_decimali Valuta Testo End EnumL'enumeratore TipoFor (tipo formattazione) permette allo sviluppatore che richiama il metodo Formatta e ne compila i parametri di farsi proporre dall'IDE il tipo di formattazione da applicare alla stringa contenuta nel campo.
[Diego] La firma del metodo Formatta di Oscar:
Public Shared Function Formatta(ByVal Sdf As String, ByVal TipoContr As TipoFor) As String[Diego] La firma corretta da Diego, con la documentazione che rende superflua la spiegazione dei parametri in questo articolo:
''' <summary> ''' applica un formato al dato passato ''' </summary> ''' <param name="valore">valore da formattare</param> ''' <param name="formato">formato da usare</param> ''' <returns>il dato formattato nel formato indicato</returns> Public Shared Function Formatta(ByVal valore As String, ByVal formato As TipoFor) As StringNel codice si usa il metodo IsNullOrTrimEmpty della classe ControlloValorizzazioneStringa per verificare che il parametro valore sia valorizzato.
Quindi in un costrutto Select si scandisce l'enumerato TipoFor e per ciascun formato si applica la relativa formattazione sfruttando l'overload del metodo ToString.[Diego] Il codice del metodo Formatta di Oscar:
Select Case TipoContr Case TipoFor.Data 'Data Return CDate(Sdf).ToString("dd/MM/yyyy") Case TipoFor.Percentuale 'Percentuale Return CSng(Sdf).ToString("0.00") Case TipoFor.Migliaia_senza_decimali 'Migliaia senza decimali Return CLng(Sdf).ToString("#,###") Case TipoFor.Migliaia_con_decimali 'Migliaia con decimali Return CDbl(Sdf).ToString("#,##0.00") Case TipoFor.Valuta 'Valuta Return CDec(Sdf).ToString("€ #,##0.00") Case Else Return Sdf End Select[Diego] Il codice del metodo Formatta corretto da me, sostituendo le 'funzioni' di conversione VB6 con i 'metodi' .Net:
Select Case formato Case TipoFor.Data 'Data Return DateTime.Parse(valore).ToString("dd/MM/yyyy") Case TipoFor.Percentuale 'Percentuale Return Single.Parse(valore).ToString("0.00") Case TipoFor.Migliaia_senza_decimali 'Migliaia senza decimali Return Long.Parse(valore).ToString("#,###") Case TipoFor.Migliaia_con_decimali 'Migliaia con decimali Return Double.Parse(valore).ToString("#,##0.00") Case TipoFor.Valuta 'Valuta Return Decimal.Parse(valore).ToString("€ #,##0.00") Case Else Return valore End SelectE' importante specificare che questo metodo va bene solo per applicazioni che sfruttano le impostazioni internazionali italiane. Se infatti l'applicazione dovesse essere usata per archiviare e gestire dati in formato per esempio USA non andrebbe più bene, in quanto come vedete io uso separatori decimali, delle migliaia e simboli valuta fissi e corrispondenti a quelli italiani.
Un upgrade di questa funzione potrebbe comportare la lettura delle impostazioni internazionali del computer di installazione e agire di conseguenza, oppure l'impostazione di una parametrizzazione con conseguente scelta da parte dell'utente utilizzatore del set di impostazioni internazionali da applicare al programma.
Chissà, magari un giorno lo farò. Se ne sarò in grado, naturalmente. ;o)Classe SvuotaControlli
Aggiungiamo una classe al progetto APP.UI e denominiamola SvuotaControlli.
Contiene metodi per lo svuotamento dei contenuti di: TextBox, ComboBox, Label e CheckBox, uno per ogni tipo di controllo.
Sono metodi che, grazie alla programmazione ricorsiva, permettono di svuotare tutti i controlli di un determinato tipo contenuti in una form. Con tutti intendo non solo quelli disegnati direttamente sulla form ma anche quelli contenuti in eventuali controlli contenitore, per esempio tutte le TextBox disegnate direttamente sulla form, quelle contenute in un Panel e quelle contenute in un Panel a sua volta contenuto dal primo Panel.
Come si ottiene questo? Con la ricorsione. Riporto da Wikipedia: "Questo tipo di algoritmo risulta particolarmente utile per eseguire dei compiti ripetitivi su di un set di input variabili. L'algoritmo richiama se stesso generando una sequenza di chiamate che ha termine al verificarsi di una condizione particolare che viene chiamata condizione di terminazione, che in genere si ha con particolari valori di input".Poiché i metodi sono molto simili tra loro, si riporta solo quello relativo alle Textbox:
Public Sub PulisciTextBox(ByVal ctl As Control) Dim Casella As TextBox = TryCast(ctl, TextBox) If Casella IsNot Nothing Then Casella.Text = String.Empty ElseIf ctl.Controls.Count > 0 Then For k As Integer = 0 To ctl.Controls.Count - 1 'Ricorsione PulisciTextBox(ctl.Controls(k)) Next k End If End SubEcco un esempio dell'uso di questo metodo:
Dim PulCon As New SvuotaControlli PulCon.PulisciTextBox(Me)Un aneddoto sull'uso dei metodi di questa classe. La prima volta che li ho utilizzati, li ho richiamati in sequenza uno dopo l'altro. Il risultato che vedo sullo schermo mi ha spiazzato: la form conteneva diverse TextBox, una ComboBox e realtiva serie di Label, tra cui una in più che doveva contenere un valore non imputabile dall'utente, ma impostato automaticamente da me. Ebbene, volendo svuotare quell'unico campo Label avevo svuotato anche tutte le label che fungevano da etichette per gli altri controlli (TextBox e ComboBox).
Logico e giusto: è quello che doveva fare il metodo ricorsivo. Da quel momento ho deciso che invece di usare Label per funzioni come quella descritta sopra uso TextBox con la proprietà ReadOnly a vero.[Diego] Oppure si passa al metodo PulisciLabel solo 'quella' Label.
E' meglio implementare un metodo versatile che adattare la progettazione a un metodo troppo limitato.[Diego] Giusto per levarci il fastidio di quel 'temporaneo' errore nel metodo Main, aggiungiamo al progetto PrimiPassi una Form e chiamiamola FrmMain. La sostituiremo quando sarà il momento.
Conclusione
Avete presente quando un dentista si prepara tutti i suoi strumenti di tortura su una apposita mensolina accanto alla vostra testa? Ecco, allo stesso modo un programmatore predispone una serie di classi accessorie (come quelle illustrate in questo articolo) per usarle nel codice che andrà a implementare nello sviluppo vero e proprio dell'applicazione.Il codice fin qui prodotto è scaricabile dall'area download. Per suggerimenti o critiche potete scrivere a Oscar che, eventualmente, riferirà a Diego.
Nel prossimo articolo verrà sviluppata la form per impostare i parametri di connessione al database.