Guarda! Senza mani! - Nona parte
a cura di Sabrina Cosolo e Diego Cattaruzza (requisiti: conoscenza intermedia di Sql Server e di .Net)Premessa
In questa puntata, scriveremo tutta l'interfaccia utente che ci permette di usare e quindi testare le classi che abbiamo disegnato per la gestione della generazione del database.
L'interfaccia è piuttosto complessa: deve permetterci infatti di inserire dati strutturati di vario tipo.
Questa complessità ci serve come scusa per spiegare tutta una serie di concetti, che speriamo troverete utili per lo sviluppo delle vostre applicazioni. Vi anticipiamo gli argomenti, chissà che non vi ispiri a proseguire la lettura
Ecco cosa creeremo in questa puntata:
- Una form complessa, con menu, textbox, pulsanti, listbox, pannelli.
- Una form generica per l'immissione di una stringa, la nostra versione della InputBox di VBA.
- Una form generica per l'immissione di un valore da una combobox, la InputCombo.
- Una form per l'inserimento e modifica dei dati di una file specification con la possibilità di scegliere la cartella di destinazione usando le dialog standard di Windows.
- I metodi per eseguire comandi SQL su SQL Server 2005.
- I metodi per salvare su file xml e caricare da file xml la specifica per la generazione del database.
Qualche correzione
Prima di iniziare con le cose nuove, facciamo alcune piccole modifiche a ciò che avevamo fatto fin qui, per migliorare alcune cose e correggere alcuni problemi che sono stati scoperti sviluppando e testando il codice di questa puntata. Non ha senso che perdiamo tempo a spiegarvi come sono stati trovati, perché il debug non è centrale in questi articoli, ma se avete la versione 8.0 potete provare a fare la parte di sviluppo e poi debuggare e capire come abbiamo trovato i problemi.Classe TAndT.Data.Collections.DataFileGroups:
E' stata modificato il metodo ToSqlString nel seguente modo:
public string ToSqlString() { try { StringBuilder sb = new StringBuilder(); bool primo = true; foreach (DataFileGroup item in this) { if (!primo) { sb.AppendLine(", "); } //sb.AppendLine(item.ToSqlString()); <<<<<<<<< sb.Append(item.ToSqlString()); primo = false; } return (sb.ToString()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Function ToSqlString() As String Try Dim sb As New StringBuilder Dim primo As Boolean = True For Each item As DataFileGroup In Me If Not primo Then sb.AppendLine(", ") End If 'sb.AppendLine(item.ToSqlString) <<<<<<<<< sb.Append(item.ToSqlString) primo = False Next Return sb.ToString Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionAbbiamo modificato l'AppendLine in Append per evitare che le eventuali virgole fra un elemento e il successivo si trovino su una riga da sole, solo una questione estetica, ma è importante anche per la leggibilità del codice SQL generato.
Classe TAndT.Data.Collections.FileSpecifications
Anche in questa classe modifichiamo il metodo ToSqlString nello stesso modo per lo stesso motivo:
public string ToSqlString() { try { StringBuilder sb = new StringBuilder(); bool primo = true; foreach (FileSpecification item in this) { if (!primo) { sb.AppendLine(", "); } //sb.AppendLine(item.ToSqlString()); <<<<<<<<< sb.Append(item.ToSqlString()); primo = false; } return (sb.ToString()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Function ToSqlString() As String Try Dim sb As New StringBuilder Dim primo As Boolean = True For Each item As FileSpecification In Me If Not primo Then sb.AppendLine(", ") End If 'sb.AppendLine(item.ToSqlString) <<<<<<<<< sb.Append(item.ToSqlString) primo = False Next Return sb.ToString Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionClasse TAndT.Data.Entities.DatabaseGenerator:
Modifichiamo il ToSqlString perché vi avevamo inserito una riga errata
public string ToSqlString() { try { StringBuilder sb = new StringBuilder(); sb.AppendLine("CREATE DATABASE "); sb.Append("\t"); sb.AppendLine(this.DatabaseName); if (this.DataFiles.IsValid()) { //sb.AppendLine("\tON"); <<<<<<<<<<<< sb.AppendLine(this.DataFiles.ToSqlString()); } if (this.LogFileSpec.IsValid()) { sb.AppendLine("\tLOG ON"); sb.AppendLine(this.LogFileSpec.ToSqlString()); } if (!StringHelper.IsNullOrTrimEmpty(this.Collation)) { sb.AppendLine("\t"); sb.AppendFormat("COLLATE {0}", this.Collation); sb.AppendLine(); } return (sb.ToString()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Function ToSqlString() As String Try Dim sb As New StringBuilder sb.AppendLine("CREATE DATABASE ") sb.Append("\t") sb.AppendLine(Me.DatabaseName) If Me.DataFiles.IsValid Then 'sb.AppendLine("\tON") <<<<<<<<<<<<<<< sb.AppendLine(Me.DataFiles.ToSqlString()) End If If Me.LogFileSpec.IsValid Then sb.AppendLine("\tLOG ON") sb.AppendLine(Me.LogFileSpec.ToSqlString()) End If If Not StringHelper.IsNullOrTrimEmpty(Me.Collation) Then sb.AppendLine("\t") sb.AppendFormat("COLLATE {0}", Me.Collation) sb.AppendLine() End If Return sb.ToString() Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionIn questo caso abbiamo tolto la riga evidenziata, perché la generazione delle stringhe dei filegroups si occupa di generare anche il pezzo che era inserito in quel punto.
Classe TAndT.Data.Entities.DbFileMaxSize:
Abbiamo modificato l'IsValid della classe per fare in modo che funzioni correttamente quando si seleziona la modalità Unlimited
public override bool IsValid() { try { if (this.SizeUmi == VAL_SpecialUmiUnlimited) { return true; } else { return (this.SizeValue > 0 && !StringHelper.IsNullOrTrimEmpty(this.SizeUmi) && this.SizeUmi != VAL_SpecialUmiPercent); } } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Overrides Function IsValid() As Boolean Try If Me.SizeUmi = VAL_SpecialUmiUnlimited Then Return True Else Return Me.SizeValue > 0 AndAlso StringHelper.IsNullOrTrimEmpty(Me.SizeUmi) _ AndAlso Not Me.SizeUmi = VAL_SpecialUmiPercent End If Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionClasse TAndT.Data.Entities.FileSpecification
Abbiamo modificato due costanti
private const string FMP_ToSqlFileName = "FILENAME = '{0}', "; private const string FMP_ToSqlGrowth = "FILEGROWTH = {0} )";Private Const FMP_ToSqlFileName As String = "FILENAME = '{0}', " Private Const FMP_ToSqlGrowth As String = "FILEGROWTH = {0} )"La prima mancava del segno = dopo la parola FileName
La seconda, indicava GROWTH invece di FILEGROWTH.Abbiamo modificato il metodo ToSqlString
public string ToSqlString() { try { StringBuilder sb = new StringBuilder(); sb.AppendFormat(FMP_ToSqlName, this.Name); sb.AppendLine(); sb.AppendFormat(FMP_ToSqlFileName, this.FileName); sb.AppendLine(); //sb.AppendFormat(FMP_ToSqlSize, this.Size); <<<<<< sb.AppendFormat(FMP_ToSqlSize, this.Size.ToSqlString()); sb.AppendLine(); //sb.AppendFormat(FMP_ToSqlMaxSize, this.MaxSize); <<<<<< sb.AppendFormat(FMP_ToSqlMaxSize, this.MaxSize.ToSqlString()); sb.AppendLine(); //sb.AppendFormat(FMP_ToSqlGrowth, this.Growth); <<<<<< sb.AppendFormat(FMP_ToSqlGrowth, this.Growth.ToSqlString()); return (sb.ToString()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex); } }Public Function ToSqlString() As String Try Dim sb As New StringBuilder sb.AppendFormat(FMP_ToSqlName, Me.Name) sb.AppendLine() sb.AppendFormat(FMP_ToSqlFileName, Me.FileName) sb.AppendLine() 'sb.AppendFormat(FMP_ToSqlSize, Me.Size) <<<<<< sb.AppendFormat(FMP_ToSqlSize, Me.Size.ToSqlString) sb.AppendLine() 'sb.AppendFormat(FMP_ToSqlMaxSize, Me.MaxSize) <<<<<< sb.AppendFormat(FMP_ToSqlMaxSize, Me.MaxSize.ToSqlString) sb.AppendLine() 'sb.AppendFormat(FMP_ToSqlGrowth, Me.Growth) <<<<<< sb.AppendFormat(FMP_ToSqlGrowth, Me.Growth.ToSqlString) Return sb.ToString() Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionLa modifica è stata effettuata sulle righe contrassegnate perché le tre dimensioni erano formattate indicando solamente this.Size o this.MaxSize, quindi il metodo AppendFormat eseguiva automaticamente il ToString della classe per ottenerne la stringa, mentre noi abbiamo predisposto ToSqlString come metodo per formattare correttamente la classe in una stringa SQL. Abbiamo quindi sostituito la chiamata corretta per produrre la stringa SQL.
Classe TAndT.UsingSqlServer.UsingSqlServerConfig
public static string[] SizeUmiList { get { return AppSettings[STT_APPDbSizeUmi].Value.Split( new char[] { ';', ' ' }, StringSplitOptions.RemoveEmptyEntries); } set { AppSettings[STT_APPDbSizeUmi].Value = String.Join(";", value); } } public static string[] MaxSizeUmiList { get { return AppSettings[STT_APPDbMaxSizeUmi].Value.Split( new char[] { ';', ' ' }, StringSplitOptions.RemoveEmptyEntries); } set { AppSettings[STT_APPDbMaxSizeUmi].Value = String.Join(";", value); } } public static string[] GrowthUmiList { get { return AppSettings[STT_APPDbGrowthUmi].Value.Split( new char[] { ';', ' ' }, StringSplitOptions.RemoveEmptyEntries); } set { AppSettings[STT_APPDbGrowthUmi].Value = String.Join(";", value); } }Public Shared Property SizeUmiList() As String() Get Return AppSettings(STT_APPDbSizeUmi).Value.Split( _ New Char() {";"c, " "c}, StringSplitOptions.RemoveEmptyEntries) End Get Set(ByVal value As String()) AppSettings(STT_APPDbSizeUmi).Value = String.Join(";", value) End Set End Property Public Shared Property MaxSizeUmiList() As String() Get Return AppSettings(STT_APPDbMaxSizeUmi).Value.Split( _ New Char() {";"c, " "c}, StringSplitOptions.RemoveEmptyEntries) End Get Set(ByVal value As String()) AppSettings(STT_APPDbMaxSizeUmi).Value = String.Join(";", value) End Set End Property Public Shared Property GrowthUmiList() As String() Get Return (AppSettings(STT_APPDbGrowthUmi).Value.Split( _ New Char() {";"c, " "c}, StringSplitOptions.RemoveEmptyEntries)) End Get Set(ByVal value As String()) AppSettings(STT_APPDbGrowthUmi).Value = String.Join(";", value) End Set End PropertyAbbiamo modificato il tipo delle StringCollection in array di String, poiché il tipo StringCollection creava più complicazioni che facilitazioni.
Qualche modifica
Terminate le correzioni, passiamo alle cose interessanti, le cose nuove: per prima cosa facciamo una modifica al metodo VediSeIlFormEAperto, perché, seppure non in questa puntata, ci servirà in seguito: la modifica che faremo servirà a permetterci di verificare se una form è aperta sia essa MDIChild o Owned.Classe TAndT.UI.Forms.FormHelper
public static Form VediSeIlFormEAperto(string pName, Form pParent, bool pIsMdi, bool pIsOwner) { try { Form retForm = null; if (pIsMdi) { retForm = CheckFormList(pParent.MdiChildren, pName); } else { if (pIsOwner) { retForm = CheckFormList(pParent.OwnedForms, pName); } } return (retForm); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } } private static Form CheckFormList(Form[] pFormList, string pName ) { try { Form retForm = null; foreach (Form child in pFormList) { if (child.Name == pName) { retForm = child; break; } } return (retForm); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Shared Function VediSeIlFormEAperto(ByVal name As String, ByVal parent As Form, _ ByVal isMdi As Boolean, ByVal isOwned As Boolean) As Form Try Dim retForm As Form = Nothing If isMdi Then retForm = CheckFormList(parent.MdiChildren, name) Else If isOwned Then retForm = CheckFormList(parent.OwnedForms, name) End If End If Return retForm Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End Function Private Shared Function CheckFormList(ByVal formList As Form(), ByVal name As String) As Form Try Dim retForm As Form = Nothing For Each child As Form In formList If child.Name = name Then retForm = child Exit For End If Next Return retForm Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionIn questa modifica, abbiamo spostato il codice precedente del metodo VediSeIlFormEAperto in un nuovo metodo CheckFormList, che riceve una lista di form e il nome della form da controllare, mentre, del metodo VediSeIlFormEAperto, abbiamo cambiato i parametri aggiungendo due flag per indicare se si tratta di una richiesta per una MDIChild oppure per una Owned form, in base ad essi usiamo il metodo CheckFormList. Da notare inoltre che, in una classe statica, anche i metodi privati devono essere statici, se no non vengono visti nemmeno all'interno della stessa classe (questo nel caso a qualcuno sembri inutile qualificare statico un metodo privato).
Come conseguenza di questa modifica, nella classe FrmConfig verrà modificata la chiamata di VediSeIlFormEAperto, allo stesso modo saranno modificate le chiamate alla stessa funzione nel FrmMain.
public static void OpenMe(Icon pIcon, Form pMDI) { try { FrmConfig frm = (FrmConfig)FormHelper.VediSeIlFormEAperto("FrmConfig", pMDI, true, false); ...Public Shared Sub OpenMe(ByVal icon As Icon, ByVal mdi As Form) Try Dim frm As FrmConfig = DirectCast(FormHelper.VediSeIlFormEAperto("FrmConfig", mdi, True, _ False), FrmConfig) ...Ancora un Setting
Per la gestione della creazione dei database, ci serve ancora una cosa, un Setting che contenga la cartella base di destinazione dei file di database, da proporre automaticamente per la creazione. Pertanto modifichiamo la classe UsingSqlServerConfig.Classe UsingSqlServerConfig
La prima modifica è l'aggiunta di due costanti: il nome di un nuovo setting e il suo valore di default, che useremo per memorizzare la cartella da proporre per la generazione dei file di database.private const string STT_APPDbFilesFolder = "DbFilesFolder"; private const string DEFAULT_DbFilesFolder = "c:\\sqldatadir";Private Const DEFAULT_DbFilesFolder As String = "c:\sqldatadir" Public Const STT_APPDbFilesFolder As String = "DbFilesFolder"Modifichiamo il metodo LoadSettings, aggiungendo in fondo alla serie dei Settings di applicazione la generazione del nuovo setting per la cartella standard per il database.
public static void LoadSettings() { try { ... if (!AppSettings.ExistSetting(STT_APPDbFilesFolder)) { AppSettings.Add(STT_APPDbFilesFolder, DEFAULT_DbFilesFolder); hasToBeSaved = true; } TAndTSettings.LoadSettings(mUserFileName, ref mUserSettings); ... }Public Shared Sub LoadSettings() Try ... If Not AppSettings.ExistSetting(STT_APPDbFilesFolder) Then AppSettings.Add(STT_APPDbFilesFolder, DEFAULT_DbFilesFolder) hasToBeSaved = True End If TAndTSettings.LoadSettings(mUserFileName, mUserSettings) ... End SubAggiungiamo la proprietà per esporre il nuovo Setting.
public static string DbFilesFolder { get { return AppSettings[STT_APPDbFilesFolder].Value; } set { AppSettings[STT_APPDbFilesFolder].Value = value; } }Public Shared Property DbFilesFolder() As String Get Return AppSettings(STT_APPDbFilesFolder).Value End Get Set(ByVal value As String) AppSettings(STT_APPDbFilesFolder).Value = value End Set End PropertyNuovi menu nella classe FrmMain
Modifichiamo la nostra MDI per aggiungere le opzioni di menu necessarie a quanto svilupperemo in questa puntata.
Per prima cosa ne introduciamo due che ci permetteranno di testare due nuove Form generiche che metteremo in libreria.Spostiamoci sul Menu della FrmMain e nel menu Helper Tests aggiungiamo due nuove opzioni:
- Inputbox - inputboxToolStripMenuItem
- InputCombo - inputcomboToolStripMenuItem
Sul menu principale, aggiungiamo un nuovo menu ed un sottomenu che chiamiamo:
- &Database - databaseToolStripMenuItem
- &Crea Database - creadatabaseToolStripMenuItem
Facciamo doppio click su ciascuna delle tre opzioni di menu per generare i rispettivi metodi di gestione dell'evento Click, ma non scriviamo ancora nulla nel codice, che implementeremo quando avremo prodotto ciò che i tre menu chiamano.
Nuove Form nel Progetto TAndT.UI
In questo progetto abbiamo già generato la classe FormHelper, con il metodo che verifica l'esistenza di un istanza di una form e ne recupera l'istanza aperta oppure ne genera una nuova.Per poter creare l'interfaccia utente che ci permetta di generare un database, abbiamo bisogno di alcune form completamente nuove; fra queste, abbiamo individuato la necessità di utilizzare una form simile alla InputBox di VBA per acquisire un valore stringa, ed una form molto simile, ma in grado di farci selezionare un valore in una Combobox.
Considerato il fatto che due form simili potrebbero essere necessarie anche nei futuri sviluppi, abbiamo deciso di crearle come form generiche e di inserirle nella nostra libreria TAndT.UI.
Le due nuove form, sono le seguenti:
- FrmInputBox - Form per l'input di una stringa.
- FrmInputCombo - Form per l'input di un valore da combobox.
Prima di creare le nostre due form, apriamo il file Resources.resx del progetto TAndT.UI ed aggiungiamo le immagini per i due bottoni che ci serviranno per i tasti OK e Cancel, che si trovano nella cartella Resources del progetto a corredo.
Per aggiungerli, impostiamo Images e poi Add Resource> Add Existing File>, selezioniamo i file che saranno caricati sulle risorse come nell'immagine qui accanto.
La classe FrmInputBox
Dal progetto TAndT.UI, facciamo click con il tasto destro e selezioniamo Add>Windows Form> Chiamiamo la form FrmInputBox e aggiungiamo i controlli e modifichiamo le loro proprietà come indicato qui sotto:
- Un TooltipProvider
- Name = ttp
- Una Textbox
- Name = txtInput (C#)
o txtImput (VB)- Anchor = Top, Left, Right
- Multiline = true
- Scrollbars = vertical
- Text = ""
- Una Label
- Name = lblInput
- AutoSize = true
- Text = Testo Input
- Un Button
- Name = btnOk
- Anchor = Bottom
- Image = TAndT.UI.Properties.Resources.btn_032_107 (selezioniamola dalle risorse)
- Text = ""
- TextAlign = MiddleRight
- TooltipOnTtp =Conferma
- DialogResult = OK
- Un secondo Button
- Name = btnCancel
- Anchor = Bottom
- Image = TAndT.UI.Properties.Resources.btn_032_108
- Text = ""
- TextAlign = MiddleRight
- TooltipOnTtp = Annulla
- DialogResult = Cancel
- Per l'intera Form
- Name = FrmInputBox
- AcceptButton = btnOk
- CancelButton = btnCancel
- Text = Input Dati
Disponiamo i controlli come nella figura e andiamo a guardare che cosa mettiamo nel codice della form:
using System; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; namespace TAndT.UI { public partial class FrmInputBox : Form { private static readonly string mClassName = System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;Imports System Imports System.ComponentModel Imports System.Drawing Imports System.Windows.Forms Public Class FrmInputBox Private Shared ReadOnly mClassName As String = _ System.Reflection.MethodBase.GetCurrentMethod.ReflectedType.NameLe direttive Using (Imports) di default e la solita variabile statica per la gestione delle eccezioni.
public FrmInputBox() { InitializeComponent(); this.StartPosition = FormStartPosition.CenterParent; }Public Sub New() InitializeComponent() Me.StartPosition = FormStartPosition.CenterParent End SubIl costruttore, con il metodo base del designer per l'inizializzazione dei controlli, e un'istruzione che abbiamo aggiunto affinché la finestra venga visualizzata automaticamente al centro della sua finestra Parent, ovvero la finestra da cui verrà chiamata.
public string LabelInput { get { return this.lblInput.Text; } set { this.lblInput.Text = value; } } public string TxtInput { get { return this.txtInput.Text; } set { this.txtInput.Text = value; } }Public Property LabelInput() As String Get Return Me.lblInput.Text End Get Set(ByVal value As String) Me.lblInput.Text = value End Set End Property Public Property TxtInput() As String Get Return Me.txtImput.Text End Get Set(ByVal value As String) Me.txtImput.Text = value End Set End PropertyDue proprietà che espongono la casella di testo (per inizializzare e poi leggere il dato inserito dall'utente - notare il nome diverso tra C# e VB, dato che quest'ultimo non distingue tra maiuscole e minuscole per i nomi di membri come controlli e proprietà) e l'etichetta con il testo che descrive il dato che l'utente deve inserire.
public static FrmInputBox BuildMe(Icon pIcon, string pTitle, string pText, string pLabel) { try { FrmInputBox frm = new FrmInputBox(); frm.Icon = pIcon; frm.Text = pTitle; frm.TxtInput = pText; frm.LabelInput = pLabel; return (frm); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Shared Function BuildMe(ByVal icon As Icon, ByVal title As String, _ ByVal text As String, ByVal label As String) As FrmInputBox Try Dim frm As New FrmInputBox frm.Icon = icon frm.Text = title frm.TxtInput = text frm.LabelInput = label Return frm Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionIl metodo BuildMe
Terminiamo la nostra form creando un metodo statico che, ricevuti i parametri base per la sua gestione, crea un'istanza della stessa classe form restituendola al chiamante. Questa funzione serve perché, per chiamare un'Inputbox, sarà difficilmente necessario modificare parametri diversi da icona, titolo, testo iniziale ed etichetta, quindi evitiamo del codice ripetitivo all'interno delle classi che utilizzeranno questa form.Per testare il funzionamento della nostra InputBox, torniamo alla FrmMain ed implementiamo il codice per il metodo relativo al menu precedentemente inserito:
private void inputBoxToolStripMenuItem_Click(object sender, EventArgs e) { FrmInputBox frm = FrmInputBox.BuildMe(this.Icon, "Test inputbox", "Modificare questo", "Indicare il valore:"); frm.ShowDialog(); if (frm.DialogResult == DialogResult.OK) { Warnings.Avviso(string.Format("Il testo è {0}", frm.TxtInput)); } else { Warnings.Avviso("input annullato"); } }Private Sub InputBoxToolStripMenuItem_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles InputBoxToolStripMenuItem.Click Dim frm As FrmInputBox = FrmInputBox.BuildMe(Me.Icon, "Test inputbox", "Modificare questo", _ "Indicare il valore:") frm.ShowDialog() If frm.DialogResult = Windows.Forms.DialogResult.OK Then Warnings.Avviso(String.Format("Il testo è {0}", frm.TxtInput)) Else Warnings.Avviso("input annullato") End If End SubCome possiamo notare, utilizziamo il metodo statico BuildMe per generare la nostra form e la mostriamo con ShowDialog. La chiamata al metodo ShowDialog non è stata inserita direttamente nel metodo BuildMe, perché in questo modo permettiamo a chi userà la form di chiamarla sia in modalità Dialog che in modalità normale, dando modo ai programmatori (tra cui noi) di lavorare più liberamente.
Lanciando l'applicazione e facendo click sul Menu InputBox, otterremo quanto mostrato in queste immagini:
la Inputbox,
il risultato alla pressione di OK
il risultato alla pressione di Cancel.![]()
![]()
Presumiamo vi siate accorti del fatto che non abbiamo scritto alcun codice per gestire gli eventi Click dei due pulsanti in modo da ottenere il comportamento corretto. Questo è dovuto al fatto che per farli reagire correttamente è bastato indicare alla form qual è il pulsante di conferma (AcceptButton) e quale quello di annullamento (CancelButton) per fare in modo che il valore di ritorno da essi predisposto sia corretto, cioè il valore delle rispettive proprietà DialogResult.
Il fatto che ognuno dei pulsanti abbia un valore nella property DialogResult fa anche in modo che entrambi i pulsanti scatenino automaticamente la chiusura della form. Non abbiamo messo alcun tipo di controllo sull'input, né alcun blocco o formattazione, però potrebbe anche darsi che in seguito faremo delle ulteriori implementazioni su questa form, che probabilmente useremo in vari luoghi della nostra User Interface.
La classe FrmInputCombo
Dal progetto TAndT.UI, facciamo click con il tasto destro e selezioniamo Add>Windows Form> Chiamiamo la form FrmInputCombo e aggiungiamo i controlli e modifichiamo le loro proprietà come indicato qui sotto:
- Un TooltipProvider
- Name = ttp
- Una Combobox
- Name = cboInput
- Anchor = Top, Left, Right
- TooltipOnTtp = "Selezionare un valore"
- Una Label
- Name = lblInput
- AutoSize = true
- Text = Testo Input
- Un Button
- Name = btnOk
- Anchor = Bottom
- Image = TAndT.UI.Properties.Resources.btn_032_107 (selezioniamola dalle risorse)
- Text = ""
- TextAlign = MiddleRight
- TooltipOnTtp =Conferma e chiudi
- DialogResult = OK
- Un secondo Button
- Name = btnCancel
- Anchor = Bottom
- Image =TAndT.UI.Properties.Resources.btn_032_108
- Text = ""
- TextAlign = MiddleRight
- TooltipOnTtp = Annulla e chiudi
- DialogResult = Cancel
- Per l'intera Form
- Name = FrmInputCombo
- AcceptButton = btnOk
- CancelButton = btnCancel
- Text = Input Dati
Disponiamo i controlli come nella figura e andiamo a scrivere il codice della form:
using System; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; namespace TAndT.UI { public partial class FrmInputCombo : Form { private static readonly string mClassName = System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;Imports System Imports System.ComponentModel Imports System.Drawing Imports System.Windows.Forms Public Class FrmInputCombo Private Shared ReadOnly mClassName As String = _ System.Reflection.MethodBase.GetCurrentMethod.ReflectedType.NameLe direttive Using (Imports) e la variabile statica uguali a quelle della form sorella.
public FrmInputCombo(string[] pItemsList) { InitializeComponent(); this.cboInput.Items.AddRange(pItemsList); this.StartPosition = FormStartPosition.CenterParent; }Public Sub New(ByVal itemsList As String()) InitializeComponent() Me.cboInput.Items.AddRange(itemsList) Me.StartPosition = FormStartPosition.CenterParent End SubIl costruttore, con il metodo base del designer per l'inizializzazione dei controlli, e l'istruzione che predispone la finestra per essere visualizzata automaticamente al centro della sua finestra Parent. Inoltre, questo costruttore prevede che gli sia passato obbligatoriamente un array di stringhe, che formerà la lista dei valori ammissibili.
public string LabelInput { get { return this.lblInput.Text; } set { this.lblInput.Text = value; } } public string TxtInput { get { return this.cboInput.Text; } set { this.cboInput.Text = value; } }Public Property LabelInput() As String Get Return Me.lblInput.Text End Get Set(ByVal value As String) Me.lblInput.Text = value End Set End Property Public Property TxtInput() As String Get Return Me.cboInput.Text End Get Set(ByVal value As String) Me.cboInput.Text = value End Set End PropertyLe property sono uguali a quelle della form InputBox, fatto salvo che il valore restituito è il Text della Combobox. Abbiamo infatti previsto una semplice Combobox contenente solo una lista di valori, non una collezione di coppie key - value come è di solito per le Combobox associate a dati.
private void FrmInputCombo_Load(object sender, EventArgs e) { this.cboInput.SelectedItem = 0; }Private Sub FrmInputCombo_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load Me.cboInput.SelectedItem = 0 End SubAl caricamento della form, selezioniamo il primo valore della Combobox.
private void FrmInputCombo_FormClosing(object sender, FormClosingEventArgs e) { if (e.CloseReason != CloseReason.ApplicationExitCall && e.CloseReason != CloseReason.MdiFormClosing && e.CloseReason != CloseReason.WindowsShutDown && e.CloseReason != CloseReason.TaskManagerClosing) { if (DialogResult == DialogResult.OK) { if (this.cboInput.SelectedIndex == -1 && this.cboInput.Text.Trim().Length > 0) { Warnings.Avviso("Il valore indicato non è valido"); e.Cancel = true; } } } }Private Sub FrmInputCombo_FormClosing(ByVal sender As Object, _ ByVal e As System.Windows.Forms.FormClosingEventArgs) _ Handles Me.FormClosing If e.CloseReason <> CloseReason.ApplicationExitCall AndAlso _ e.CloseReason <> CloseReason.MdiFormClosing AndAlso _ e.CloseReason <> CloseReason.WindowsShutDown AndAlso _ e.CloseReason <> CloseReason.TaskManagerClosing Then If DialogResult = Windows.Forms.DialogResult.OK Then If Me.cboInput.SelectedIndex = -1 AndAlso _ Me.cboInput.Text.Trim.Length > 0 Then Warnings.Avviso("Il valore indicato non è valido") e.Cancel = True End If End If End If End SubAlla chiusura della form, verifichiamo che la chiusura non sia stata richiesta da un evento di sistema o da una forzata chiusura. Se la chiusura è richiesta da utente e se è stato premuto il tasto OK, permettiamo la chiusura della form solo se è stato selezionato un valore valido dalla Combobox.
public static FrmInputCombo BuildMe(Icon pIcon, string pTitle, string pText, string pLabel, string[] pItemsList) { try { FrmInputCombo frm = new FrmInputCombo(pItemsList); frm.Icon = pIcon; frm.Text = pTitle; frm.TxtInput = pText; frm.LabelInput = pLabel; return (frm); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Shared Function BuildMe(ByVal icon As Icon, ByVal title As String, ByVal text As String, _ ByVal label As String, ByVal itemsList As String()) As FrmInputCombo Try Dim frm As New FrmInputCombo(itemsList) frm.Icon = icon frm.Text = title frm.TxtInput = text frm.LabelInput = label Return frm Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionInfine, anche per questa form generiamo il metodo statico BuildMe, che costruisce ed istanzia la form chiedendo solo i dati necessari all'utente, in modo da evitare codice ripetitivo nelle form dove questa form ausiliaria verrà utilizzata.
Per testare il funzionamento della nostra InputBox, torniamo alla FrmMain ed implementiamo il codice per il metodo relativo al menu precedentemente inserito:
private void inputComboToolStripMenuItem_Click(object sender, EventArgs e) { FrmInputCombo frm = FrmInputCombo.BuildMe(this.Icon, "Test InputCombo", "Selezionare un valore", "Scegliere il valore che preferite", new string[] { "Bianco", "Rosso", "Verde", "Giallo", "Blu", "Arancio", "Rosa" }); frm.ShowDialog(); if (frm.DialogResult == DialogResult.OK) { Warnings.Avviso(string.Format("Il valore selezionato è {0}", frm.TxtInput)); } else { Warnings.Avviso("input combo annullato"); } }Private Sub InputComboToolStripMenuItem_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles InputComboToolStripMenuItem.Click Dim frm As FrmInputCombo = FrmInputCombo.BuildMe(Me.Icon, "Test InputCombo", _ Selezionare un valore", "Scegliere il valore che preferite", _ New String() {"Bianco", "Rosso", "Verde", "Giallo", "Blu", "Arancio", "Rosa"}) frm.ShowDialog() If frm.DialogResult = Windows.Forms.DialogResult.OK Then Warnings.Avviso(String.Format("Il valore selezionato è {0}", frm.TxtInput)) Else Warnings.Avviso("input combo annullato") End If End SubCome possiamo notare, utilizziamo il metodo statico BuildMe per generare la nostra form, esattamente come per l'InputBox, però in questo caso generiamo anche l'array di stringhe contenente i valori che l'utente deve selezionare dalla combobox.
Lanciando l'applicazione e facendo click sul Menu InputBox, otterremo quanto mostrato nelle immagini:
Se scriviamo nel testo della combobox un valore non valido e premiamo OK, appare il messaggio di avviso e non possiamo chiudere la finestra. Se selezioniamo un valore valido, il valore viene trasmesso alla form chiamante. Se chiudiamo con il tasto Annulla otteniamo il messaggio di input annullato.
Armati di queste due nuove form, proseguiamo iniziando lo sviluppo della terza form ausiliaria.
La classe FrmFileSpecification
La terza form che svilupperemo, è una form accessoria che ci serve per chiedere all'utente i dati necessari a generare una FileSpecification. Per creare questo tipo di oggetto, costruiamo una form 'ad hoc' perché lo stesso oggetto viene utilizzato sia per la definizione dei file dati che per il file di log, pertanto sarebbe sciocco creare due serie di controlli identici sulla form per svolgere un unico lavoro.Questa form è una form specializzata, non una form generica, pertanto la generiamo nel progetto UsingSqlServer. Quindi, tasto destro sul progetto UsingSqlServer, Add> Windows Form> e chiamiamo la nostra form FrmFileSpecification.
Vediamo quali controlli abbiamo allocato e come sono configurati:
- La Form
- Name=FrmFileSpecification
- ControlBox = false
- FormBorderStyle = FixedSingle
- Text = Inserimento modifica Specifica file
- Un Tooltip
- Name = ttp
- Un ToolStrip
- Name=TbrTools
- Text = Tools
- Items
- Un ToolstripButton
- Name = tooOk
- DisplayStyle = Image
- Image = dalle resources btn_032_107 (Import...)
- Un secondo ToolstripButton
- Name = tooCancel
- DisplayStyle = Image
- Image = dalle resources btn_032_108
- Una OpenFileDialog
- Name = ofd
- CheckFileExists = false
- CheckPathExists = false
- Title = Selezionare il file
- 5 Label
- tutte:
- AutoSize = false
- TextAlign = MiddleRight
- Name = lblName
- Text = Nome:
- Name = lblFileName
- Text = Nome File:
- Name = lblSize
- Text = Dimensione Iniziale:
- Name = lblMaxSize
- Text = Dimensione massima:
- Name = lblGrowth
- Text = Crescita:
- Una Textbox
- Name = txtName
- Text = ""
- Tooltip on ttp =
- Indicare il nome logico del file Es. MyDatabase, Northwind, Pubs,TAndTContacts.
- Una Textbox
- Name = txtFileName
- Text = ""
- Tooltip on ttp =
- Indicare il percorso completo ove dovrà essere memorizzato il file
Es.: C:\sql.dir\data\TandTContacts.mdf
C:\sql.dir\data\TandTContacts_ext.ndf
C:\sql.dir\data\TandTContactsLog.ldf- Un Button
- Name = btnSearchPath
- Text = "..."
- Tooltip on ttp =
- Premere qui per selezionare su quale cartella generare il file
il valore selezionato sarà riportato nella casella nome file.
Si tratta esclusivamente di un tool per selezionare in modo
facile la cartella ove inserire il file.- Un NumericUpDown
- Name = txtSize
- Increment = 10
- Maximum = 100000
- Minimum = 0
- Value = 0
- Tooltip on ttp =
- Indicare la dimensione iniziale del file
Es.: 10, 50, 100- Una Combobox
- Name = cboSize
- Tooltip on ttp =
- Indicare in quale misura è indicata la dimensione iniziale
Es: MB- Un NumericUpDown
- Name = txtMaxSize
- Increment = 10
- Maximum = 100000
- Minimum = 0
- Value = 0
- Tooltip on ttp =
- Indicare la dimensione massima che è consentito raggiungere al file.
E' possibile usare Unlimited, ma è consigliato specificare una dimensione
adeguata ad evitare che il database possa per qualsiasi motivo
riempire completamente il disco ove è contenuto, creando poi
problemi per poter gestire l'intervallo di tempo necessario a
passare ad un unità disco più grande.
Di solito si lascia un paio di Gigabyte sotto il massimo possibile come
somma delle dimensioni massime di tutti i file dati.
- Una Combobox
- Name = cboMaxSize
- Tooltip on ttp =
- Indicare l'Unità di misura in cui calcolare la dimensione massima.
- Un NumericUpDown
- Name = txtGrowth
- Increment = 10
- Maximum = 100000
- Minimum = 0
- Value = 0
- Tooltip on ttp =
- Modalità di crescita del file, quando la dimensione iniziale impostata
sarà completamente riempita dai dati, SQL Server allocherà tanto
spazio a disposizione del database, quanto indicato da questo parametro,
che può essere una misura fisica o una percentuale sulla dimensione del database.
E' opportuno dimensionare questo valore affinché il database non debba aumentare
la propria dimensione troppo spesso per evitare cali di prestazioni e
eccessiva frammentazione dei file dati su disco.- Una Combobox
- Name = cboGrowth
- Tooltip on ttp
- Unità di misura da usare per calcolare il valore della crescita
del database, può essere: KB, MB, GB, TB o %.Come possiamo vedere, la form non è banale nella sua forma a design, e rappresenta in forma grafica il contenuto della classe Entity FileSpecification. Facciamo notare come questa form sia diversa dalle due form di dialogo che abbiamo generato in precedenza. Questo è voluto, sia per differenziare il look delle form specializzate del nostro progetto, sia per dimostrare che vi sono molti modi per generare una interfaccia utente, in modo tale da permettervi poi di scegliere quello che considerate più consono all'uso dei vostri utenti.
Esaminiamo ora il codice:using System; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Text; using System.Windows.Forms; using TAndT.Data.Entities; using TAndT.UI; using TAndT.UI.Forms;Imports System Imports System.ComponentModel Imports System.Data Imports System.Drawing Imports System.IO Imports System.Text Imports System.Windows.Forms Imports TAndT.Data.Entities Imports TAndT.UI Imports TAndT.UI.FormsLe direttive Using (Imports): oltre a quelli standard, importiamo anche tre dei namespace delle nostre librerie di base.
namespace TAndT.UsingSqlServer { public partial class FrmFileSpecification : Form { private static readonly string mClassName = System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name; FileSpecification mFileSpec;Public Class FrmFileSpecification Private Shared ReadOnly mClassName As String = _ System.Reflection.MethodBase.GetCurrentMethod.ReflectedType.Name private mFileSpec as FileSpecificationI campi privati, la solita mClassName e una FileSpecification che ci permetterà di passare i dati dalle strutture per la generazione del database a questa form e viceversa, attraverso la proprietà corrispondente:
public FileSpecification FileSpec { get { return mFileSpec; } set { mFileSpec = value; LoadDataFromFileSpec(); } }Public Property FileSpec() As FileSpecification Get Return mFileSpec End Get Set(ByVal value As FileSpecification) mFileSpec = value LoadDataFromFileSpec() End Set End PropertyLa proprietà che espone il campo FileSpecification e che, nella parte set, dopo la modifica del campo chiama il metodo che aggiorna il contenuto dei controlli della form:
private void LoadDataFromFileSpec() { this.cboSize.Text = FileSpec.Size.SizeUmi; this.txtSize.Value = FileSpec.Size.SizeValue; this.cboMaxSize.Text = FileSpec.MaxSize.SizeUmi; this.txtMaxSize.Value = FileSpec.MaxSize.SizeValue; this.cboGrowth.Text = FileSpec.Growth.SizeUmi; this.txtGrowth.Value = FileSpec.Growth.SizeValue; this.txtFileName.Text = FileSpec.FileName; this.txtName.Text = FileSpec.Name; }Private Sub LoadDataFromFileSpec() Me.cboSize.Text = FileSpec.Size.SizeUmi Me.txtSize.Value = FileSpec.Size.SizeValue Me.cboMaxSize.Text = FileSpec.MaxSize.SizeUmi Me.txtMaxSize.Value = FileSpec.MaxSize.SizeValue Me.cboGrowth.Text = FileSpec.Growth.SizeUmi Me.txtGrowth.Value = FileSpec.Growth.SizeValue Me.txtFileName.Text = FileSpec.FileName Me.txtName.Text = FileSpec.Name End SubIl metodo di caricamento dei dati dalla proprietà FileSpec ai controlli della form, come possiamo vedere si tratta di una semplice assegnazione dei dati.
public FrmFileSpecification() { InitializeComponent(); this.mFileSpec = new FileSpecification(string.Empty, string.Empty); this.txtName.Text = string.Empty; this.txtFileName.Text = string.Empty; this.txtSize.Minimum = 0; this.txtSize.Maximum = int.MaxValue; this.txtSize.Value = 0; this.txtMaxSize.Minimum = 0; this.txtMaxSize.Maximum = int.MaxValue; this.txtMaxSize.Value = 0; this.txtGrowth.Minimum = 0; this.txtGrowth.Maximum = int.MaxValue; this.txtGrowth.Value = 0; }Public Sub New() InitializeComponent() Me.mFileSpec = New FileSpecification(String.Empty, String.Empty) Me.txtName.Text = String.Empty Me.txtFileName.Text = String.Empty Me.txtSize.Minimum = 0 Me.txtSize.Maximum = Integer.MaxValue Me.txtSize.Value = 0 Me.txtMaxSize.Minimum = 0 Me.txtMaxSize.Maximum = Integer.MaxValue Me.txtMaxSize.Value = 0 Me.txtGrowth.Minimum = 0 Me.txtGrowth.Maximum = Integer.MaxValue Me.txtGrowth.Value = 0 End SubIl costruttore della form, che inizializza i componenti e azzera i campi dei controlli.
private void FrmFileSpecification_Load(object sender, EventArgs e) { try { this.cboSize.Items.AddRange(UsingSqlServerConfig.SizeUmiList); this.cboMaxSize.Items.AddRange(UsingSqlServerConfig.MaxSizeUmiList); this.cboGrowth.Items.AddRange(UsingSqlServerConfig.GrowthUmiList); if (this.txtFileName.Text.Trim().Length == 0) { this.txtFileName.Text = Path.Combine(UsingSqlServerConfig.DbFilesFolder, this.txtName.Text); } } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub FrmFileSpecification_Load(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Me.Load Try Me.cboSize.Items.AddRange(UsingSqlServerConfig.SizeUmiList) Me.cboMaxSize.Items.AddRange(UsingSqlServerConfig.MaxSizeUmiList) Me.cboGrowth.Items.AddRange(UsingSqlServerConfig.GrowthUmiList) If Me.txtFileName.Text.Trim.Length = 0 Then Me.txtFileName.Text = Path.Combine(UsingSqlServerConfig.DbFilesFolder, Me.txtName.Text) End If Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubL'event handler del caricamento della form, che provvede a caricare nelle combobox le tre liste dei valori ammessi dalle unità di misura. Con le StringCollection, si sarebbe dovuto passare attraverso la copia degli elementi in un array di String.
Se ben ricordate, abbiamo inserito nella struttura dei setting di configurazione dell'applicazione una stringa ove memorizzare ciascuna delle tre liste delle unità di misura, come potete vedere nella figura accanto, che mostra la FrmConfig.
Così possiamo osservare non solo i setting per le unità di misura della specifica di file, ma anche il nuovo setting per la cartella standard dei file di database.
Nell'event handler del caricamento della form, oltre al riempimento delle combobox, viene controllato se il percorso di destinazione è vuoto, nel qual caso viene generato un path di base per facilitare l'operatore, usando il nome logico del file e la cartella indicata nel setting DbFilesFolder.
private void btnSearchPath_Click(object sender, EventArgs e) { try { ofd.Title = "Selezione destinazione file dati"; ofd.Multiselect = false; ofd.FileName = string.Empty; ofd.Filter = "All Files (*.*)|*.*"; ofd.FilterIndex = 0; if (ofd.ShowDialog() == DialogResult.OK) { this.txtFileName.Text = ofd.FileName; FileInfo fInfo = new FileInfo(ofd.FileName); this.txtName.Text = fInfo.Name.Remove(fInfo.Name.IndexOf('.')); } } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub btnSearchPath_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnSearchPath.Click Try ofd.Title = "Selezione destinazione file dati" ofd.Multiselect = False ofd.FileName = String.Empty ofd.Filter = "All Files (*.*)|*.*" ofd.FilterIndex = 0 If ofd.ShowDialog = DialogResult.OK Then Me.txtFileName.Text = ofd.FileName Dim fInfo As New FileInfo(ofd.FileName) Me.txtName.Text = fInfo.Name.Remove(fInfo.Name.IndexOf("."c)) End If Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubIl gestore dell'evento Click del pulsante di selezione di percorso. In esso si mostra come utilizzare la OpenFileDialog per selezionare la cartella di destinazione del nostro file di database: assegnamo alle proprietà i valori che ci servono per indicare al controllo come comportarsi (un titolo, il divieto di selezionare più files, il valore iniziale vuoto, un semplice "Filtro" che permette di vedere tutti i file), quindi usiamo il metodo ShowDialog che istanzia la form e ci permette di usarla per decidere dove mettere il file dati. Il nome del file viene poi copiato nella Textbox e, senza estensione, nella casella del nome logico del file.
private void tooOk_Click(object sender, EventArgs e) { try { this.FileSpec.Name = this.txtName.Text; this.FileSpec.FileName = this.txtFileName.Text; this.FileSpec.Size.SizeValue = Convert.ToInt32(this.txtSize.Value); this.FileSpec.Size.SizeUmi = this.cboSize.Text; this.FileSpec.MaxSize.SizeValue = Convert.ToInt32(this.txtMaxSize.Value); this.FileSpec.MaxSize.SizeUmi = this.cboMaxSize.Text; this.FileSpec.Growth.SizeValue = Convert.ToInt32(this.txtGrowth.Value); this.FileSpec.Growth.SizeUmi = this.cboGrowth.Text; this.DialogResult = DialogResult.OK; } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub TooOk_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles TooOk.Click Try Me.FileSpec.Name = Me.txtName.Text Me.FileSpec.FileName = Me.txtFileName.Text Me.FileSpec.Size.SizeValue = Convert.ToInt32(Me.txtSize.Value) Me.FileSpec.Size.SizeUmi = Me.cboSize.Text Me.FileSpec.MaxSize.SizeValue = Convert.ToInt32(Me.txtMaxSize.Value) Me.FileSpec.MaxSize.SizeUmi = Me.cboMaxSize.Text Me.FileSpec.Growth.SizeValue = Convert.ToInt32(Me.txtGrowth.Value) Me.FileSpec.Growth.SizeUmi = Me.cboGrowth.Text Me.DialogResult = DialogResult.OK Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubLa gestione del tasto OK posto sulla barra. I valori dei controlli vengono copiati nelle rispettive proprietà dell'oggetto FileSpecification, e il valore DialogResult della form viene posto a OK, chiudendo così la form.
Come potete notare, i valori dei controlli vengono passati alla FileSpecification senza alcun controllo di validazione sui dati.
Non è una dimenticanza, ma una scelta precisa, perché le entità FileSpecification hanno già il necessario a validare il proprio contenuto al loro interno. Una ulteriore implementazione dei controlli di validazione la lasciamo ai nostri lettori, suggerendo di usare una struttura di transito per non rovinare i dati di quella ufficiale e di utilizzare l'IsValid per verificare la validità degli oggetti.private void tooCancel_Click(object sender, EventArgs e) { this.DialogResult = DialogResult.Cancel; }Private Sub TooCancel_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles TooCancel.Click Me.DialogResult = Windows.Forms.DialogResult.Cancel End SubLa gestione del tasto Annulla, molto semplice, che si limita ad impostare la proprietà DialogResult della form come serve.
public static FrmFileSpecification BuildMe(string pFrmName, Icon pIcon) { try { FrmFileSpecification frm = new FrmFileSpecification(); frm.Icon = pIcon; return (frm); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Shared Function BuildMe(ByVal frmName As String, ByVal icon As Icon) _ As FrmFileSpecification Try Dim frm As New FrmFileSpecification frm.Icon = icon Return frm Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionInfine, il metodo BuildMe, che genera un istanza della classe form, da usarsi per evitare di scrivere codice ripetitivo. Anche in questo caso, la form verrà poi trattata come form di dialogo. C'è una sola differenza rispetto alle versioni relative a InputBox e InputCombo: qui passiamo un nome per la form, che ci permetterà, se vogliamo, di istanziare più form diverse di questo tipo e utilizzare comunque il metodo VediSeIlFormEAperto per verificare se sono già aperte. Ma al momento ci limitiamo a lavorare in modo più semplice.
Bene, abbiamo concluso le form di contorno, ora passiamo alla form più corposa, succosa, e soprattutto complessa...
La classe FrmCreateDatabase
Questa form permetterà di effettuare le seguenti operazioni:
- Definire la struttura di un database.
- Salvarla su file XML.
- Caricarla da file XML.
- Modificarla ed aggiornarla.
- Usarla per produrre il codice SQL per la generazione del database.
- Usarla per generare il database.
Dopo aver aggiunto una form di nome FrmCreateDatabase al progetto UsingSqlServer, inseriamo i seguenti controlli, impostandone le proprietà come indicato:
- Il Tooltip
- Name = ttp
- La OpenFileDialog
- Name = ofd
- La SaveFileDialog
- Name = sfd
- Il MenuStrip
- Name = mnuBase
- AllowMerge = false
- Text = Menu Crea Database
- Gli Items
- Name=creaDatabaseToolStripMenuItem
- Text = &Crea Database
- Items (come è noto, scrivere il Text fa ottenere automaticamente il Name)
- Name = nuovaSpecificaToolStripMenuItem
- Text = &Nuova Specifica
- Name =visualizzaScriptSQLToolStripMenuItem
- Text = &Visualizza Script SQL
- Name = creaDbToolStripMenuItem
- Text = &Crea DB (dopo la creazione, modificate in &Crea Database)
- Name = importaSpecificaDatabaseToolStripMenuItem
- Text = &Importa Specifica Database
- Name = esportaSpecificaDatabaseToolStripMenuItem
- Text = &Esporta Specifica Database
- L'Imagelist
- Name = imlButtons
- ColorDepth = Depth32Bit
- ImageSize = 16;16
- TransparentColor = Transparent
- Images
- btn_032_109 (ricordarsi di cambiare il nome immagine togliendo l'estensione dopo averla caricata dalla solita cartella Resources fornita col sorgente a corredo)
- btn_032_110
- La Form
- Name=FrmCreateDatabase
- MainMenuStrip = mnuBase
- MinimumSize = 495; 464
- Text = Generazione/Modifica Database
- Un Panel (contenitore per le textbox in cima alla form)
- Name = pnlTopContainer
- BackColor = 255; 224; 192
- Dock = top
- Size = 487; 67
che contiene:
- Una Label
- Name = lblDbName
- AutoSize = False
- BackColor = Transparent
- Text = Nome Database:
- TextAlign= MiddleRight
- Una Label
- Name=lblCollation
- AutoSize = False
- BackColor = Transparent
- Text = Nome Collation:
- TextAlign= MiddleRight
- Una Textbox
- Name = txtDbName
- Tooltip on ttp =
- Digitare il nome del database, senza spazi, trattini, punti, caratteri strani.
Solo lettere e numeri e underscore. Es. My_Database Data_4Fun- Una Textbox
- Name = txtCollation
- Tooltip on ttp = Lasciare vuoto per la collation di default,
altrimenti digitare il nome esatto della collation desiderata- Uno Splitter (separa il pannello superiore da quelli inferiori)
- Name = splTop
- BackColor = RoyalBlue
- Dock = Top
- Size = 487;3
- Un Panel (Contenitore delle liste in mezzo alla form)
- Name = pnlListContainer
- Dock = Top
- Size = 487; 183
che contiene:- Un Panel (contiene i controlli per i filegroup dentro al pannello centrale sul lato sinistro)
- Name = pnlGroups
- BackColor = 192; 192; 255
- Dock = Left
- Size = 163; 179
che contiene:
- Una Label (titolo box a sinistra)
- Name = lblTtlListGroups
- AutoSize = False
- BackColor = 128; 128; 255
- BorderStyle = FixedSingle
- Dock = Top
- Text = FileGroups
- TextAlign = MiddleLeft
- Un Button (Genera filegroup)
- Name = btnNewFileGroup
- ImageList = imlButtons
- ImageKey = btn_032_109
- ImageAlign = MiddleLeft
- Text = Nuovo
- TextAlign=MiddleRight
- Size = 72; 26
- Tooltip on ttp = Genera un nuovo filegroup
- Un Button (Cancella filegroup)
- Name = btnDeleteFileGroup
- ImageList = imlButtons
- ImageKey = btn_032_110
- ImageAlign = MiddleLeft
- Text = Cancella
- TextAlign=MiddleRight
- Size = 72; 26
- Tooltip on ttp = Cancella il filegroup correntemente selezionato sulla lista dei filegroup.
- Una Listbox (Lista dei filegroup)
- Name = lstGroup
- Dock = Bottom
- BackColor =192; 192; 255
- Tooltip on ttp = Lista dei Filegroup in cui sono divisi i dati del database. Doppio Click per modificare (salvo Primary)
- Un Panel (contiene i controlli per i data file dentro al pannello centrale in mezzo)
- Name = pnlDataFiles
- BackColor = 192; 255; 192
- Dock = Fill
- Size = 161; 179
che contiene:
- Una Label (titolo box al centro)
- Name = lblTtlListDataFiles
- AutoSize = False
- BackColor = 128; 255; 128
- BorderStyle = FixedSingle
- Dock = Top
- Text = File Dati
- TextAlign = MiddleLeft
- Un Button (Genera file dati)
- Name = btnNewDataFile
- ImageList = imlButtons
- ImageKey = btn_032_109
- ImageAlign = MiddleLeft
- Text = Nuovo
- TextAlign=MiddleRight
- Size = 72; 26
- Tooltip on ttp = Aggiunge un file dati al filegroup selezionato correntemente. Se non è selezionato nulla, viene aggiunto al PRIMARY
- Un Button (Cancella file specification)
- Name = btnDeleteDataFile
- ImageList = imlButtons
- ImageKey = btn_032_110
- ImageAlign = MiddleLeft
- Text = Cancella
- TextAlign = MiddleRight
- Size = 72; 26
- Tooltip on ttp = Cancella il file dati correntemente selezionato nella lista.
- Una Listbox (Lista dei file dati del filegroup selezionato)
- Name = lstDataFiles
- Dock = Bottom
- BackColor =192; 255; 192
- Tooltip on ttp = Lista dei File di ciascuno dei filegroup. Doppio Click per modificare.
- Un Panel (contiene i controlli per i log file dentro al pannello centrale a destra)
- Name = pnlLogFiles
- BackColor = 255; 255; 192
- Dock = Right
- Size = 159; 179
che contiene:
- Una Label (titolo box a destra)
- Name = lblTtlListLogFiles
- AutoSize = False
- BackColor = 255; 255; 128
- BorderStyle = FixedSingle
- Dock = Top
- Text = File Log
- TextAlign = MiddleLeft
- Un Button (Genera file log)
- Name = btnNewLogFile
- ImageList = imlButtons
- ImageKey = btn_032_109
- ImageAlign = MiddleLeft
- Text = Nuovo
- TextAlign = MiddleRight
- Size = 72; 26
- Tooltip on ttp = Aggiunge un file di log alla lista.
- Un Button (Cancella specifica file di log)
- Name = btnDeleteLogFile
- ImageList = imlButtons
- ImageKey = btn_032_110
- ImageAlign = MiddleLeft
- Text = Cancella
- TextAlign=MiddleRight
- Size = 72; 26
- Tooltip on ttp = Cancella il file di log correntemente selezionato nella lista
- Una Listbox (Lista dei file di log)
- Name = lstLogFiles
- Dock = Bottom
- BackColor =255; 255; 192
- Tooltip on ttp = Lista dei files del Log del database. Doppio Click per modificare.
- Uno Splitter (separa il pannello centrale da quello inferiore)
- Name = splBottom
- BackColor = RoyalBlue
- Dock = Top
- Size = 487;3
- Una Textbox (Visualizza i risultati e fa da pannello inferiore)
- Name = txtResult
- Backcolor = 255; 192; 255
- Dock = fill
- Font = Arial; 10pt
- Forecolor = Navy
- Multiline = true
- Scrollbars = vertical
Lo schema da utilizzare per posizionare i controlli è quello in cui li abbiamo ordinati; inserendoli nell'ordine in cui sono elencati, la form assume in modo semplice la configurazione della figura. Le immagini per la ImageList si trovano nella cartella resources del progetto, L'ImageList è stata utilizzata per ridimensionare automaticamente le immagini, che altrimenti i controlli button non sono capaci di modificare, e per mostrare uno dei vari modi in cui utilizzare immagini all'interno di un controllo. Fatto il primo dei tre pannelli centrali, si può copiarlo e incollarlo per ottenere gli altri due.
Ed ora passiamo al codice che sta dietro alla form:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using TAndT.UI; using TAndT.UI.Forms; using TAndT.Data.Entities; using System.IO; using TAndT.Base; using TAndT.Data;Imports System Imports System.Collections.Generic Imports System.ComponentModel Imports System.Data Imports System.Drawing Imports System.Text Imports System.Windows.Forms Imports TAndT.UI Imports TAndT.UI.Forms Imports TAndT.Data.Entities Imports System.IO Imports TAndT.Base Imports TAndT.DataLa definizione dei namespace importati, come sempre per abbreviare i nomi delle classi utilizzate.
namespace TAndT.UsingSqlServer { public partial class FrmCreateDatabase : Form { private static readonly string[] COLLATION_LIST = new string[] { COLLATION_Default, "Latin1_General_CI_AS", "SQL_Latin1_General_CP1_CI_AS" }; private static readonly string mClassName = System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name; private DatabaseGenerator dbGenerator; } }Public Class FrmCreateDatabase Private Shared ReadOnly mClassName As String = _ System.Reflection.MethodBase.GetCurrentMethod.ReflectedType.Name Private Shared ReadOnly COLLATION_LIST As String() = New String() _ {COLLATION_Default, _ "Latin1_General_CI_AS", _ SQL_Latin1_General_CP1_CI_AS"} Private dbGenerator As DatabaseGeneratorLa struttura e i campi della classe. Oltre la solita mClassName, la lista collation (costruita anche con una costante di cui nel seguito) e il DatabaseGenerator che ci fornisce la struttura dati ed i metodi necessari alla gestione della generazione dei database.
private const string COLLATION_Default = "Default"; private const string DBNAME_Default = "Nuovo_Db"; public const string DEF_DataFile = "DataFile"; public const string DEF_ExportFile = "DbInfo_{0}.xml"; public const string DEF_LogFile = "LogFile"; private const string DFG_PRIMARY = "PRIMARY"; private const string FMP_FileLdb = "{0}.ldb"; private const string FMP_FileMdb = "{0}.mdb"; private const string FMP_LogName = "{0}_log"; private const string IBX_LBLCollation = "Indicare la collation per il database"; private const string IBX_LBLDbName = "Indicare il nome database"; public const string IBX_LblNomeDelFilegroup = "Indicare il nome del filegroup"; private const string IBX_TTLCollation = "Collation del Database"; private const string IBX_TTLDbName = "Nome nuovo Database"; public const string SINO_ConfermaCancellazioneFilegroup = "Confermi la cancellazione del filegroup?"; public const string TXT_ExportFileTitle = "Destinazione file esportazione specifica database"; public const string TXT_FGrupUpdate = "Modifica Nome Filegroup"; public const string TXT_ImportFilter = "File XML|*.xml|File TXT|*.txt|Tutti i file|*.*"; public const string TXT_ImportTitle = "Nome file specifica database"; public const string TXT_NewFGrup = "Nuovo Filegroup"; public const string WAR_Annullato = "Operazione annullata."; public const string WAR_AnnullatoNewFGrup = "Operazione annullata, non è stato specificato un File Dati per il FileGroup."; public const string WAR_AnnullatoNewLogFile = "Aggiunta file di log annullata."; public const string WAR_DbCreateSuccess = "Il database {0} è stato generato con successo"; public const string WAR_DbExist = "Il Database {0} esiste già all'interno del server, operazione annullata."; public const string WAR_FGrupNotUpdatable = "Non è possibile modificare il filegroup primary"; public const string WAR_ImpossibileCancellare = "Non è possibile cancellare l'ultimo file dati e rendere il filegroup non valido, " + "cancellare l'intero filegroup oppure aggiungere un nuovo file prima di eliminare " + "quello prescelto."; public const string WAR_InvalidExportCancelled = "La specifica database non è valida, impossibile esportarla, " + "verificare che la specifica sia valida prima di esportarla.";Private Const COLLATION_Default As String = "Default" Private Const DBNAME_Default As String = "Nuovo_Db" Public Const DEF_DataFile As String = "DataFile" Public Const DEF_ExportFile As String = "DbInfo_{0}.xml" Public Const DEF_LogFile As String = "LogFile" Private Const DFG_PRIMARY As String = "PRIMARY" Private Const FMP_FileLdb As String = "{0}.ldb" Private Const FMP_FileMdb As String = "{0}.mdb" Private Const FMP_LogName As String = "{0}_log" Private Const IBX_LBLCollation As String = "Indicare la collation per il database" Private Const IBX_LBLDbName As String = "Indicare il nome database" Public Const IBX_LblNomeDelFilegroup As String = "Indicare il nome del filegroup" Private Const IBX_TTLCollation As String = "Collation del Database" Private Const IBX_TTLDbName As String = "Nome nuovo Database" Public Const SINO_ConfermaCancellazioneFilegroup As String = _ "Confermi la cancellazione del filegroup?" Public Const TXT_ExportFileTitle As String = _ "Destinazione file esportazione specifica database" Public Const TXT_FGrupUpdate As String = "Modifica Nome Filegroup" Public Const TXT_ImportFilter As String = "File XML|*.xml|File TXT|*.txt|Tutti i file|*.*" Public Const TXT_ImportTitle As String = "Nome file specifica database" Public Const TXT_NewFGrup As String = "Nuovo Filegroup" Public Const WAR_Annullato As String = "Operazione annullata." Public Const WAR_AnnullatoNewFGrup As String = _ "Operazione annullata, non è stato specificato un File Dati per il FileGroup." Public Const WAR_AnnullatoNewLogFile As String = "Aggiunta file di log annullata." Public Const WAR_DbCreateSuccess As String = "Il database {0} è stato generato con successo" Public Const WAR_DbExist As String = _ "Il Database {0} esiste già all'interno del server, operazione annullata." Public Const WAR_FGrupNotUpdatable As String = _ "Non è possibile modificare il filegroup primary" Public Const WAR_ImpossibileCancellare As String = _ "Non è possibile cancellare l'ultimo file dati e rendere il filegroup non valido, " & _ "cancellare l'intero filegroup oppure aggiungere un nuovo file prima di eliminare " & _ "quello prescelto." Public Const WAR_InvalidExportCancelled As String = _ "La specifica database non è valida, impossibile esportarla, " & _ "verificare che la specifica sia valida prima di esportarla."Le costanti. Sono dei dati di default, dei Format Pattern e i messaggi per le inputbox.
Vi abbiamo introdotto all'uso delle costanti già da un paio di puntate; non le usiamo per cattiveria, onde rendere meno comprensibile il codice, ma per renderlo più solido, evitando stringhe ovunque e stringhe ripetute.public FrmCreateDatabase() { InitializeComponent(); }Public Sub New() InitializeComponent() End SubIl costruttore della form, limitato al solo richiamo del metodo di inizializzazione dei componenti, che bisogna scriversi in C#, che in VB sarebbe automaticamente prodotto, ma che Diego produce esplicitamente.
private void FrmCreateDatabase_Load(object sender, EventArgs e) { try { EnableControls(); } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub EnableControls() Try Me.txtCollation.Enabled = False Me.txtDbName.Enabled = False Me.btnNewFileGroup.Enabled = Me.dbGenerator <> Nothing Me.btnNewDataFile.Enabled = Me.dbGenerator <> Nothing Me.btnNewLogFile.Enabled = Me.dbGenerator <> Nothing Me.btnDeleteDataFile.Enabled = Me.lstDataFiles.Items.Count > 0 Me.btnDeleteFileGroup.Enabled = Me.lstGroup.Items.Count > 0 Me.btnDeleteLogFile.Enabled = Me.lstLogFiles.Items.Count > 0 Me.lstGroup.Enabled = Me.lstGroup.Items.Count > 0 Me.lstDataFiles.Enabled = Me.lstDataFiles.Items.Count > 0 Me.lstLogFiles.Enabled = Me.lstLogFiles.Items.Count > 0 Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End SubAl caricamento della form attiviamo/disattiviamo i controlli, richiamando il metodo seguente, perché, se non è stata ancora definita una struttura di definizione database, non devono essere attivi i controlli che permettono di modificarla, poiché provocherebbero degli errori a causa del fatto che gli oggetti che manipolano non esistono.
private void EnableControls() { try { this.txtCollation.Enabled = false; this.txtDbName.Enabled = false; this.btnNewFileGroup.Enabled = this.dbGenerator != null; this.btnNewdataFile.Enabled = this.dbGenerator != null; this.btnNewLogFile.Enabled = this.dbGenerator != null; this.btnDeleteDataFile.Enabled = this.lstDataFiles.Items.Count > 0; this.btnDeleteFileGroup.Enabled = this.lstGroup.Items.Count > 0; this.btnDeleteLogFile.Enabled = this.lstLogFiles.Items.Count > 0; this.lstGroup.Enabled = this.lstGroup.Items.Count > 0; this.lstDataFiles.Enabled = this.lstDataFiles.Items.Count > 0; this.lstLogFiles.Enabled = this.lstLogFiles.Items.Count > 0; } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Private Sub EnableControls() Try Me.txtCollation.Enabled = False Me.txtDbName.Enabled = False Me.btnNewFileGroup.Enabled = Me.dbGenerator <> Nothing Me.btnNewDataFile.Enabled = Me.dbGenerator <> Nothing Me.btnNewLogFile.Enabled = Me.dbGenerator <> Nothing Me.btnDeleteDataFile.Enabled = Me.lstDataFiles.Items.Count > 0 Me.btnDeleteFileGroup.Enabled = Me.lstGroup.Items.Count > 0 Me.btnDeleteLogFile.Enabled = Me.lstLogFiles.Items.Count > 0 Me.lstGroup.Enabled = Me.lstGroup.Items.Count > 0 Me.lstDataFiles.Enabled = Me.lstDataFiles.Items.Count > 0 Me.lstLogFiles.Enabled = Me.lstLogFiles.Items.Count > 0 Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End SubIl metodo di attivazione/disattivazione dei controlli richiamato in vari punti del programma, per attivare i controlli della form o disattivarli se non possono essere usati. Le textbox per il nome database e la collation sono sempre disattivate, poiché questi due dati verranno richiesti alla definizione della struttura database e non saranno modificabili in modo diretto. I bottoni e le listbox invece saranno attivati in base allo stato della struttura di definizione database in modo da impedire errori dovuti alla mancanza di dati o al fatto che la struttura non è ancora stata inizializzata.
private void nuovaSpecificaToolStripMenuItem_Click(object sender, EventArgs e) { try { FrmInputBox frmDbName = FrmInputBox.BuildMe(this.Icon, IBX_TTLDbName, DBNAME_Default, IBX_LBLDbName); if (frmDbName.ShowDialog() == DialogResult.OK) { FrmInputCombo frmCollation = FrmInputCombo.BuildMe(this.Icon, IBX_TTLCollation, COLLATION_Default, IBX_LBLCollation, COLLATION_LIST); if (frmCollation.ShowDialog() == DialogResult.OK) { this.dbGenerator = new DatabaseGenerator(frmDbName.TxtInput); if (frmCollation.TxtInput.Trim().ToUpper() != COLLATION_Default.ToUpper()) { this.dbGenerator.Collation = frmCollation.TxtInput; } this.dbGenerator.DataFiles.Add(new DataFileGroup(DFG_PRIMARY)); this.dbGenerator.DataFiles[DFG_PRIMARY].IsPrimary = true; string fileName = string.Format(FMP_FileMdb, this.dbGenerator.DatabaseName); string fileFullName = Path.Combine(UsingSqlServerConfig.DbFilesFolder, fileName); this.dbGenerator.DataFiles[DFG_PRIMARY].FileSpecs.Add(this.dbGenerator.DatabaseName, fileFullName); string logName = string.Format(FMP_LogName, this.dbGenerator.DatabaseName); fileName = string.Format(FMP_FileLdb, logName); fileFullName = Path.Combine(UsingSqlServerConfig.DbFilesFolder, fileName); this.dbGenerator.LogFileSpec.Add(logName, fileFullName); this.txtDbName.Text = this.dbGenerator.DatabaseName; this.txtCollation.Text = StringHelper.IsNullOrTrimEmpty( this.dbGenerator.Collation) ? COLLATION_Default : this.dbGenerator.Collation; LoadLists(); EnableControls(); } } } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub NuovaSpecificaToolStripMenuItem_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles NuovaSpecificaToolStripMenuItem.Click Try Dim frmBbName As FrmInputBox = FrmInputBox.BuildMe(Me.Icon, IBX_TTLCollation, DBNAME_Default, _ IBX_LBLDbName) If frmBbName.ShowDialog = Windows.Forms.DialogResult.OK Then Dim frmCollation As FrmInputCombo = FrmInputCombo.BuildMe(Me.Icon, IBX_TTLCollation, _ COLLATION_Default, _ IBX_LBLCollation, COLLATION_LIST) If frmCollation.ShowDialog = Windows.Forms.DialogResult.OK Then Me.dbGenerator = New DatabaseGenerator(frmBbName.TxtInput) If frmCollation.TxtInput.Trim.ToUpper <> COLLATION_Default.ToUpper Then Me.dbGenerator.Collation = frmCollation.TxtInput End If Me.dbGenerator.DataFiles.Add(New DataFileGroup(DFG_PRIMARY)) Me.dbGenerator.DataFiles(DFG_PRIMARY).IsPrimary = True Dim fileName As String = String.Format(FMP_FileMdb, Me.dbGenerator.DatabaseName) Dim fileFullName As String = Path.Combine(UsingSqlServerConfig.DbFilesFolder, fileName) Me.dbGenerator.DataFiles(DFG_PRIMARY).FileSpecs.Add(Me.dbGenerator.DatabaseName, _ fileFullName) Dim logName As String = String.Format(FMP_LogName, Me.dbGenerator.DatabaseName) fileName = String.Format(FMP_FileLdb, logName) fileFullName = Path.Combine(UsingSqlServerConfig.DbFilesFolder, fileName) Me.dbGenerator.LogFileSpec.Add(logName, fileFullName) Me.txtDbName.Text = Me.dbGenerator.DatabaseName If StringHelper.IsNullOrTrimEmpty(Me.dbGenerator.Collation) Then Me.txtCollation.Text = COLLATION_Default Else Me.txtCollation.Text = Me.dbGenerator.Collation End If LoadLists() EnableControls() End If End If Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubIl gestore dell'evento Click del menu per una nuova specifica database. Cominciamo ad usare le form ausiliarie che abbiamo definito. Infatti, per prima cosa ci serve dare un nome al nostro database, quindi utilizziamo una FrmInputBox. Per ora non facciamo controlli di validazione, ma in seguito potremmo farlo per rendere l'oggetto più professionale.
Se l'utente inserisce un nome per il database, passiamo a chiedergli la collation da utilizzare e sperimentiamo la FrmInputCombo. Anche qui il controllo è minimo, anche se su una combobox è relativamente facile.
Se l'utente inserisce e conferma la collation, istanziamo l'oggetto DbGenerator e definiamo immediatamente il filegroup PRIMARY per il database. Inoltre, utilizzando il setting DbFilesFolder, già definito, creiamo le specifiche di base per il file dati ed il file di log del nostro database.Fatto questo, provvediamo a caricare i controlli della form con tutti i dati - usiamo anche il metodo LoadLists di cui nel seguito - e attiviamo i controlli che permettono di fare le modifiche (EnableControls).
La form appena aperta, disattivata. La form dopo la definizione della specifica database con i controlli attivi. private void LoadLists() { try { LoadGroups(); if (this.dbGenerator.DataFiles.Count > 0) { this.lstGroup.SelectedValue = DFG_PRIMARY; } LoadLogs(); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Private Sub LoadLists() Try LoadGroups() Me.lstGroup.SelectedValue = DFG_PRIMARY LoadLogs() Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End SubIl metodo LoadLists carica la lista dei filegroup, attiva il filegroup PRIMARY e carica la lista dei file di log (vedi in seguito).
Invece non viene caricata la lista dei file dati collegati al filegroup selezionato, in quanto l'operazione di caricamento della listbox centrale è delegata al gestore dell'evento SelectedIndexChanged della listbox dei Filegroup, che implementeremo tra poco.private void LoadGroups() { this.lstGroup.Items.Clear(); foreach (DataFileGroup dfg in this.dbGenerator.DataFiles) { this.lstGroup.Items.Add(dfg.Name); } EnableControls(); }Private Sub LoadGroups() Me.lstGroup.Items.Clear() For Each dfg As DataFileGroup In Me.dbGenerator.DataFiles Me.lstGroup.Items.Add(dfg.Name) Next EnableControls() End SubIl metodo di caricamento della lista dei FileGroup del nostro database. Fa un ciclo sulla collection dei Filegroup e carica la listbox con i loro nomi. La chiamata ad EnableControls attiva o disattiva i controlli collegati alla presenza di dati sulla collezione dei filegroup.
private void LoadLogs() { this.lstLogFiles.Items.Clear(); foreach (FileSpecification fsp in this.dbGenerator.LogFileSpec) { this.lstLogFiles.Items.Add(fsp.Name); } if (this.lstLogFiles.Items.Count > 0) { this.lstLogFiles.SelectedIndex = 0; } EnableControls(); }Private Sub LoadLogs() Me.lstLogFiles.Items.Clear() For Each fsp As FileSpecification In Me.dbGenerator.LogFileSpec Me.lstLogFiles.Items.Add(fsp.Name) Next If Me.lstLogFiles.Items.Count > 0 Then Me.lstLogFiles.SelectedIndex = 0 End If End SubCome il caricamento dei filegroup, anche il caricamento della lista dei file di log definiti viene è effettuato con un ciclo sulla collezione delle specifiche di file di log della nostra struttura. Viene quindi selezionato il primo elemento della lista, se presente.
private void lstGroup_SelectedIndexChanged(object sender, EventArgs e) { try { LoadDataFiles(); } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub lstGroup_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles lstGroup.SelectedIndexChanged Try LoadDataFiles() Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubL'event handler dell' evento SelectedIndexChanged della listbox dei Filegroup, questo evento viene automaticamente scatenato quando il metodo Loadlists seleziona il filegroup Primary e, come vediamo, esegue il metodo di caricamento nella listbox delle specifiche dei file dati.
private void LoadDataFiles() { this.lstDataFiles.Items.Clear(); foreach (FileSpecification fsp in this.dbGenerator.DataFiles[lstGroup.Text].FileSpecs) { this.lstDataFiles.Items.Add(fsp.Name); } EnableControls(); }Private Sub LoadDataFiles() Me.lstDataFiles.Items.Clear() For Each fsp As FileSpecification In Me.dbGenerator.DataFiles(lstGroup.Text).FileSpecs Me.lstDataFiles.Items.Add(fsp.Name) Next EnableControls() End SubL'ultimo metodo di caricamento è simile ai precedenti e riempie la lista dei file dati. Quindi richiama il metodo di abilitazione dei controlli.
I metodi di Inserimento, Cancellazione, Modifica dei Filegroup
private void btnNewFileGroup_Click(object sender, EventArgs e) { try { FrmInputBox frmInputGroup = FrmInputBox.BuildMe(this.Icon, TXT_NewFGrup, string.Empty, IBX_LblNomeDelFilegroup); if (frmInputGroup.ShowDialog() == DialogResult.OK) { DataFileGroup dfg = new DataFileGroup(frmInputGroup.TxtInput); if (NewDataFile(dfg) == DialogResult.OK) { this.dbGenerator.DataFiles.Add(dfg); LoadGroups(); if (lstGroup.Items.Count > 0) { lstGroup.SelectedItem = frmInputGroup.TxtInput; } } else { Warnings.Avviso(WAR_AnnullatoNewFGrup); } } else { Warnings.Avviso(WAR_Annullato); } } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub btnNewFileGroup_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnNewFileGroup.Click Try Dim frmInputGroup As FrmInputBox = FrmInputBox.BuildMe(Me.Icon, TXT_NewFGrup, String.Empty, _ IBX_LblNomeDelFilegroup) If frmInputGroup.ShowDialog = Windows.Forms.DialogResult.OK Then Dim dfg As New DataFileGroup(frmInputGroup.TxtInput) If NewDataFile(dfg) = Windows.Forms.DialogResult.OK Then Me.dbGenerator.DataFiles.Add(dfg) LoadGroups() If lstGroup.Items.Count > 0 Then lstGroup.SelectedItem = frmInputGroup.TxtInput End If Else Warnings.Avviso(WAR_AnnullatoNewFGrup) End If Else Warnings.Avviso(WAR_Annullato) End If Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubPer aggiungere un filegroup alla specifica di un database, per prima cosa dobbiamo chiederne il nome, quindi utilizziamo ancora una volta la FrmInputBox per ottenere questo dato. Poi, è necessario che in questo filegroup sia specificato almeno un file dati, quindi, automaticamente viene attivata la form per la gestione dell'inserimento/modifica di un nuovo file dati, attraverso la chiamata di un metodo esposto in seguito.
Al termine dell'inserimento del nuovo file dati, se l'utente ha confermato e inserito i dati necessari, il filegroup viene generato e i dati sulla form aggiornati, altrimenti, viene visualizzato un messaggio di avviso e il filegroup non viene generato.
private void btnDeleteFileGroup_Click(object sender, EventArgs e) { try { if (Warnings.SiNo(SINO_ConfermaCancellazioneFilegroup) == DialogResult.Yes) { DataFileGroup dfg = this.dbGenerator.DataFiles[this.lstGroup.SelectedItem.ToString()]; this.dbGenerator.DataFiles.Remove(dfg); LoadGroups(); lstGroup.SelectedIndex = 0; } } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub btnDeleteFileGroup_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnDeleteFileGroup.Click Try If Warnings.SiNo(SINO_ConfermaCancellazioneFilegroup) = Windows.Forms.DialogResult.Yes Then Dim dfg As DataFileGroup = Me.dbGenerator.DataFiles(Me.lstGroup.SelectedItem.ToString) Me.dbGenerator.DataFiles.Remove(dfg) LoadGroups() lstGroup.SelectedIndex = 0 End If Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubPer cancellare un filegroup, basta selezionarlo sulla listbox, premere il bottone di cui vediamo l'event handler e confermare la cancellazione. Il filegroup verrà rimosso e i dati aggiornati. Non abbiamo messo controlli sulla cancellazione del filegroup PRIMARY, né avvisi, ma, in una realizzazione non didattica, è buona cosa considerare i vari casi e inserire controlli per guidare l'utente a non sbagliare.
private void lstGroup_DoubleClick(object sender, EventArgs e) { try { ListBox list = (ListBox)sender; string name = list.SelectedItem.ToString(); if (name != DFG_PRIMARY) { FrmInputBox frmInputGroup = FrmInputBox.BuildMe(this.Icon, TXT_FGrupUpdate, name, IBX_LblNomeDelFilegroup); if (frmInputGroup.ShowDialog() == DialogResult.OK) { this.dbGenerator.DataFiles[name].Name = frmInputGroup.TxtInput; LoadGroups(); list.SelectedItem = frmInputGroup.TxtInput; } } else { Warnings.Avviso(WAR_FGrupNotUpdatable); } } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub lstGroup_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles lstGroup.DoubleClick Try Dim list As ListBox = DirectCast(sender, ListBox) Dim name As String = list.SelectedItem.ToString If name <> DFG_PRIMARY Then Dim frmInputgroup As FrmInputBox = FrmInputBox.BuildMe(Me.Icon, TXT_FGrupUpdate, name, _ IBX_LblNomeDelFilegroup) If frmInputgroup.ShowDialog = Windows.Forms.DialogResult.OK Then Me.dbGenerator.DataFiles(name).Name = frmInputgroup.TxtInput LoadGroups() list.SelectedItem = frmInputgroup.TxtInput End If Else Warnings.Avviso(WAR_FGrupNotUpdatable) End If Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubLa modifica di un filegroup è attivata dal doppio click sul nome dello stesso sulla listbox. Il nome è la sola cosa modificabile in un filegroup; infatti, per modificare i file dati che lo compongono, deve essere usata la lista specifica. Abbiamo inserito un controllo che permette di modificare tutti i nomi tranne quello del filegroup PRIMARY.
I metodi di Inserimento, Cancellazione, Modifica delle specifiche del file dati
private void btnNewdataFile_Click(object sender, EventArgs e) { try { NewDataFile(this.dbGenerator.DataFiles[this.lstGroup.SelectedItem.ToString()]); LoadDataFiles(); } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub btnNewDataFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnNewDataFile.Click Try NewDataFile(Me.dbGenerator.DataFiles(Me.lstGroup.SelectedItem.ToString)) LoadDataFiles() Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubIl gestore dell'evento Click sul pulsante btnNewdataFile chiama il metodo NewDataFile (nel seguito) e aggiorna la lista sottostante chiamando LoadDataFiles.
private DialogResult NewDataFile(DataFileGroup pGroup) { try { FrmFileSpecification frmData = FrmFileSpecification.BuildMe(DEF_DataFile, this.Icon); DialogResult dlgRs = frmData.ShowDialog(); if (dlgRs == DialogResult.OK) { pGroup.FileSpecs.Add(frmData.FileSpec); } return (dlgRs); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Private Function NewDataFile(ByVal group As DataFileGroup) As DialogResult Try Dim frmData As FrmFileSpecification = FrmFileSpecification.BuildMe(DEF_DataFile, Me.Icon) Dim dlgrs As DialogResult = frmData.ShowDialog If dlgrs = Windows.Forms.DialogResult.OK Then group.FileSpecs.Add(frmData.FileSpec) End If Return dlgrs Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionPer inserire un nuovo file dati, instanziamo una FrmFileSpecification usando il suo metodo BuildMe, apriamo la form come dialogo e, se l'utente esce salvando i dati, aggiungiamo la FileSpecification che è stata definita nella form al filegroup corrente (che è stato passato come parametro).
private void btnDeleteDataFile_Click(object sender, EventArgs e) { try { if (this.lstDataFiles.Items.Count > 1) { string lGroupSI = this.lstGroup.SelectedItem.ToString(); string lDatafileSI = this.lstDataFiles.SelectedItem.ToString(); FileSpecification fsp = this.dbGenerator.DataFiles[lGroupSI].FileSpecs[lDatafileSI]; this.dbGenerator.DataFiles[lGroupSI].FileSpecs.Remove(fsp); LoadDataFiles(); if (lstDataFiles.Items.Count > 0) { lstDataFiles.SelectedIndex = 0; } } else { Warnings.Avviso(WAR_ImpossibileCancellare); } } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub btnDeleteDataFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnDeleteDataFile.Click Try If Me.lstDataFiles.Items.Count > 1 Then Dim lGroupSI As String = Me.lstGroup.SelectedItem.ToString Dim lDatafileSI As String = Me.lstDataFiles.SelectedItem.ToString Dim fsp As FileSpecification = Me.dbGenerator.DataFiles(lGroupSI).FileSpecs(lDatafileSI) Me.dbGenerator.DataFiles(lGroupSI).FileSpecs.Remove(fsp) LoadDataFiles() If lstDataFiles.Items.Count > 0 Then lstDataFiles.SelectedIndex = 0 End If Else Warnings.Avviso(WAR_ImpossibileCancellare) End If Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubPer cancellare una specifica di file dati, facciamo prima un controllo: infatti, se è l'ultimo file dati non permettiamo sia cancellato, ma obblighiamo l'utente a cancellare tutto il filegroup, in quanto un filegroup senza almeno un file dati non è valido. Per cancellare il file dati, lo rimuoviamo dalla collezione del suo filegroup e aggiorniamo il contenuto delle liste.
private void lstDataFiles_DoubleClick(object sender, EventArgs e) { try { ListBox lst = (ListBox)sender; string fileSpecName = lst.SelectedItem.ToString(); string fileGroupName = this.lstGroup.SelectedItem.ToString(); FrmFileSpecification frmData = FrmFileSpecification.BuildMe(DEF_DataFile, this.Icon); frmData.FileSpec = this.dbGenerator.DataFiles[fileGroupName].FileSpecs[fileSpecName]; if (frmData.ShowDialog() == DialogResult.OK) { LoadDataFiles(); lst.SelectedItem = frmData.FileSpec.Name; } } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub lstDataFiles_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles lstDataFiles.DoubleClick Try Dim lst As ListBox = DirectCast(sender, ListBox) Dim fileSpecName As String = lst.SelectedItem.ToString Dim fileGroupName As String = Me.lstGroup.SelectedItem.ToString Dim frmData As FrmFileSpecification = FrmFileSpecification.BuildMe(DEF_DataFile, Me.Icon) frmData.FileSpec = Me.dbGenerator.DataFiles(fileGroupName).FileSpecs(fileSpecName) If frmData.ShowDialog = Windows.Forms.DialogResult.OK Then LoadDataFiles() lst.SelectedItem = frmData.FileSpec.Name End If Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubIl doppio click sulla lista, invece, ci permette di modificare la forma di un DataFileSpecification. Anche in questo caso, apriamo la form apposita, ma prima gli passiamo la struttura della specifica di file che vogliamo modificare, in modo che i dati vengano automaticamente aggiornati quando l'utente conferma con il tasto OK. Al termine della modifica, ricarichiamo la lista dei dati.
I metodi di Inserimento, Cancellazione, Modifica delle specifiche di file di log
private void btnNewLogFile_Click(object sender, EventArgs e) { try { FrmFileSpecification frmLog = FrmFileSpecification.BuildMe(DEF_LogFile, this.Icon); if (frmLog.ShowDialog() == DialogResult.OK) { this.dbGenerator.LogFileSpec.Add(frmLog.FileSpec); string name = frmLog.FileSpec.Name; LoadLogs(); this.lstLogFiles.SelectedItem = name; } else { Warnings.Avviso(WAR_AnnullatoNewLogFile); } } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub btnNewLogFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnNewLogFile.Click Try Dim frmLog As FrmFileSpecification = FrmFileSpecification.BuildMe(DEF_LogFile, Me.Icon) If frmLog.ShowDialog = Windows.Forms.DialogResult.OK Then Me.dbGenerator.LogFileSpec.Add(frmLog.FileSpec) Dim name As String = frmLog.FileSpec.Name LoadLogs() Me.lstLogFiles.SelectedItem = name Else Warnings.Avviso(WAR_AnnullatoNewLogFile) End If Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubAnche i file di Log seguono la stessa forma dei file dati. Pertanto, il codice per l'inserimento, la cancellazione e la modifica della specifica di un file di log è identico a quello scritto per le specifiche dei file di dati, variano solamente i nomi e le estensioni.
private void btnDeleteLogFile_Click(object sender, EventArgs e) { FileSpecification fsp = this.dbGenerator.LogFileSpec[this.lstLogFiles.SelectedItem.ToString()]; this.dbGenerator.LogFileSpec.Remove(fsp); LoadLogs(); if (lstLogFiles.Items.Count > 0) { lstLogFiles.SelectedIndex = 0; } }Private Sub btnDeleteLogFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnDeleteLogFile.Click Dim fsp As FileSpecification = Me.dbGenerator.LogFileSpec(Me.lstLogFiles.SelectedItem.ToString) Me.dbGenerator.LogFileSpec.Remove(fsp) LoadLogs() If lstLogFiles.Items.Count > 0 Then lstLogFiles.SelectedIndex = 0 End If End SubMetodo di cancellazione del file di log.
private void lstLogFiles_DoubleClick(object sender, EventArgs e) { try { ListBox lst = (ListBox)sender; FrmFileSpecification frmLog = FrmFileSpecification.BuildMe(DEF_LogFile, this.Icon); frmLog.FileSpec = this.dbGenerator.LogFileSpec[lst.SelectedItem.ToString()]; if (frmLog.ShowDialog() == DialogResult.OK) { string name = frmLog.FileSpec.Name; LoadLogs(); this.lstLogFiles.SelectedItem = name; } } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub lstLogFiles_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles lstLogFiles.DoubleClick Try Dim lst As ListBox = DirectCast(sender, ListBox) Dim frmLog As FrmFileSpecification = FrmFileSpecification.BuildMe(DEF_LogFile, Me.Icon) frmLog.FileSpec = Me.dbGenerator.LogFileSpec(lst.SelectedItem.ToString) If frmLog.ShowDialog = Windows.Forms.DialogResult.OK Then Dim name As String = frmLog.FileSpec.Name LoadLogs() Me.lstLogFiles.SelectedItem = name End If Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubMetodo di modifica del file di log.
Metodo di visualizzazione dello script SQL
private void visualizzaScriptSQLToolStripMenuItem_Click(object sender, EventArgs e) { try { this.txtResult.Text = this.dbGenerator.ToSqlString() + Environment.NewLine + this.txtResult.Text; } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub VisualizzaScriptSQLToolStripMenuItem_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles VisualizzaScriptSQLToolStripMenuItem.Click Try Me.txtResult.Text = Me.dbGenerator.ToSqlString & Environment.NewLine & Me.txtResult.Text Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End Sub
Abbiamo inserito questo metodo sia per permettervi di verificare la correttezza del codice SQL generato dalle nostre classi, sia per osservare come il codice SQL che abbiamo predisposto sulle varie classi che abbiamo costruito nelle scorse puntate si combina per ottenere la stringa di creazione database, sia, infine, perché possiate usarla per creare lo script e poi includerlo in un programma che state costruendo. Come potete vedere, utilizza semplicemente la funzione ToSqlString della classe DatabaseGenerator. Il risultato dell'esecuzione del metodo è visibile nell'immagine accanto.
Il metodo di esportazione su file XML
private void esportaSpecificaDatabaseToolStripMenuItem_Click(object sender, EventArgs e) { try { if (this.dbGenerator.IsValid()) { sfd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal); sfd.Title = TXT_ExportFileTitle; sfd.FileName = string.Format(DEF_ExportFile, this.dbGenerator.DatabaseName); if (sfd.ShowDialog() == DialogResult.OK) { this.dbGenerator.WriteXml(sfd.FileName); } } else { Warnings.Avviso(WAR_InvalidExportCancelled); } } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub EsportaSpecificaDatabaseToolStripMenuItem_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles EsportaSpecificaDatabaseToolStripMenuItem.Click Try If Me.dbGenerator.IsValid Then sfd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal) sfd.Title = TXT_ExportFileTitle sfd.FileName = String.Format(DEF_ExportFile, Me.dbGenerator.DatabaseName) If sfd.ShowDialog = Windows.Forms.DialogResult.OK Then Me.dbGenerator.WriteXml(sfd.FileName) End If Else Warnings.Avviso(WAR_InvalidExportCancelled) End If Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubIl metodo di esportazione della specifica database ci permette invece di verificare il corretto funzionamento del metodo di serializzazione della nostra classe DatabaseGenerator. In questo codice, possiamo vedere anche due cose nuove, la prima è come ottenere dal sistema il path della cartella documenti dell'utente corrente, la seconda è come utilizzare la SaveFileDialog fornita dal sistema per decidere dove scrivere il nostro file di dati.
Una volta chiamato questo metodo, il file risultante è il seguente:
<?xml version="1.0" encoding="utf-8"?> <DatabaseGenerator xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.visual-basic.it"> <Collation /> <DataFiles> <DataFileGroup> <FileSpecs> <FileSpecification> <FileName>d:\sql.dir\MyDb.mdb</FileName> <Growth> <SizeUmi>MB</SizeUmi> <SizeValue>10</SizeValue> </Growth> <MaxSize> <SizeUmi>MB</SizeUmi> <SizeValue>1024</SizeValue> </MaxSize> <Name>MyDb</Name> <Size> <SizeUmi>MB</SizeUmi> <SizeValue>100</SizeValue> </Size> </FileSpecification> </FileSpecs> <IsPrimary>true</IsPrimary> <Name>PRIMARY</Name> </DataFileGroup> </DataFiles> <LogFileSpec> <FileSpecification> <FileName>d:\sql.dir\MyDb_log.ldb</FileName> <Growth> <SizeUmi>%</SizeUmi> <SizeValue>10</SizeValue> </Growth> <MaxSize> <SizeUmi>MB</SizeUmi> <SizeValue>512</SizeValue> </MaxSize> <Name>MyDb_log</Name> <Size> <SizeUmi>MB</SizeUmi> <SizeValue>5</SizeValue> </Size> </FileSpecification> </LogFileSpec> <DatabaseName>MyDb</DatabaseName> </DatabaseGenerator>Possiamo vedere che, grazie al fatto che le classi che compongono l'oggetto DatabaseGenerator sono state predisposte per la serializzazione automatica, non abbiamo dovuto fare praticamente nulla se non scrivere una riga di codice nella classe per ottenere una copia dell'oggetto, che può essere trasportata e riutilizzata per qualsiasi scopo. In qualsiasi nostro programma ove dovessimo generare un database, infatti, possiamo utilizzare la struttura XML invece dello script SQL e, agganciando la libreria che contiene la classe DatabaseGenerator, leggere il file dell'oggetto e generare un nuovo database.
Il metodo di importazione di una specifica salvata in XML
private void importaSpecificaDatabaseToolStripMenuItem_Click(object sender, EventArgs e) { try { ofd.Title = TXT_ImportTitle; ofd.Multiselect = false; ofd.Filter = TXT_ImportFilter; ofd.FilterIndex = 1; ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal); if (ofd.ShowDialog() == DialogResult.OK) { this.dbGenerator = DatabaseGenerator.ReadXml(ofd.FileName, false); } else { this.dbGenerator = new DatabaseGenerator(DBNAME_Default); } LoadFromGenerator(); } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub ImportaSpecificaDatabaseToolStripMenuItem_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles ImportaSpecificaDatabaseToolStripMenuItem.Click Try ofd.Title = TXT_ImportTitle ofd.Multiselect = False ofd.Filter = TXT_ImportFilter ofd.FilterIndex = 1 ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal) If ofd.ShowDialog = Windows.Forms.DialogResult.OK Then Me.dbGenerator = DatabaseGenerator.ReadXml(ofd.FileName, False) Else Me.dbGenerator = New DatabaseGenerator(DBNAME_Default) End If LoadFromGenerator() Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubUtilizziamo ancora una volta la OpenFileDialog e la funzione per la lettura della cartella Documenti dell'utente corrente e facciamo scegliere all'utente il file contenente la specifica database. In caso la selezione sia effettuata correttamente, carichiamo la specifica salvata, altrimenti azzeriamo il contenuto della form. Vi invito a provare per gioco ad aprire file non validi, per verificare come vengano restituite le eccezioni del caso.
Per popolare la form, viene chiamato il metodo seguente:private void LoadFromGenerator() { this.txtCollation.Text = dbGenerator.Collation; this.txtDbName.Text = dbGenerator.DatabaseName; LoadLists(); }Private Sub LoadFromGenerator() Me.txtCollation.Text = Me.dbGenerator.Collation Me.txtDbName.Text = Me.dbGenerator.DatabaseName LoadLists() End SubIl metodo di creazione del database
private void creaDbToolStripMenuItem_Click(object sender, EventArgs e) { try { if (!SqlHelper.ExistDatabase(UsingSqlServerConfig.CnStringMaster, this.dbGenerator.DatabaseName)) { SqlHelper.ExecCommand(UsingSqlServerConfig.CnStringMaster, this.dbGenerator.ToSqlString(), CommandType.Text); Warnings.Info(string.Format(WAR_DbCreateSuccess, this.dbGenerator.DatabaseName)); } else { Warnings.Avviso(string.Format(WAR_DbExist, this.dbGenerator.DatabaseName)); } } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub CreaDBToolStripMenuItem_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles CreaDBToolStripMenuItem.Click Try If Not SqlHelper.ExistDatabase(UsingSqlServerConfig.CnStringMaster, _ Me.dbGenerator.DatabaseName) Then SqlHelper.ExecCommand(UsingSqlServerConfig.CnStringMaster, _ Me.dbGenerator.ToSqlString, CommandType.Text) Warnings.Info(String.Format(WAR_DbCreateSuccess, Me.dbGenerator.DatabaseName)) Else Warnings.Avviso(String.Format(WAR_DbExist, Me.dbGenerator.DatabaseName)) End If Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubIl metodo per la generazione del database utilizza due nuovi metodi di cui abbiamo sentito la necessità e che abbiamo inserito nella classe SqlHelper, che spiegheremo di seguito. Il primo metodo, controlla che il database che vogliamo generare non esista già sul server; il secondo invia il comando di generazione del database al server. Il risultato dell'operazione è uno dei seguenti:
public static void OpenMe(Icon pIcon, Form pMDI) { try { FrmCreateDatabase frm = (FrmCreateDatabase)FormHelper.VediSeIlFormEAperto( "FrmCreateDatabase", pMDI, true, false); if (frm == null) { frm = new FrmCreateDatabase(); frm.Icon = pIcon; frm.MdiParent = pMDI; frm.Show(); } { frm.WindowState = FormWindowState.Normal; frm.BringToFront(); } } catch (Exception ex) { Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Public Shared Sub OpenMe(ByVal icon As Icon, ByVal mdi As Form) Try Dim frm As FrmCreateDatabase = DirectCast(FormHelper.VediSeIlFormEAperto( _ "FrmCreateDatabase", mdi, True, False), FrmCreateDatabase) If frm Is Nothing Then frm = New FrmCreateDatabase frm.Icon = icon frm.MdiParent = mdi frm.Show() End If frm.WindowState = FormWindowState.Normal frm.BringToFront() Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubIl metodo OpenMe per instanziare e aprire la form, da usare dal menu della FrmMain, così:
private void creaDatabaseToolStripMenuItem_Click(object sender, EventArgs e) { FrmCreateDatabase.OpenMe(this.Icon, this); }Private Sub CreaDatabaseToolStripMenuItem_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles CreaDatabaseToolStripMenuItem.Click FrmCreateDatabase.OpenMe(Me.Icon, Me) End SubPossiamo notare che non ci serve molta fatica per utilizzare la nostra form di creazione database.
La classe SqlHelper
Abbiamo modificato la classe SqlHelper nel seguente modo, per poter generare un nuovo database e per lavorare con esso:using System.Data;Imports System.DataAbbiamo aggiunto la direttiva using (Imports) per avere a disposizione gli oggetti del namespace Data.
private const string SQL_ExistDb = "SELECT count(name) FROM sys.databases WHERE name = @DbName"; private const string SQL_ParamDbName = "@DbName";Private Const SQL_ExistDb As String = "SELECT count(name) FROM sys.databases WHERE name = @DbName" Private Const SQL_ParamDbName As String = "@DbName"Le costanti che ci servono per predisporre la stringa sql per la verifica dell'esistenza del database, e per il nome del parametro base per la richiesta.
public static void ExecCommand(string pCnString, string pCommand, CommandType pType, SqlParameter[] pParams) { SqlConnection cn = null; try { cn = new SqlConnection(); cn.ConnectionString = pCnString; SqlCommand cmd = new SqlCommand(); cmd.Connection = cn; cmd.CommandText = pCommand; cmd.CommandType = pType; if (pParams != null) { cmd.Parameters.AddRange(pParams); } cn.Open(); cmd.ExecuteNonQuery(); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } finally { if (cn != null) { if (cn.State == ConnectionState.Open) { cn.Close(); } } } } public static void ExecCommand(string pCnString, string pCommand, CommandType pType) { ExecCommand(pCnString, pCommand, pType, null); }Public Shared Sub ExecCommand(ByVal cnString As String, ByVal command As String, _ ByVal type As CommandType, ByVal params As SqlParameter()) Dim cn As SqlConnection = Nothing Try cn = New SqlConnection cn.ConnectionString = cnString Dim cmd As New SqlCommand cmd.Connection = cn cmd.CommandText = command cmd.CommandType = type If params IsNot Nothing Then cmd.Parameters.AddRange(params) End If cn.Open() cmd.ExecuteNonQuery() Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) Finally If cn IsNot Nothing Then If cn.State = ConnectionState.Open Then cn.Close() End If End If End Try End Sub Public Shared Sub ExecCommand(ByVal cnString As String, ByVal command As String, _ ByVal type As CommandType) ExecCommand(cnString, command, type, Nothing) End SubIl metodo per eseguire uno script SQL sul server. Sarà utilizzato per tutti i comandi di DDL (data definition language) sul database. Ne abbiamo predisposto due overload: uno con i parametri Sql, l'altro senza (ad esempio, il comando CreateDatabase non ha parametri).
public static object ExecScalar(string pCnString, string pCommand, CommandType pType, SqlParameter[] pParams) { SqlConnection cn = null; try { cn = new SqlConnection(); cn.ConnectionString = pCnString; SqlCommand cmd = new SqlCommand(); cmd.Connection = cn; cmd.CommandText = pCommand; cmd.CommandType = pType; if (pParams != null) { cmd.Parameters.AddRange(pParams); } cn.Open(); return (cmd.ExecuteScalar()); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } finally { if (cn != null) { if (cn.State == ConnectionState.Open) { cn.Close(); } } } } public static void ExecScalar(string pCnString, string pCommand, CommandType pType) { ExecScalar(pCnString, pCommand, pType, null); }Public Shared Function ExecScalar(ByVal cnString As String, ByVal command As String, _ ByVal type As CommandType, ByVal params As SqlParameter()) As Object Dim cn As SqlConnection = Nothing Try cn = New SqlConnection cn.ConnectionString = cnString Dim cmd As New SqlCommand cmd.Connection = cn cmd.CommandText = command cmd.CommandType = type If params IsNot Nothing Then cmd.Parameters.AddRange(params) End If cn.Open() Return cmd.ExecuteScalar Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) Finally If cn IsNot Nothing Then If cn.State = ConnectionState.Open Then cn.Close() End If End If End Try End Function Public Shared Sub ExecScalar(ByVal cnString As String, ByVal command As String, _ ByVal type As CommandType) ExecScalar(cnString, command, type, Nothing) End SubIl metodo, anch'esso con un overload, per eseguire un comando che restituisce un singolo valore. Lo utilizzeremo nel metodo che verifica l'esistenza del database:
public static bool ExistDatabase(string pCnString, string pDbName) { try { bool retExist = false; SqlParameter[] para = new SqlParameter[] { new SqlParameter( SQL_ParamDbName, pDbName) }; int count = (int)ExecScalar(pCnString, SQL_ExistDb, CommandType.Text, para); retExist = count > 0; return (retExist); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Shared Function ExistDatabase(ByVal cnString As String, ByVal dbName As String) As Boolean Try Dim retExist As Boolean = False Dim para As SqlParameter() = New SqlParameter() {New SqlParameter(SQL_ParamDbName, dbName)} Dim count As Integer = CType(ExecScalar(cnString, SQL_ExistDb, CommandType.Text, para), _ Integer) retExist = count > 0 Return retExist Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionIl metodo che specificamente controlla l'esistenza del database sul server.
Avviso
Sabrina vi ricorda che, per l'esecuzione di tutte e due le chiamate a SQL Server inserite nella form FrmCreateDatabase, l'utente che si connette a SQL Server, sia via Trusted Connection, che via utente SQL, deve avere diritti di amministrazione elevati sul server, in quanto l'interrogazione per l'esistenza del database viene effettuata sul database Master, come pure la Create Database. Pertanto, se l'utente non appartiene al ruolo server SysAdmin di SQL Server, difficilmente potrà eseguire le operazioni che abbiamo descritto in questo articolo, pure essendovi un altro ruolo specifico (che a memoria Sabrina non ricorda, quindi non cita, né Diego sa) che permette alcune delle operazioni di base.Parleremo estesamente di Login, Mappature di Utenti e gruppi Windows in SQL Server, Ruoli Server e Ruoli Database, Utenti, Permessi e Password negli articoli che dedicheremo alla Sicurezza di SQL Server.
Arrivederci alla prossima puntata
Con questo, abbiamo concluso la nostra nona puntata ed abbiamo predisposto il necessario per testare ed utilizzare tutte le classi che abbiamo generato fin qui. Nelle prossime puntate, inizieremo a costruire la struttura del database che ci accompagnerà per il resto delle puntate di questa serie, esplorando quelle che sono le basi per la costruzione di un database e quanto occorre a gestirne non solo la struttura, ma anche il contenuto, cioè inserimento, modifica, cancellazione e consultazione dei dati.Come al solito, il codice fin qui prodotto è scaricabile dall'Area Download.
(nota: il codice è 'sempre' aggiornato, con eventuali correzioni al codice precedente, non citate nell'articolo).
Potete scrivere Feedback (commenti, critiche, suggerimenti, correzioni) sul blog di Sabrina.