Collezioni di controlli e passaggio di informazioni fra form

Premessa
L’idea di scrivere questo articolo è scaturita da una serie di quesiti posti sul forum .NET da parte di principianti, soprattutto coloro che arrivano da VB6 con cui erano abituati a fare delle cose che sembrano non essere più possibili in VB.NET o C#. Anche in questo caso l’idea di base, il codice C# e tutte le porcherie sono opera di Sabrina Cosolo, la correzione, la stesura in italiano comprensibile, la versione in VB.Net e l’aggiunta di tutte le cose che di solito Sabrina si dimentica, sono opera di Diego Cattaruzza.

Uno dei quesiti più frequenti, da parte di chi ha lavorato in VB6, riguarda l’uso di array di controlli. Questi sembravano una panacea in VB6, ma in VB.NET e C# sembrano non esistere più. Nulla di più sbagliato: esistono e si possono costruire, solo che nella maggior parte dei casi non sono necessari, perché sostituibili da oggetti molto più potenti e flessibili.
A questo argomento sono dedicati anche altri articoli di Diego (l’ultimo è ‘Quel punto‘)

Un altro quesito frequente riguarda il metodo per passare informazioni da una form ad un’altra, cosa che sembra essere incomprensibilmente complicata nel nuovo ambiente.

Il progetto fornito con questo articolo è svolto in maniera pseudo-professionale, non sono stati seguiti i rigidi criteri usati per la serie “Guarda! Senza mani!“, perché si sarebbe dovuto scrivere un “Guarda! Senza mani! 2 – la vendetta” e, per quanto ci piacerebbe, non abbiamo fisicamente il tempo necessario ad affrontarlo. Quindi c’è un solo progetto, ci sono due classi helper copiate da “Guarda! Senza Mani!” (Warnings e ExceptionHelper), ma il codice che rimane è uguamente succoso e speriamo interessante.

Lo spunto per l’argomento del progetto è venuto da uno dei membri della lista, che ha detto di dover creare una applicazione per gestire le prenotazioni di uno stabilimento balneare. Questo progetto non è in grado di gestire uno stabilimento balneare in tutto e per tutto, però fornisce un’idea di base su come gestire le prenotazioni degli ombrelloni di una spiaggia virtuale. Con poco lavoro, potrebbe divenire un programma funzionante, e con ancor meno lavoro potrebbe essere utilizzato per organizzare le prenotazioni dei biglietti di un cinema oppure di un teatro (sempre che a lavorare sia un unico operatore: per fare qualcosa di più complesso e gestire la multiutenza e la concorrenza, bisogna lavorare con strumenti diversi e cambiare tipo di approccio ai dati).

Gli argomenti trattati nell’articolo sono i seguenti:

  •  Creare a runtime una collezione di controlli
  •   Attivare sui controlli generati delle funzionalità a cui essi rispondono
  •   Agganciare una form di tipo Owned ad una form principale
  •   Fare reagire il contenuto della form Owned a ciò che accade sulla form Owner
  •   Gestire dei dati utilizzando gli oggetti dati di .NET senza un database collegato.
  •   Generare dati, caricare dati, salvare dati, aggiornare dati, cancellare dati.
  •   Creare una form dettagli per l’inserimento e la modifica dati che scambi i dati con gli oggetti contenuti nella form principale.
  •   Eseguire operazioni obbedendo al click di un bottone.
  •   Eseguire operazioni obbedendo al click su un controllo
  •   Eseguire operazioni obbedendo al doppio click su una riga di un controllo lista.
  •   Formattare correttamente il contenuto di un controllo lista.
  •   Utilizzare le risorse per gestire immagini e icone.

La struttura del progetto

  •  Apriamo Visual Studio, selezioniamo New>Project>.
  •  Nella finestra dei progetti, selezioniamo Windows Application, chiamiamo la nostra applicazione ControlCollection, spuntiamo la casella Create Directory for Solution
  •  Rinominiamo FrmMain la classe Form1, eventualmente a mano se il refactoring non lo facesse da solo.
    •  Per i programmatori in VB.Net: seguite le raccomandazioni indicate nel primo elemento del paragrafo “Qualche buona abitudine” che trovate nella prima parte di“Guarda! Senza mani!”, impostando VB in modo più serio (infatti, al prossimo punto…)
  •  Creiamo la classe vuota Program
  •  Nel progetto a corredo di questo articolo, trovate le classi ExceptionHelper e Warnings .
    •  Copiatele nella cartella del progetto
    •  aggiornate la visualizzazione attivando l’opzione visualizza tutti i files nel solution explorer,
    •  selezionate i due file e includeteli nel progetto attraverso l’apposito elemento del menu di contesto
      (se volete sapere cosa sono e come funzionano, è spiegato per filo e per segno nell’articolo di cui al precedente link).
      Se, come noi, avete già cominciato “Guarda! Senza mani!“, potete copiarveli da lì.
  •  Aggiungiamo altre due form facendo click con il tasto destro sul progetto ControlCollection Add>Windows Form> e chiamiamole rispettivamente:
    •  FrmBookings
    •  FrmBookingsDetail

La classe Program
Per ora non mettiamo nulla in alcuna form, ma andiamo invece ad aprire il file program.cs (vb).

namespace ControlCollection
{
static class Program
  {
    [STAThread]
    static void Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      Application.Run(new FrmMain());
    }
  }
}
Public Class Program
  <STAThread()> _
  Shared Sub Main()
    Application.EnableVisualStyles()
    Application.SetCompatibleTextRenderingDefault(False)
    Application.Run(New FrmMain)
  End Sub
End Class

Questa classe, creata automaticamente dal template di C# e che si può far creare automaticamente anche a VB generandovi un Template personalizzato (Vedi articolo Modifica dei template di Visual Studio 2005), è quello che si chiama Entry Point del programma: contiene il metodo statico Main che il Framework .NET automaticamente chiama quando una applicazione viene avviata.
In VB, quando utilizzate le scorciatoie di progetto che evitano l’uso di questa classe, essa viene invece creata automaticamente dal compilatore, ma perché lasciare che il compilatore pensi per noi?

Per i principianti, spieghiamo esattamente il codice:

  •  STAThread
    •  è un attributo che indica al compilatore che modello di threading utilizzare per lanciare questa applicazione.
      Lo standard, pur essendo il meno performante, è Single Threaded Apartment (STA). E’ possibile utilizzare anche il Multi Threaded Apartment (MTA – MTAThread), ma questo funziona esclusivamente se non ci sono chiamate a componenti COM all’interno dell’applicazione, e tutto il codice è rigorosamente managed: basta che ci serva una chiamata ad una Dll non .NET o a un componente .COM e non si può utilizzare.
      Per la spiegazione di cosa significano Single Threaded Apartment e Multi Threaded Apartment, i link portano alle pagine di MSDN che spiegano molto meglio di quanto potremmo fare noi di cosa si tratta.
  •  static void Main (Shared Sub Main)
    •  è l’entry point, ovvero il primo metodo chiamato dal sistema operativo quando un Exe .NET viene lanciato.
      Il suo scopo è quello di istanziare la classe principale su cui il programma si basa. Usualmente, per i programmi Windows Forms, la classe principale è una form, può essere una MDI per un programma complesso oppure una normale form. Se non specificato diversamente, la chiusura di questa prima form lanciata, termina automaticamente il programma.
      La parola static (Shared) davanti al metodo indica che esso può essere eseguito senza la necessità di istanziare un oggetto. Il metodo Main deve obbligatoriamente essere definito in questo modo.
      La parola void (Sub) indica che il metodo Main non ha un valore di ritorno. E’ possibile, se necessario, far restituire dal metodo Main un valore intero che indichi lo stato della chiusura dell’applicazione e può essere utilizzato da una eventuale applicazione chiamante.
      Nel nostro esempio, il metodo Main non ha parametri di input nella sua chiamata, ma, volendo, è possibile passare al Main un array di parametri stringa cambiandone la dichiarazione in static void Main(string[] Args) in C# o Shared Sub Main(Args As String()) in VB: in questo caso, Args contiene un array di tutti i parametri passati da linea di comando.
      Quando lanciamo una applicazione è possibile passargli dei parametri, es. winword.exe mydocument.doc apre il documento specificato in Word.
      Oppure MyProgram.exe /o /b /d pippo.txt farebbe in modo che il parametro Args del Main di MyProgram.exe contenga un array con i seguenti valori: “/o“, “/b“, “/d“, “pippo.txt“.
  •  Application.EnableVisualStyles
    •  è la chiamata ad un metodo statico della classe Application fornita dal Framework .NET che attiva automaticamente la visualizzazione delle form nello stile di Windows XP; senza questa chiamata, l’applicazione viene visualizzata nello stile di Windows vecchia modalità.
  •  Application.SetCompatibleTextrenderingDefault(false)
    •  è la chiamata ad un altro metodo statico della classe Application; questo metodo è nuovo del Framework 2.0 e attiva una serie di funzionalità per l’ottimizzazione della visualizzazione delle modifiche ai testi dovute alla localizzazione fornite dalla GDI+ ai controlli di nuova concezione.
      Vi rimandiamo a MSDN per saperne di più. La specifica avvisa di non usare questa funzionalità se la vostra applicazione è eseguita all’interno di un browser.
  •  Application.Run(new FrmMain())
    •  La cosa più importante del metodo Main: la chiamata al metodo Run della classe Application, che istanzia la nostra form principale.
      Application.Run, informa il sistema che l’oggetto FrmMain governa l’applicazione: quando l’oggetto viene rilasciato, l’applicazione si chiude.
      new FrmMain istanzia la nostra classe form e la visualizza con il suo metodo Show.

La classe FrmMain
Si tratta della classe principale della nostra applicazione.
Visto che abbiamo creato questa applicazione solo per dimostrare l’uso dei controlli e delle form, abbiamo inserito all’interno di questa form anche tutte le funzionalità che usualmente sono delegate alle classi dati e alle classi di business logic.
Questo tipo di impostazione ci dà modo di sviluppare velocemente la nostra applicazione, però si rivelerebbe controproducente se in seguito volessimo evolvere l’applicazione aggiungendo nuove funzionalità. Pertanto, pur essendo una cosa buona per una applicazione didattica, vi sconsigliamo di sviluppare così le applicazioni per uso nella vostra azienda o per la vendita. Per vedere una struttura più solida per applicazioni con codice riusabile, confrontatela con la già citata serie “Guarda! Senza Mani!“.

Potrete notare come, al contrario di quanto trovate in “Guarda! Senza Mani!“, qui non esiste una form MDI, e non abbiamo neppure utilizzato i MenuStrip e le ToolStrip, per ridurre la complessità al minimo e concentrarci invece sull’argomento principale: le collezioni di controlli e la “comunicazione” fra form.

Vediamo un po’ cosa abbiamo inserito dentro alla Form:

  •  Apriamo FrmMain nel designer e impostiamone:
    •  Size = 800; 580
    •  Text = Gestione Ombrelloni, O’Sole Mio s.i.e.
      (cioè ‘società a irresponsabilità esagerata’)
  •  Trasciniamo al suo interno per prima cosa un controllo SplitContainer
    •  Name = splBase
    •  Dock = Fill
    •  Orientation = Horizontal
    •  SplitterDistance = 87
    •  Modifichiamo il Backcolor del pannello 1 a 255; 255; 192 (giallo chiaro nel pannello dei colori personalizzati)
    •  Modifichiamo il Backcolor del pannello 2 a 192; 192; 255 (azzurro chiaro nel pannello dei colori personalizzati)
      (potete anche selezionare la parola che compare e sovrascrivervi le cifre indicate)
  •  Trasciniamo sulla form un controllo di tipo ToolTip, che apparirà come nella foto in fondo alla form e chiamiamolo ttp.
    La presenza di questo controllo aggiungerà a tutti i controlli presenti sulla form una proprietà che si chiama Tooltip on ttp in cui possiamo scrivere il testo dei tooltip dei controlli che inseriremo sulla form.
  •  Nel Panel 1 aggiungiamo una Label
    •   Name = lblDateFrom
    •   AutoSize = false
    •   Location = 3;26
    •   Text = Dalla Data:
    •   TextAlign = MiddleRight
  •   Duplichiamo (copia-incolla) la label modificata in una nuova Label
    •   Name = lblDateTo
    •   Text = Alla Data:
  •   Allo stesso modo, aggiungiamo due DateTimePicker
    •   Name = dtpDateFrom e dtpDateTo
    •   Format = Short
    •   Size = 100; 20
    •   Tooltip on ttp = rispettivamente “Data inizio periodo selezionato” e “Data fine periodo selezionato”.
  •   Allo stesso modo, aggiungiamo due button
    •   Name = btnUpdateImages e btnNewBooking
    •   Size = 90; 45
    •   Text = “Aggiorna Vista” e “Genera Prenotazione”
    •   Tooltip on ttp = “Fa un refresh dello stato delle immagini della form” e “Genera una nuova prenotazione per l’ombrellone attualmente selezionato”.
  •   Duplichiamo una delle due label già generate (per mantenere le impostazioni)
    •   Name = lblSelectedItem
    •   Font.Size = 14
    •   Font.Bold = true
    •   ForeColor = Darkblue
    •   Text = Selezionato:
  •   Duplichiamo un’altra label da una delle precedenti
    •   Name = lblSelected
    •   BackColor = Coral
    •   Font.Size = 24
    •   Font.Bold = true
    •   ForeColor = Gold
    •   Text … (sarà gestito a runtime)
    •   TextAlign = MiddleCenter

Posizioniamo tutti i controlli come nell’immagine e i controlli della nostra form sono tutti al loro posto.
Tranne ovviamente la collezione degli ombrelloni, che invece verrà generata a runtime, in modo che appaia così:

E’ un pochino diversa, giusto? allora vediamo come fare a ottenere quanto si vede nell’immagine.
Ma prima di scrivere codice, bisogna che ci occupiamo un pochino di grafica.

  •   Apriamo le Proprietà del progetto, facendo doppio click su ‘My Project” nelSolution Explorer.
  •   Selezioniamo la scheda Resources
  •   Selezioniamo Images come tipo di risorsa
  •   Dal menu Add Resource, selezioniamoAdd Existing File
  •   e andiamo a selezionare i file delle icone che ci servono per rappresentare i nostri ombrelloni.
    Non abbiamo trovato qualcosa di più interessante della prima immagine a sinistra, che abbiamo modificato per ottenere le altre.
    Le immagini si trovano nel progetto allegato, nella cartella Resources.

Le icone rappresentano le seguenti situazioni:

  1.   neutro - ombrellone senza alcuna prenotazione
  2.   mezzo - ombrellone parzialmente prenotato per il periodo prescelto
  3.   libero - l’ombrellone ha prenotazioni ma non nel periodo prescelto
  4.   intero - ombrellone totalmente prenotato per il periodo prescelto
  5.   selected - ombrellone selezionato

Aggiungiamo ancora una risorsa, ovvero un’icona da utilizzare su tutte le form dell’applicazione al posto di quella standard, il che è un mini tocco di professionalità. Selezioniamo quindi il tipo di risorsa Icon e aggiungiamo il file btn361.ico, la pallina blu.

Ed ora dedichiamoci al codice della classe, cominciando dalla struttura della classe FrmMain, con le direttive using (Imports):

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Text;

namespace ControlCollection
{
  public partial class FrmMain : Form
  {

  }
}
Imports System
Imports System.Collections.Generic
Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Data
Imports System.Drawing
Imports System.IO
Imports System.Text

Public Class FrmMain

End Class

Per i principianti che forse non lo sanno, queste direttive sono un sistema di “alias” per i namespace del Framework o che generiamo noi.
Quando non vi fosse la direttiva using (Imports) System.Data, per riferirsi ad un oggetto definito in tale namespace dovremmo scrivere ad esempio:

System.Data.DataTable MyDataTable = new System.Data.DataTable();
Dim MyDataRable As New System.Data.DataTable

Se invece la clausola using (Imports) ci fosse, come nel nostro caso, ci basterebbe scrivere:

DataTable MyDataTable = new DataTable();
Dim MyDataRable As New DataTable

Per sistema di “alias” si intende dire che la sintassi reale di using (Imports)è:

using [alias = ] namespace;
Imports [alias = ] namespace

Questo perché potremmo avere dei (Sub)Namespace con lo stesso nome che usiamo in una stessa classe e quindi non potremmo utilizzare la clausola using (Imports) su entrambi. Per esempio, nelle sue librerie personali, Sabrina ha i seguenti namespace:

  •   ForYou.Base.Collections
  •   ForYou.Base.Data.Collections

Supponendo che in una classe Sabrina debba utilizzare un oggetto il cui nome è contenuto in entrambi i namespace, potrebbe utilizzare la direttiva using in questo modo:

using FyBCollections = ForYou.Base.Collections;
using FyDCollections = ForYou.Base.Data.Collections;

Mentre se ne usasse uno soltanto potrebbe scrivere

using ForYou.Base.Collections;

Pertanto lo using (Imports) senza alcun alias è solo un caso “estremo” di compattazione, ove l’alias è “” (stringa vuota).

    public const string FLD_Code = "Code";
    public const string FLD_DateFrom = "DateFrom";
    public const string FLD_DateTo = "DateTo";
    public const string FLD_ID = "ID";
    public const string FLD_Note = "Note";
    private const string FMP_Code = "{0}{1}";
    private const string FMP_CodeNumber = "0#";
    private const string FMP_CodeOnBitmap = "Cod. {0}";
    private const string FMP_FilterID = "{0}={1}";
    private const string FMP_FilterUmbrella = "{0}='{1}'";
    private const string FONT_Arial = "Arial";
    private const char LetterBase = 'A';
    private const string TbBookings = "TbPrenotazioni";
    private const string TbUmbrellas = "TbOmbrelloni";
    private const string TXT_ColHeadCode = "Codice";
    private const string TXT_ColHeadDateFrom = "Dal";
    private const string TXT_ColHeadDateTo = "Al";
    private const string TXT_ColHeadID = "ID";
    private const string TXT_ColHeadNote = "Nota/Dati Acquirente";
    private const string TXT_FileBookings = "bookings.xml";
    private const string Txt_MsgUpdate = "Prenotazioni aggiornate.";
    private const string TXT_SampleCode1 = "A01";
    private const string TXT_SampleCode2 = "J01";
    private const string TXT_SampleNote1 = 
                                     "Sig. Mario Rossi, Via delle conchiglie 12, Tel. 335 12345678";
    private const string TXT_SampleNote2 = 
                                     "Sig. Giuseppe Verdi, Via delle magnolie 24, Tel. 335 2334455";
    private const string TXT_SampleNote3 = 
                                "Sig. Franco Bianchi, Via delle stelle marine 26, Tel. 335 7654321";
    private const int UmbrellaColumns = 20;
    private const int UmbrellaRows = 15;
  Public Const FLD_Code As String = "Code"
  Public Const FLD_DateFrom As String = "DateFrom"
  Public Const FLD_DateTo As String = "DateTo"
  Public Const FLD_ID As String = "ID"
  Public Const FLD_Note As String = "Note"
  Private Const FMP_Code As String = "{0}{1}"
  Private Const FMP_CodeNumber As String = "0#"
  Private Const FMP_CodeOnBitmap As String = "Cod. {0}"
  Private Const FMP_FilterID As String = "{0}={1}"
  Private Const FMP_FilterUmbrella As String = "{0}='{1}'"
  Private Const FONT_Arial As String = "Arial"
  Private Const LetterBase As Char = "A"c
  Private Const TbBookings As String = "TbPrenotazioni"
  Private Const TbUmbrellas As String = "TbOmbrelloni"
  Private Const TXT_ColHeadCode As String = "Codice"
  Private Const TXT_ColHeadDateFrom As String = "Dal"
  Private Const TXT_ColHeadDateTo As String = "Al"
  Private Const TXT_ColHeadID As String = "ID"
  Private Const TXT_ColHeadNote As String = "Nota/Dati Acquirente"
  Private Const TXT_FileBookings As String = "bookings.xml"
  Private Const Txt_MsgUpdate As String = "Prenotazioni aggiornate."
  Private Const TXT_SampleCode1 As String = "A01"
  Private Const TXT_SampleCode2 As String = "J01"
  Private Const TXT_SampleNote1 As String = _
                                      "Sig. Mario Rossi, Via delle conchiglie 12, Tel. 335 12345678"
  Private Const TXT_SampleNote2 As String = _
                                      "Sig. Giuseppe Verdi, Via delle magnolie 24, Tel. 335 2334455"
  Private Const TXT_SampleNote3 As String = _
                                 "Sig. Franco Bianchi, Via delle stelle marine 26, Tel. 335 7654321"
  Private Const UmbrellaColumns As Integer = 20
  Private Const UmbrellaRows As Integer = 15

Le costanti. Le troviamo all’inizio della classe, ma, ovviamente, non sono la prima cosa che abbiamo definito: le abbiamo aggiunte man mano che abbiamo steso il codice, ovunque necessitavamo di un valore di tipo “hardcoded”.
Come potete vedere, ci sono molte stringhe, un carattere e degli interi.
Perché usiamo delle costanti per le stringhe, costringendoci (sembrerebbe) a scrivere codice due volte?

  1.   il compilatore ottimizza le costanti e quindi spreca meno codice a farvi controlli rispetto a quando usiamo delle variabili.
  2.   Per le stringhe, inoltre, l’uso delle costanti ci permette di averle tutte in uno stesso punto del codice, e se decidiamo di fare un programma multilingua, ci basta poco sforzo per trasferire le costanti nelle resources ove possono essere poi tradotte in varie lingue e culture.
  3.   usare una costante invece di ripetere una stringa più e più volte nel codice, ci evita errori di digitazione ed eccezioni nell’esecuzione del codice, ad esempio quando andiamo a manipolare DataRows ove le colonne sono usualmente richiamate con il loro nome ed è necessario rispettare il character casing (maiuscole e minuscole).

La lettera e i due numeri che abbiamo utilizzato in questa classe, invece, sono i parametri base di configurazione della matrice dei controlli, che partono da A01 per 15 righe di 20colonne. Basta cambiare questi parametri per cambiare la forma della spiaggia virtuale.

    private readonly static string mClassName =
      System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;

    private readonly string mBookingsFileName;
    DataTable mDtBookings;
    DataTable mDtUmbrellas;
    FrmBookings mFrmBookings;
    Font mImageFont;
    PictureBox mSelectedPix;
    string mSelectedUmbrella;
  Private Shared ReadOnly mClassName As String = _
    System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name

  Private ReadOnly mBookingsFileName As String
  Private mDtBookings As DataTable
  Private mDtUmbrellas As DataTable
  Private mFrmBookings As FrmBookings
  Private mImageFont As Font
  Private mSelectedPix As PictureBox
  Private mSelectedUmbrella As String

I campi della classe. Il primo, mClassName, lo trovate in tutte le classi di Sabrina ed è una variabile che contiene il nome della classe ottenuto via Reflection; questa variabile è utilizzata nella gestione delle eccezioni, per permettere di verificare in quale classe è sorta un’eccezione e qual è la catena di chiamate che l’ha provocata.
Gli altri campi sono i seguenti:

  •   mBookingsFileName - Nome del file dati ove memorizzeremo la lista delle prenotazioni (Bookings)
  •   mDtBookings - DataTable che contiene la lista delle prenotazioni.
  •   mDtUmbrellas - DataTable di supporto che contiene la lista degli ombrelloni e serve a costruire la matrice dei controlli.
  •   mFrmBookings - Variabile che contiene un riferimento alla form Owned che visualizza la lista prenotazioni dell’ombrellone correntemente selezionato.
  •   mImageFont - Font utilizzato per scrivere i nomi degli ombrelloni su ciascuna delle immagini che li rappresentano.
  •   mSelectedPix - Picturebox correntemente selezionata
  •   mSelectedUmbrella - Codice dell’ombrellone correntemente selezionato

Se non lo aveste ancora notato, tutti i campi delle classi hanno il nome in camelCase ed inizia per m (member variable) questa è la nostra convenzione di denominazione (una di quelle su cui siamo d’accordo).

    public FrmMain()
    {

    }
  Public Sub New()

  End Sub

Il costruttore della form, il cui codice esamineremo man mano. Qui iniziamo a vedere un po’ di cose interessanti:

      InitializeComponent();
    InitializeComponent()

Il metodo InitializeComponent è un metodo automaticamente generato e gestito dal designer della form e si occupa di istanziare e inizializzare i controlli che abbiamo inserito a design sulla form.
Non si trova nel file FrmMain.cs (vb), ma nel file FrmMain.design.cs (vb): una partial class, generata automaticamente dal template delle Windows Forms, che contiene tutto il codice automaticamente generato dal designer di Visual Studio.
Se aprite il file e andate a curiosarvi, vi troverete, oltre al metodo descritto, anche le dichiarazioni dei controlli che abbiamo trascinato sulla form e gestito graficamente. Usualmente il codice della parte design di una classe form o controllo non si modifica manualmente, perché viene rigenerato dal designer, ma modificarlo manualmente con un po’ di attenzione è possibile, la modifica manuale viene riportata automaticamente poi a design.

      this.Icon = Properties.Resources.btn361;
    Me.Icon = My.Resources.btn361

L’assegnazione dell’icona. Come potete vedere, l’icona btn361 che abbiamo messo nelle risorse è accessibile direttamente tramite la proprietà Properties.Resources.btn361, perché il Resource designer di Visual Studio 2005 produce automaticamente una classe con il codice che espone per ogni oggetto contenuto nelle risorse il necessario a gestirlo.
Vi invitiamo a curiosare nel codice del file generato da visual studio per osservare come è costruito (ve ne mostriamo solo una parte).

        internal static System.Drawing.Icon btn361 {
            get {
                object obj = ResourceManager.GetObject("btn361", resourceCulture);
                return ((System.Drawing.Icon)(obj));
            }
        }
        Friend ReadOnly Property btn361() As System.Drawing.Icon
            Get
                Dim obj As Object = ResourceManager.GetObject("btn361", resourceCulture)
                Return CType(obj,System.Drawing.Icon)
            End Get
        End Property

Come potete notare, il file delle risorse contiene codice C# (o VB) autogenerato. Curiosarvi, così come curiosare nel codice del designer delle form, può tornare utile per capire come scrivere alcune cose all’interno dei nostri programmi.

      mBookingsFileName = 
                  Path.Combine(Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), 
                                                         TXT_FileBookings);
    mBookingsFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), _
                                     TXT_FileBookings)

Generiamo il nome del file in cui andremo a memorizzare le prenotazioni. Questo file sarà memorizzato sulla cartella Documenti dell’utente.
Visto il fine didattico di questo progetto, usiamo questa variabile come scusa per spiegare come utilizzare la classe Environment del .NET Framework per ottenere una delle cartelle speciali del sistema operativo, in questo caso la cartella Documenti. Questa cartella si può ottenere utilizzando il metodo GetFolderPath e l’enumerazioneSystem.Environment.SpecialFolder, che permette di acquisire il path completo di questa cartella.
Il metodo Combine della classe Path compone il nome della cartella e la costante con il nome del file da noi predisposta, per darci il path completo del file delle prenotazioni.

C#
VB

Come possiamo vedere dalle immagini, l’Intellisense ci espone l’enumerazione SpecialFolder con la lista di tutte le cartelle speciali e configurabili del sistema operativo.
Il valore che otterremo è simile a C:\Documents and Settings\UserName\Documenti\bookings.xml

      mDtUmbrellas = new DataTable();
      mDtUmbrellas.TableName = TbUmbrellas;
      mDtUmbrellas.Columns.Add(new DataColumn(FLD_Code, typeof(string)));
      mDtUmbrellas.PrimaryKey = new DataColumn[] { mDtUmbrellas.Columns[FLD_Code] };
    Dim keys(0) As DataColumn

    mDtUmbrellas = New DataTable
    mDtUmbrellas.TableName = TbUmbrellas
    mDtUmbrellas.Columns.Add(New DataColumn(FLD_Code, Type.GetType("System.String")))
    keys(0) = mDtUmbrellas.Columns(FLD_Code)
    mDtUmbrellas.PrimaryKey = keys

Questo codice costruisce in fase di esecuzione una datatable molto semplice che al suo interno conterrà la lista degli ombrelloni. Abbiamo usato la DataTable per semplicità e per utilizzare degli strumenti coerenti. Ci sono cento oggetti diversi che avrebbero potuto ospitare questa lista (collezioni, classi costruite ad hoc eccetera). In questo caso, mostriamo la DataTable anche per far vedere come sia facile crearne una via codice.
Vediamo l’uso delle costanti TbUmbrellas, (nome tabella) e FLD_Code, nome della colonna.
Abbiamo creato una tabella dati, con una sola colonna che è anche chiave univoca della stessa.

      mDtBookings = new DataTable();
      mDtBookings.TableName = TbBookings;
      mDtBookings.Columns.Add(new DataColumn(FLD_ID, typeof(int)));
      mDtBookings.Columns.Add(new DataColumn(FLD_Code, typeof(string)));
      mDtBookings.Columns.Add(new DataColumn(FLD_DateFrom, typeof(DateTime)));
      mDtBookings.Columns.Add(new DataColumn(FLD_DateTo, typeof(DateTime)));
      mDtBookings.Columns.Add(new DataColumn(FLD_Note, typeof(string)));
      mDtBookings.Columns[FLD_ID].AutoIncrement = true;
      mDtBookings.Columns[FLD_ID].AutoIncrementSeed = 1;
      mDtBookings.Columns[FLD_ID].AutoIncrementStep = 1;
      mDtBookings.PrimaryKey = new DataColumn[] { mDtBookings.Columns[FLD_ID] };
    mDtBookings = New DataTable
    With mDtBookings
      .TableName = TbBookings
      .Columns.Add(New DataColumn(FLD_ID, Type.GetType("System.Int32")))
      .Columns.Add(New DataColumn(FLD_Code, Type.GetType("System.String")))
      .Columns.Add(New DataColumn(FLD_DateFrom, Type.GetType("System.DateTime")))
      .Columns.Add(New DataColumn(FLD_DateTo, Type.GetType("System.DateTime")))
      .Columns.Add(New DataColumn(FLD_Note, Type.GetType("System.String")))
      .Columns(FLD_ID).AutoIncrement = True
      .Columns(FLD_ID).AutoIncrementSeed = 1
      .Columns(FLD_ID).AutoIncrementStep = 1
      keys(0) = .Columns(FLD_ID)
      .PrimaryKey = keys
    End With

Questo snippet di codice genera invece la tabella per noi più importante, ovvero la tabella delle prenotazioni, la sola che poi salveremo su disco.
Vediamo come sia semplice definire colonne con tipi dati predefiniti, interi, stringhe, date. Vediamo inoltre come assegnare ad una colonna un contatore autoincrementante e come predisporre un vincolo di chiave primaria sull’ID della prenotazione.

      mSelectedUmbrella = string.Empty;
      mSelectedPix = null;
      mImageFont = new Font(FONT_Arial, 8, FontStyle.Bold, GraphicsUnit.Point);

      InitBookingsList();
    mSelectedUmbrella = String.Empty
    mSelectedPix = Nothing
    mImageFont = New Font(FONT_Arial, 8, FontStyle.Bold, GraphicsUnit.Point)

    InitBookingsList()

Terminiamo il costruttore azzerando le due variabili usate per la selezione dell’ombrellone su cui lavorare e definendo la Font con cui scrivere sulle immagini.
Per ultimo, chiamiamo il metodo che inizializza la form di visualizzazione della lista prenotazioni di un singolo ombrellone, cioè un metodo che sappiamo di dover implementare, ma che non abbiamo ancora scritto; e non abbiamo neanche la form con la sua lista. Quindi, scriviamo solo la firma del metodo, per non vedere Visual Studio che ci ammonisce della sua assenza:

    private void InitBookingsList()
  Private Sub InitBookingsList()

…e passiamo a creare lo scheletro della form per le prenotazioni.

  •  Aggiungiamo una form al progetto e chiamiamola FrmBookings
  •  al suo interno, aggiungiamo solo due controlli:
    •  Button
      •  Name = btnDelete
      •  Dock = top
      •  Text = Cancella prenotazione selezionata
    •  ListView
      •  Name = lvBookings
      •  Dock = Fill
  • Nel suo codice, aggiungiamo solo una proprietà:

    public ListView BookingsList
    {
      get
      {
        return lvBookings;
      }
    }
  Public ReadOnly Property BookingsList() As ListView
    Get
      Return lvBookings
    End Get
  End Property

Al resto ci penseremo poi, adesso torniamo alla FrmMain e aggiungiamo ancora soltanto la firma di un metodo che implementeremo in seguito:

    void BookingList_MouseDoubleClick(object sender, MouseEventArgs e)
  Private Sub BookingList_MouseDoubleClick(ByVal sender As Object, ByVal e As MouseEventArgs)

Tutte queste ‘preparazioni’ vengono fatte avendo in testa un certo scenario, una certa struttura di programma, per cui prevediamo ci serviranno degli oggetti, ma stiamo implementando un metodo che li usa e vogliamo concentrarci su di esso. Nello stesso tempo abbiamo bisogno delle facilitazioni di Intellisense relative a quegli oggetti. Quindi ci limitiamo a crearli per ‘vederli’. In seguito apporteremo quanto necessario.

Il metodo InitBookingList

    private void InitBookingsList()
    {
      try
      {
        this.mFrmBookings = new FrmBookings();
        this.mFrmBookings.Icon = this.Icon;

        this.mFrmBookings.BookingsList.BeginUpdate();

        this.mFrmBookings.BookingsList.Columns.Add(TXT_ColHeadID, 60);
        this.mFrmBookings.BookingsList.Columns.Add(TXT_ColHeadCode, 60);
        this.mFrmBookings.BookingsList.Columns.Add(TXT_ColHeadDateFrom, 100);
        this.mFrmBookings.BookingsList.Columns.Add(TXT_ColHeadDateTo, 100);
        this.mFrmBookings.BookingsList.Columns.Add(TXT_ColHeadNote, 300);
        this.mFrmBookings.BookingsList.GridLines = true;
        this.mFrmBookings.BookingsList.FullRowSelect = true;
        this.mFrmBookings.BookingsList.MultiSelect = false;

        this.mFrmBookings.BookingsList.MouseDoubleClick += 
                                                new MouseEventHandler(BookingList_MouseDoubleClick);

      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
      finally
      {
        this.mFrmBookings.BookingsList.EndUpdate();
      }
    }
  Private Sub InitBookingsList()

    Try
      Me.mFrmBookings = New FrmBookings
      Me.mFrmBookings.Icon = Me.Icon

      Me.mFrmBookings.BookingsList.BeginUpdate()

      Me.mFrmBookings.BookingsList.Columns.Add(TXT_ColHeadID, 60)
      Me.mFrmBookings.BookingsList.Columns.Add(TXT_ColHeadCode, 60)
      Me.mFrmBookings.BookingsList.Columns.Add(TXT_ColHeadDateFrom, 100)
      Me.mFrmBookings.BookingsList.Columns.Add(TXT_ColHeadDateTo, 100)
      Me.mFrmBookings.BookingsList.Columns.Add(TXT_ColHeadNote, 300)
      Me.mFrmBookings.BookingsList.GridLines = True
      Me.mFrmBookings.BookingsList.FullRowSelect = True
      Me.mFrmBookings.BookingsList.MultiSelect = False

      AddHandler Me.mFrmBookings.BookingsList.MouseDoubleClick, _
                                                              AddressOf BookingList_MouseDoubleClick

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    Finally
      Me.mFrmBookings.BookingsList.EndUpdate()

    End Try

  End Sub

Per visualizzare la lista delle prenotazioni dell’ombrellone selezionato dall’operatore sulla form principale, utilizzeremo una form che genereremo in modalità Owned ovvero come “appendice” della FrmMain, questa form verrà definita dalla classe FrmBookings appena predisposta, sarà impostata in modo da essere sempre in primo piano e conterrà una lista delle prenotazioni dell’ombrellone correntemente selezionato.

Per predisporre la form prenotazioni, la generiamo (New) e la riferiamo all’apposito campo (mFrmBookings) che abbiamo definito in testa alla form principale, le assegnamo l’icona e poi vediamo un primo modo di manipolare una form da un’altra form.

Anche se non l’abbiamo ancora completata, la form FrmBookings contiene al suo interno un controllo ListView, esposto con una proprietà pubblica (BookingsList) che ci permette di vederlo da questa form.
Utilizziamo quindi le proprietà di questo controllo per definire come la lista verrà visualizzata.

E’ interessante vedere come abbiamo approfittato di questo semplice metodo per dimostrare l’uso di Try Catch Finally, per quanto sia improbabile un’eccezione in questo frangente. I metodi BeginUpdate e EndUpdate hanno lo scopo di inibire il repaint automatico della lista mentre viene modificata e ripristinarlo a modifiche terminate. Questo rende molto veloce l’aggiornamento della lista stessa. Il fatto di mettere EndUpdate nella parte Finally della gestione errori fa in modo che la lista si ridisegni correttamente anche qualora si verificasse una eccezione, onde evitare una finestra “bucata” a video.

Cosa facciamo all’interno della ListView? Innanzitutto creiamo le colonne che rappresentano i campi della tabella TbBookings, attiviamo la visualizzazione della griglia attorno agli elementi della lista, attiviamo la modalità per cui al click viene selezionata una riga intera e infine indichiamo che si può selezionare solo una riga per volta.
L’ultima istruzione, la più interessante aggiunge un EventHandler all’evento MouseDoubleClick della ListView (che si trova su una form diversa da quella attuale), che ci permetterà di gestire la modifica di una prenotazione. Il metodo BookingList_MouseDoubleClick verrà esaminato in seguito, così come vedremo l’aggiunta dei controlli allaFrmBookings, adesso andiamo con ordine e passiamo dal costruttore al caricamento della form.

L’event handler dell’evento Load della form
Il metodo eseguito al caricamento della form genera la spiaggia, carica le prenotazioni esistenti e predispone la form che visualizzerà la lista prenotazioni per l’ombrellone selezionato.

    private void FrmMain_Load(object sender, EventArgs e)
    {
      try
      {
        this.StartPosition = FormStartPosition.Manual;
        this.Location = new Point(0, 0);
  Private Sub FrmMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

    Try
      Me.StartPosition = FormStartPosition.Manual
      Me.Location = New Point(0, 0)

La modifica a queste proprietà fa in modo che la nostra form appaia automaticamente nell’angolo in alto a sinistra del desktop alla partenza.

        char letter1 = LetterBase;
        for (int i = 0; i < UmbrellaRows; i++)
        {
          for (int j = 0; j < UmbrellaColumns; j++)
          {
            DataRow drO = mDtUmbrellas.NewRow();
            drO[FLD_Code] = string.Format(FMP_Code, letter1, (j + 1).ToString(FMP_CodeNumber));
            mDtUmbrellas.Rows.Add(drO);
          }
          letter1++;
        }
      Dim b As Integer = Char.ConvertToUtf32(LetterBase.ToString, 0)
      For i As Integer = 0 To UmbrellaRows - 1
        Dim letter1 As Char = Char.ConvertFromUtf32(b).Chars(0)
        For j As Integer = 0 To UmbrellaColumns - 1
          Dim drO As DataRow = mDtUmbrellas.NewRow
          drO(FLD_Code) = String.Format(FMP_Code, letter1, (j + 1).ToString(FMP_CodeNumber))
          mDtUmbrellas.Rows.Add(drO)
        Next
        b += 1
      Next

Questo ciclo genera gli ombrelloni all’interno della tabella TbUmbrellas che abbiamo predisposto nel costruttore, la tabella servirà poi per generare la spiaggia virtuale. La sola cosa da notare, oltre all’uso delle costanti già definite per descrizioni e nomi di campi, è l’uso un po’ anomalo della variabile letter1 che genera la parte alfabetica dei nomi degli ombrelloni, che viene inizializzata con ‘A‘ e poi incrementata (in C#) come fosse un numero. Infatti in VB non è permesso questo incremento e Diego ricorre ad una variabile intera ed ai metodi di encoding forniti dalla classe Char. Come è noto, Diego rifugge dall’usare funzioni decrepite (più che obsolete) come ASC e CHR

        LoadBookings();
        BuildImages();
      LoadBookings()
      BuildImages()

Di questi due metodi, che vedremo in dettaglio nel seguito, l’uno carica le prenotazioni salvate (se ve ne sono) dal file prenotazioni e l’altro genera la spiaggia virtuale.

        mFrmBookings.BookingsList.Items.Clear();
        mFrmBookings.StartPosition = FormStartPosition.Manual;
        mFrmBookings.Location = new Point(this.Width - 100, 25);
        mFrmBookings.Owner = this;
        mFrmBookings.Show();
      mFrmBookings.BookingsList.Items.Clear()
      mFrmBookings.StartPosition = FormStartPosition.Manual
      mFrmBookings.Location = New Point(Me.Width - 100, 25)
      mFrmBookings.Owner = Me
      mFrmBookings.Show()

Quest’ultima parte del codice svuota la lista prenotazioni e visualizza nell’angolo in alto a destra della nostra form principale la form che conterrà la lista prenotazioni dell’ombrellone selezionato dall’operatore sulla spiaggia virtuale. Da notare solo la proprietà Owner della form che viene inizializzata con un riferimento alla form corrente (FrmMain) per indicare che la seconda form è di sua proprietà. Il fatto che la form sia Owned fa anche in modo che rimanga sempre in primo piano rispetto al suo Owner.

Il metodo LoadBookings, nel caso non ci sia il file delle prenotazioni, ne genera tre, usando il metodo AddBooking, che spiegheremo più avanti. Però, poiché ci serve adesso, ne scriviamo la sola firma:

    public void AddBooking(string pCode, DateTime pDateFrom, DateTime pDateTo, string pNote)
    {
    }
  Private Sub AddBooking(ByVal code As String, ByVal dateFrom As DateTime, ByVal dateTo As DateTime, ByVal note As String)

  End Sub

Il metodo LoadBookings

    private void LoadBookings()
    {
      try
      {

        FileInfo fInfo = new FileInfo(mBookingsFileName);
        if (fInfo.Exists)
        {
          mDtBookings.ReadXml(fInfo.FullName);
        }
        else
        {
          DataRow drP = mDtBookings.NewRow();
          AddBooking(TXT_SampleCode1
          , new DateTime(2007, 6, 01)
          , new DateTime(2007, 6, 15)
          , TXT_SampleNote1);

          AddBooking(TXT_SampleCode1
          , new DateTime(2007, 6, 16)
          , new DateTime(2007, 6, 21)
          , TXT_SampleNote2);
          AddBooking(TXT_SampleCode2
            , new DateTime(2007, 6, 01)
            , new DateTime(2007, 6, 21)
            , TXT_SampleNote3);
        }
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Private Sub LoadBookings()

    Try
      Dim fInfo As New FileInfo(mBookingsFileName)
      If fInfo.Exists Then
        mDtBookings.ReadXml(fInfo.FullName)
      Else
        Dim drP As DataRow = mDtBookings.NewRow
        AddBooking(TXT_SampleCode1, New DateTime(2007, 6, 1), New DateTime(2007, 6, 15), _
                   TXT_SampleNote1)
        AddBooking(TXT_SampleCode1, New DateTime(2007, 6, 16), New DateTime(2007, 6, 21), _
                   TXT_SampleNote2)
        AddBooking(TXT_SampleCode2, New DateTime(2007, 6, 1), New DateTime(2007, 6, 21), _
                   TXT_SampleNote3)
      End If

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try

  End Sub

Questo metodo carica informazioni dal file XML, il cui nome abbiamo generato nel costruttore, nella tabella mDtBookings.
Se il file stesso esiste, lo legge. altrimenti, ai fini didattici, genera tre record di test sulla tabella.
Come possiamo vedere, la lettura del file dati viene effettuata con una sola istruzione ricorrendo al metodo ReadXml della nostra DataTable. Esso deserializza la tabella dal file su disco dove è stata salvata (vedremo come salvarla più avanti).
Per l’aggiunta dei record di test, come già indicato, utilizziamo il metodo AddBooking, che spiegheremo in seguito, quando parleremo della form di aggiunta e modifica dati.

Il metodo BuildImages
Questo è uno dei metodi più importanti del progetto, il metodo che si occupa di generare la spiaggia virtuale, ovvero la nostra collezione di controlli.
Per rappresentare gli ombrelloni, abbiamo scelto il controllo Picturebox.
Per decidere che tipo di immagine esporre e per predisporla, facciamo uso di due metodi, GetImageType e GetBitmap, che non abbiamo ancora scritto, di cui ci serve la firma. Poi ci viene comodo usare una enumerazione per il tipo di immagine. Inoltre, dobbiamo assegnare il gestore dell’evento Click su un ombrellone. Ergo, ecco le preparazioni, e di seguito il codice del metodo:

  public enum ImageType : int
  {
    neutro = 0,
    libero,
    mezzo,
    intero,
    selected
  }

    public ImageType GetImageType(string pName)
    {
    }

    private Bitmap GetBitmap(Font pFont, string pName, ImageType pImageType)
    {
    }

    void pb_Click(object sender, EventArgs e)
    {
    }
Public Enum ImageType As Integer
  neutro = 0
  libero
  mezzo
  intero
  selected
End Enum

  Public Function GetImageType(ByVal name As String) As ImageType

  End Function

  Private Function GetBitmap(ByVal font As Font, ByVal nome As String, _
                             ByVal imageType As ImageType) As Bitmap

  End Function

  Private Sub pb_Click(ByVal sender As Object, ByVal e As EventArgs)

  End Sub
    private void BuildImages()
    {
      try
      {
        int k = 0;
        int z = 0;
        for (int i = 0; i < mDtUmbrellas.Rows.Count; i++)
        {
          PictureBox pb = new PictureBox();
          pb.Name = mDtUmbrellas.Rows[i][FLD_Code].ToString();
          pb.Size = new Size(32, 32);
          pb.Location = new Point(3 + (35 * z), 3 + (35 * k));
          ImageType tipo = GetImageType(pb.Name);
          Bitmap bmp = GetBitmap(mImageFont, pb.Name, tipo);
          pb.Image = bmp;
          pb.Tag = tipo;
          pb.SizeMode = PictureBoxSizeMode.Zoom;
          pb.Click += new EventHandler(pb_Click);
          ttp.SetToolTip(pb, string.Format("{0} - {1}", pb.Name, tipo));
          this.splBase.Panel2.Controls.Add(pb);
          z++;
          if (z >= 20)
          {
            k++;
            z = 0;
          }

        }
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Private Sub BuildImages()

    Try
      Dim k As Integer = 0
      Dim z As Integer = 0
      For i As Integer = 0 To mDtUmbrellas.Rows.Count - 1
        Dim pb As New PictureBox
        pb.Name = mDtUmbrellas.Rows(i)(FLD_Code).ToString
        pb.Size = New Size(32, 32)
        pb.Location = New Point(3 + (35 * z), 3 + (35 * k))
        Dim tipo As ImageType = GetImageType(pb.Name)
        Dim bmp As Bitmap = GetBitmap(mImageFont, pb.Name, tipo)
        pb.Image = bmp
        pb.Tag = tipo
        pb.SizeMode = PictureBoxSizeMode.Zoom
        AddHandler pb.Click, AddressOf pb_Click
        ttp.SetToolTip(pb,string.Format("{0} - {1}", pb.Name,tipo))
        Me.splBase.Panel2.Controls.Add(pb)
        z += 1
        If z >= 20 Then
          k += 1
          z = 0
        End If
      Next

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try

  End Sub

Abbiamo utilizzato la tabella di appoggio ombrelloni creata al caricamento della form e due indici, uno per le righe e l’altro per le colonne. Con un ciclo unico e questi indici, generiamo una serie di PictureBox, che si chiamano come gli ombrelloni.
Vediamo cosa facciamo per ogni ombrellone generato:

  1.   Generiamo un controllo PictureBox
  2.   Gli diamo come nome il nome dell’ombrellone, leggendolo dalla riga della tabella, facciamo un cast a string perché i campi delle DataRow sono di tipo Object.
  3.   Dimensioniamo la PictureBox a 32 x 32 pixel
  4.   Calcoliamo con un semplice algoritmo, basato sugli indici di riga e colonna, la posizione dell’angolo superiore sinistro della PictureBox nella spiaggia virtuale.
  5.   Stabiliamo quale immagine deve rappresentare l’ombrellone utilizzando il metodo GetImageType, che si occupa di verificare nel periodo selezionato qual è lo stato dell’ombrellone. Vedremo fra poco come funziona questo metodo.
  6.   In base al tipo di immagine, il metodo GetBitmap genera l’immagine giusta con il codice ombrellone stampato sopra.
  7.   Assegnamo il Bitmap generato alla PictureBox.
  8.   Memorizziamo sulla proprietà Tag della PictureBox il suo Tipo di immagine. Questo ci servirà per lavorare furbescamente in seguito.
  9.   Predisponiamo la PictureBox in modalità Zoom aggiornando la proprietà SizeMode.
  10.   Aggiungiamo all’event handler dell’evento Click della PictureBox un metodo che useremo poi per pilotare la risposta al click delle nostre immagini. Questo metodo sarà spiegato in seguito.
  11.   Aggiorniamo il Tooltip della PictureBox in modo che visualizzi il nome e il tipo dell’immagine visualizzata.
  12.   Infine, aggiungiamo l’immagine all’insieme Controls del Panel2 dello SplitContainer inserito nella nostra form.

Il metodo GetImageType

    public ImageType GetImageType(string pName, DateTime pDateFrom, DateTime pDateTo)
    {
      try
      {
        ImageType retType = ImageType.neutro;
        string filter = string.Format(FMP_FilterUmbrella, FLD_Code, pName);
        DataRow[] rows = this.mDtBookings.Select(filter);
        if (rows.Length > 0)
        {
          Dictionary<DateTime, bool> dateList = new Dictionary<DateTime, bool>();
          DateTime dtCursor = pDateFrom;
          DateTime dtEnd = pDateTo;
          while (dtCursor <= dtEnd)
          {
            dateList.Add(dtCursor, false);
            dtCursor = dtCursor.AddDays(1);
          }

          for (int i = 0; i < rows.Length; i++)
          {

            DateTime currda = ((DateTime)rows[i][FLD_DateFrom]).Date;
            DateTime curr_a = ((DateTime)rows[i][FLD_DateTo]).Date;
            while (currda <= curr_a)
            {
              if (dateList.ContainsKey(currda))
              {
                dateList[currda] = true;
              }
              currda = currda.AddDays(1);
            }

          }
          int contaTrue = 0;
          int contaFalse = 0;
          foreach (KeyValuePair<DateTime, bool> kvp in dateList)
          {
            if (kvp.Value)
            {
              contaTrue++;
            }
            else
            {
              contaFalse++;
            }
          }
          retType = ImageType.mezzo;
          if (contaFalse == 0)
          {
            retType = ImageType.intero;
          }
          if (contaTrue == 0)
          {
            retType = ImageType.libero;
          }
        }
        return (retType);
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Public Function GetImageType(ByVal name As String, ByVal dateFrom As DateTime, _
                               ByVal dateTo As DateTime) As ImageType

    Try
      Dim retType As ImageType = ImageType.neutro
      Dim filter As String = String.Format(FMP_FilterUmbrella, FLD_Code, name)
      Dim rows As DataRow() = Me.mDtBookings.Select(filter)
      If rows.Length > 0 Then
        Dim dateList As New Dictionary(Of DateTime, Boolean)
        Dim dtCursor As DateTime = dateFrom
        Dim dtEnd As DateTime = dateTo
        While dtCursor <= dtEnd
          dateList.Add(dtCursor, False)
          dtCursor = dtCursor.AddDays(1)
        End While

        For i As Integer = 0 To rows.Length - 1
          Dim currda As DateTime = DirectCast(rows(i)(FLD_DateFrom), DateTime).Date
          Dim curr_a As DateTime = DirectCast(rows(i)(FLD_DateTo), DateTime).Date
          While currda <= curr_a
            If dateList.ContainsKey(currda) Then
              dateList(currda) = True
            End If
            currda = currda.AddDays(1)
          End While
        Next

        Dim contaTrue As Integer = 0
        Dim contaFalse As Integer = 0
        For Each kvp As KeyValuePair(Of DateTime, Boolean) In dateList
          If kvp.Value Then
            contaTrue += 1
          Else
            contaFalse += 1
          End If
        Next

        retType = ImageType.mezzo
        If contaFalse = 0 Then
          retType = ImageType.intero
        End If
        If contaTrue = 0 Then
          retType = ImageType.libero
        End If
      End If

      Return retType

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try

  End Function

Questo è uno dei metodi più importanti di questa form, in quanto stabilisce lo stato dell’ombrellone in un determinato periodo di tempo.
Non è assolutamente il solo modo possibile di farlo, né il più efficiente; certamente si potrà pensare a qualcosa di più elaborato; per il nostro esempio interessava solo un metodo che desse un dato certo, poi sta alle menti matematiche trovare un algoritmo efficiente.
Vediamo come funziona questo:

  1.   Imposta il valore di ritorno all’immagine neutra, se non c’è alcuna prenotazione su questo ombrellone, questo è il valore che verrà tornato.
  2.   Effettua una select per ottenere le righe della tabella prenotazioni che rispondono al criterio richiesto, in questo caso, molto semplicemente, tutte le prenotazioni dell’ombrellone passato come parametro.
  3.   Per verificare quali date del periodo richiesto sono occupate, creiamo una collezione Generic di tipo Dictionary(Of DateTime, Boolean) dove memorizziamo tutte le date del periodo richiesto e le impostiamo tutte come libere.
  4.   Per ciascuna prenotazione del nostro ombrellone, verifichiamo ciascuno dei giorni e se cade nell’intervallo stabilito, poniamo a True il valore dell’elemento della nostra lista Dictionary.
  5.   Con un banale ciclo sulla collezione delle giornate richieste, contiamo i valori true e quelli false.
  6.   Impostiamo il valore dell’immagine a mezzo (= non tutto occupato, il più probablile); poi, se il contatore dei liberi è 0, impostiamo l’immagine intero (= tutto occupato), se al contrario il contatore occupati è 0, impostiamo l’immagine libero (= tutto libero).
  7.   Infine restituiamo il valore calcolato.

Come già detto, lasciamo ai matematici trovare un algoritmo più efficiente di questo, che può bastare finché i dati sono pochi, ma diventa lento con l’aumentare del numero dei dati.

    public ImageType GetImageType(string pName)
    {
      return GetImageType(pName, this.dtpDateFrom.Value.Date, this.dtpDateTo.Value.Date);
    }
  Public Function GetImageType(ByVal name As String) As ImageType
    Return GetImageType(name, Me.dtpDateFrom.Value.Date, Me.dtpDateTo.Value.Date)
  End Function

Facciamo un overload del metodo precedente, che utilizza per le date di inizio e fine periodo il contenuto dei due DateTimePicker che abbiamo inserito sulla pagina. Un utilizzo di questo overload l’abbiamo già visto. Vedremo più avanti un altro uso ancora.
Facciamo notare come i metodi siano stati definiti pubblici, quindi potranno essere chiamati anche dall’esterno della form.

Il Metodo GetBitmap

    private Bitmap GetBitmap(Font pFont, string pName, ImageType pImageType)
    {
      try
      {
        Bitmap baseBitmap = Properties.Resources.neutro;
        switch (pImageType)
        {
          case ImageType.intero:
            baseBitmap = Properties.Resources.intero;
            break;
          case ImageType.libero:
            baseBitmap = Properties.Resources.libero;
            break;
          case ImageType.mezzo:
            baseBitmap = Properties.Resources.mezzo;
            break;
          case ImageType.selected:
            baseBitmap = Properties.Resources.selected;
            break;
        }
        Bitmap bmp = new Bitmap(baseBitmap, new Size(32, 32));
        Graphics grfx = Graphics.FromImage(bmp);
        grfx.DrawString(pName, pFont, Brushes.DarkBlue, 1, 1);
        return bmp;
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Private Function GetBitmap(ByVal font As Font, ByVal nome As String, _
                             ByVal imageType As ImageType) As Bitmap

    Try
      Dim baseBitmap As Bitmap = My.Resources.neutro
      Select Case imageType
        Case imageType.intero
          baseBitmap = My.Resources.intero
        Case imageType.libero
          baseBitmap = My.Resources.libero
        Case imageType.mezzo
          baseBitmap = My.Resources.mezzo
        Case imageType.selected
          baseBitmap = My.Resources.selected
      End Select
      Dim bmp As New Bitmap(baseBitmap, New Size(32, 32))
      Dim grfx As Graphics = Graphics.FromImage(bmp)
      grfx.DrawString(nome, font, Brushes.DarkBlue, 1, 1)
      Return bmp

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try

  End Function

Questo metodo produce l’immagine da impostare sulla PictureBox, diciamo ‘produce’, non ‘legge’, perché, una volta prelevata l’immagine dalle risorse, la modifica, scrivendovi sopra il codice dell’ombrellone utilizzando il Font che abbiamo predisposto nel costruttore della form. Vediamo come:

  1.   In base al tipo di immagine che gli viene passato, legge dalle risorse l’immagine stabilita in un bitmap.
  2.   Genera un nuovo bitmap di 32 x 32 pixel dall’immagine.
  3.   Genera un oggetto Graphics dall’immagine.
  4.   Usa il metodo DrawString fornito dalla GDI+ per scrivere sul bitmap il codice dell’ombrellone lasciando un pixel di margine dall’angolo superiore sinistro.
  5.   Restituisce il bitmap come richiesto.

Il metodo dtpDateFrom_ValueChanged

    private void dtpDateFrom_ValueChanged(object sender, EventArgs e)
    {
      if (dtpDateTo.Value < dtpDateFrom.Value)
      {
        dtpDateTo.Value = dtpDateFrom.Value;
      }
    }
  Private Sub dtpDateFrom_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                    Handles dtpDateFrom.ValueChanged
    If dtpDateTo.Value < dtpDateFrom.Value Then
      dtpDateTo.Value = dtpDateFrom.Value
    End If
  End Sub

Questo codice gestisce la modifica al valore della data inizio periodo sul DateTimePicker. Il suo scopo è fare in modo che la data fine periodo sia sempre maggiore o uguale alla data inizio periodo, altrimenti calcolare la prenotazione sarebbe un po’ complicato.

Il metodo btnUpdateImages_Click

    private void btnUpdateImages_Click(object sender, EventArgs e)
    {
      try
      {

        UpdateImages();

      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub btnUpdateImages_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                       Handles btnUpdateImages.Click

    Try
      UpdateImages()

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try

  End Sub

Anche questo è un metodo molto semplice, pecrhé richiama semplicemente il metodo che aggiorna le immagini in base al periodo impostato, metodo che viene richiamato anche da altri punti del programma:

Il metodo UpdateImages

    private void UpdateImages()
    {
      try
      {
        foreach (Control pic in this.splBase.Panel2.Controls)
        {
          PictureBox picBox = pic as PictureBox;
          if (pic != null)
          {
            ImageType oldType = (ImageType)picBox.Tag;
            ImageType newType = GetImageType(picBox.Name);
            if (newType != oldType)
            {
              Bitmap bmp = GetBitmap(mImageFont, picBox.Name, newType);
              picBox.Image = bmp;
              picBox.Tag = newType;
            }
          }
        }
        if (this.mSelectedPix != null)
        {
          this.mSelectedPix.Image = GetBitmap(mImageFont, mSelectedPix.Name, ImageType.selected);
        }
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Private Sub UpdateImages()

    Try
      For Each pic As Control In Me.splBase.Panel2.Controls
        Dim picBox As PictureBox = DirectCast(pic, PictureBox)
        If pic IsNot Nothing Then
          Dim oldType As ImageType = DirectCast(picBox.Tag, ImageType)
          Dim newType As ImageType = GetImageType(picBox.Name)
          If newType <> oldType Then
            Dim bmp As Bitmap = GetBitmap(mImageFont, picBox.Name, newType)
            picBox.Image = bmp
            picBox.Tag = newType
          End If
        End If
      Next
      If Me.mSelectedPix IsNot Nothing Then
        Me.mSelectedPix.Image = GetBitmap(mImageFont, mSelectedPix.Name, ImageType.selected)
      End If

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try

  End Sub

Questo metodo, aggiorna le immagini della spiaggia virtuale in base al periodo selezionato, vediamo come:

  1. Per ognuno dei controlli contenuti nel Panel2 dello SplitContainer
  2. Se il controllo è una PictureBox (questo controllo è stato inserito perché, se domani modificassimo qualcosa e aggiungessimo altri controlli, questo metodo continuerebbe a funzionare correttamente)
  3. Recupera l’immagine attuale dal Tag
  4. Calcola il tipo di immagine nuovo
  5. Se le immagini sono diverse
  6. Va a generare il nuovo bitmap e lo assegna alla PictureBox e aggiorna il Tag.
  7. Se c’è un’immagine selezionata (e vedremo come questo accade) mette l’immagine selected nel suo PictureBox.

Il metodo click delle picturebox

    void pb_Click(object sender, EventArgs e)
    {
      try
      {
        Bitmap bmp = null;
        if (this.mSelectedPix != null)
        {
          bmp = GetBitmap(mImageFont, this.mSelectedPix.Name, (ImageType)mSelectedPix.Tag);
          mSelectedPix.Image = bmp;
        }
        //Acquisizione del controllo selezionato
        PictureBox pBox = (PictureBox)sender;

        //Memorizzazione del codice del controllo
        this.mSelectedUmbrella = pBox.Name;
        //Memorizzazione del controllo selezionato x il cambio immagine
        this.mSelectedPix = pBox;
        //Cambio immagine in Selezionata
        bmp = GetBitmap(mImageFont, pBox.Name, ImageType.selected);
        pBox.Image = bmp;
        //Visualizzazione etichetta controllo selezionato
        this.lblSelected.Text = string.Format(FMP_CodeOnBitmap, this.mSelectedUmbrella);

        ViewBookings();
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
      System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub pb_Click(ByVal sender As Object, ByVal e As EventArgs)

    Try
      Dim bmp As Bitmap = Nothing
      If Me.mSelectedPix IsNot Nothing Then
        bmp = GetBitmap(mImageFont, me.mSelectedPix.Name,directcast(mSelectedPix.Tag,ImageType)
        Me.mSelectedPix.Image = bmp
      End If

      Dim pBox As PictureBox = DirectCast(sender, PictureBox)
      Me.mSelectedUmbrella = pBox.Name
      Me.mSelectedPix = pBox
      bmp = GetBitmap(mImageFont, pBox.Name, ImageType.selected)
      pBox.Image = bmp
      Me.lblSelected.Text = String.Format(FMP_CodeOnBitmap, Me.mSelectedUmbrella)

      ViewBookings()

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try

  End Sub

Se ben ricordate, quando abbiamo generato le PictureBox, abbiamo assegnato al loro evento click un eventhandler, vediamo quindi come animare la nostra collezione di controlli.

  1. Aggiorniamo la bitmap dell’immagine attualmente selezionata per riportarla allo stato di Non Selezionata e usiamo il suo Tag per dargli l’immagine giusta in base al suo stato tramite il metodo GetBitmap.
  2. Effettuiamo il cast a PictureBox dell’oggetto (la picturebox) che ha scatenato l’evento.
  3. Assegnamo il suo nome al campo mSelectedUmbrella di cui vedremo poi l’uso.
  4. Assegnamo la PictureBox stessa al campo mSelectedPix.
  5. Aggiorniamo il suo Bitmap con il bitmap Selected.
  6. Aggiorniamo la label che visualizza il codice dell’ombrellone selezionato in alto a sinistra sulla form.
  7. Chiamiamo il metodo che visualizza tutte le prenotazioni dell’ombrellone. Questo metodo verrà trattato dopo aver predisposto i controlli necessari sulla form FrmBookings.

Come possiamo notare, non abbiamo avuto bisogno né dell’indice né di altro segno di riconoscimento per animare la PictureBox della collezione, e spero questo inizi a farci capire perché non c’è più bisogno di Array di controlli in VB.NET e C#.

Ci rimangono ancora alcuni metodi da spiegare in questa form, però per farlo ci serve terminare di configurare le altre due form accessorie, quindi stiamo per andare a vedere cosa ci metteremo dentro. Poiché abbiamo già in testa cosa ci servirà, prepariamo solo la firma del metodo:

    public void DeleteBooking(int pID)
    {
    }
  Public Sub DeleteBooking(ByVal id As Integer)

  End Sub

La Form FrmBookings
Questa form, accessoria alla FrmMain e “posseduta” dalla stessa sempre visibile e in primo piano, contiene una lista che permette di visualizzare, modificare, cancellare le prenotazioni di un ombrellone.

Come ricordiamo, dopo averla già abbozzata prima di impementare il metodo InitBookingList, la form è molto semplice, con solo due controlli: rimangono da impostare alcune altre proprietà della form:

  •  ControlBox = False
  •  Size = 568; 268
  •  Text = Prenotazioni
  •  lvBookings:
    •  HeaderStyle = Nonclickable
    •  MultiSelect = False
    •  View = Details

Il particolare più importante è l’impostazione a False della ControlBox: in tal modo la FrmBookings è davvero ‘posseduta’ dalla FrmMain, di cui costituisce un accessorio, e non è indipendente da essa. Questo è molto importante, parlando di OOP: infatti, non sarebbe corretto permettere ad un oggetto esterno di modificare contenuti di un oggetto interno. Il codice deve essere incapsulato il più possibile. Oltre che una questione di correttezza, sembra una questione di buon senso. Una comunicazione tra form indipendenti tra loro dovrebbe avvenire tramite eventi, o metodi pubblici (cioè metodi che sarebbero comunque usati dalla form che li contiene, ma usabili dall’esterno, all’occorrenza), ma questo può essere argomento di un altro articolo.

Disegnato l’aspetto e impostate le caratteristiche, spostiamoci sul codice della form e scriviamo:

    private const string TXT_ConfDelete = "Confermi la cancellazione della prenotazione selezionata";
  Private Const TXT_ConfDelete As String = "Confermi la cancellazione della prenotazione selezionata"

La stringa costante che useremo per il messaggio di conferma cancellazione.

Abbiamo già visto sopra, poiché dovevamo inizializzarla nel metodo InitBookingList, la proprietà BookingsList, attraverso la quale esponiamo la ListView all’esterno della form.
E’ in questo modo che si ‘permette’ un accesso dall’esterno ad un controllo, che è opportuno abbia sempre visibilità Friend, mai Public.
Notate che è esposto solo in lettura. Questo significa non che si può solo leggere, ma che non si può, dall’esterno, valorizzare la proprietà BookingList (ad esempio con il riferimento ad un’altra ListView).

Creiamo un eventhandler per il click del bottone di cancellazione e vediamo come cancellare una prenotazione che si trova in una tabella contenuta nella form FrmMain.

    private void btnDelete_Click(object sender, EventArgs e)
    {
      try
      {
        if (BookingsList.SelectedItems != null && BookingsList.SelectedItems.Count > 0)
        {
          int idPrenotazione = int.Parse(BookingsList.SelectedItems[0].Text);
          if (idPrenotazione > 0)
          {
            if (Warnings.SiNo(TXT_ConfDelete) == DialogResult.Yes)
            {
              FrmMain frm = (FrmMain)this.Owner;
              frm.DeleteBooking(idPrenotazione);
              frm.ViewBookings();
            }
          }
        }

      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub btnDelete_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                             Handles btnDelete.Click

    Try
      If BookingsList.SelectedItems IsNot Nothing AndAlso BookingsList.SelectedItems.Count > 0 Then
        Dim idPrenotazione As Integer = Integer.Parse(BookingsList.SelectedItems(0).Text)
        If idPrenotazione > 0 Then
          If Warnings.SiNo(TXT_ConfDelete) = DialogResult.Yes Then
            Dim frm As FrmMain = DirectCast(Me.Owner, FrmMain)
            frm.DeleteBooking(idPrenotazione)
            frm.ViewBookings()
          End If
        End If
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try

  End Sub
  1. Se è stato selezionato un elemento della lista
  2. Leggiamo il suo ID dal primo elemenro della riga che lo contiene in formato stringa.
  3. Se il codice è > 0, quindi se è un valore valido,
  4. Chiediamo conferma usando la costante prima definita come messaggio.
  5. Facciamo il cast del nostro form Owner a FrmMain
  6. Usiamo il metodo pubblico DeleteBooking della FrmMain passandogli l’ID appena calcolato per cancellare la prenotazione.
  7. Chiamiamo il metodo pubblico ViewBookings per aggiornare la lista e la visualizzazione del contenuto della FrmMain.

Possiamo quindi vedere come è possibile utilizzare metodi pubblici per manipolare una form da un’altra ed è possibile utilizzare questi metodi grazie all’oggetto standard Owner e al metodo di Cast che lo trasforma nel nostro FrmMain.

Il Metodo DeleteBooking
Vediamo ora come cancellare un elemento dalla nostra tabella prenotazioni e salvare la tabella modificata.

    public void DeleteBooking(int pID)
    {
      try
      {
        string filter = string.Format(FMP_FilterID, FLD_ID, pID);
        DataRow[] rows = this.mDtBookings.Select(filter);
        if (rows.Length > 0)
        {
          for (int i = 0; i < rows.Length; i++)
          {
            rows[i].Delete();
          }
        }
        SaveBookings();
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Public Sub DeleteBooking(ByVal id As Integer)

    Try
      Dim filter As String = String.Format(FMP_FilterID, FLD_ID, id)
      Dim rows As DataRow() = Me.mDtBookings.Select(filter)
      If rows.Length > 0 Then
        For i As Integer = 0 To rows.Length - 1
          rows(i).Delete()
        Next
      End If
      SaveBookings()

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try

  End Sub
  1. Generiamo una stringa per ottenere un filtro sul campo ID della tabella TbBookings.
  2. Applichiamo il filtro con il metodo Select della tabella.
  3. Se l’ID selezionato esiste (dovrebbe essere sempre vero, ma come sempre preferiamo mettere un controllo in più).
  4. Cancelliamo la/le righe, anche in questo caso dovrebbe sempre essere una sola riga, ma preferiamo essere certi.
  5. Salviamo il contenuto della tabella.

Il Metodo SaveBookings
Salvare le modifiche alla tabella delle prenotazioni, sia quando si aggiunge che quando si cancella o si modifica una prenotazione, è molto semplice.

    public void SaveBookings()
    {
      try
      {
        mDtBookings.AcceptChanges();
        mDtBookings.WriteXml(mBookingsFileName);
        Warnings.Info(Txt_MsgUpdate);
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Public Sub SaveBookings()

    Try
      mDtBookings.AcceptChanges()
      mDtBookings.WriteXml(mBookingsFileName)
      Warnings.Info(Txt_MsgUpdate)

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try

  End Sub
  1. Usiamo il metodo AcceptChanges della DataTable per aggiornare tutte le modifiche fatte.
  2. Chiamiamo il metodo di serializzazione XML automatica della DataTable, passandogli il nome del file dati precedentemente generato.
  3. Avvisiamo l’utente che i dati sono stati salvati.

Da questo momento in poi, se chiudiamo l’applicazione, riaprendola avremo i dati fin qui memorizzati.

Il Metodo ViewBookings
Questo metodo, viene utilizzato sia dalla FrmMain al click sulle PictureBox, sia dalla form FrmBookings stessa per aggiornarsi quando viene cancellata una prenotazione. Il metodo azzera e ricarica la ListView delle prenotazioni aggiornandone il contenuto.

    public void ViewBookings()
    {
      try
      {
        this.mFrmBookings.BookingsList.Items.Clear();

        DataRow[] rowPre = this.mDtBookings.Select(string.Format(FMP_FilterUmbrella, FLD_Code, this.mSelectedUmbrella), FLD_DateFrom);
        if (rowPre.Length > 0)
        {
          this.mFrmBookings.BookingsList.BeginUpdate();
          for (int i = 0; i < rowPre.Length; i++)
          {
            DataRow currentRow = rowPre[i];
            ListViewItem item = new ListViewItem(currentRow[FLD_ID].ToString());
            item.UseItemStyleForSubItems = false;
            item.BackColor = ColorTranslator.FromHtml("#F8FDA5");
            item.SubItems.Add(currentRow[FLD_Code].ToString(), item.ForeColor, ColorTranslator.FromHtml("#BDDAE6"), item.Font);
            item.SubItems.Add(currentRow[FLD_DateFrom].ToString(), item.ForeColor, ColorTranslator.FromHtml("#AAE4B8"), item.Font);
            item.SubItems.Add(currentRow[FLD_DateTo].ToString(), item.ForeColor, ColorTranslator.FromHtml("#FBF6B9"), item.Font);
            item.SubItems.Add(currentRow[FLD_Note].ToString(), item.ForeColor, ColorTranslator.FromHtml("#DAADEF"), item.Font);
            this.mFrmBookings.BookingsList.Items.Add(item);
          }
          this.mFrmBookings.Refresh();
        }
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
      finally
      {
        this.mFrmBookings.BookingsList.EndUpdate();
      }
    }
  Public Sub ViewBookings()

    Try
      Me.mFrmBookings.BookingsList.Items.Clear()

      Dim rowPre As DataRow() = Me.mDtBookings.Select(String.Format(FMP_FilterUmbrella, FLD_Code, Me.mSelectedUmbrella), FLD_DateFrom)
      If rowPre.Length > 0 Then
        Me.mFrmBookings.BookingsList.BeginUpdate()
        For i As Integer = 0 To rowPre.Length - 1
          Dim currentRow As DataRow = rowPre(i)
          Dim item As New ListViewItem(currentRow(FLD_ID).ToString)
          item.UseItemStyleForSubItems = False
          item.BackColor = ColorTranslator.FromHtml("#F8FDA5")
          item.SubItems.Add(currentRow(FLD_Code).ToString, item.ForeColor, ColorTranslator.FromHtml("#BDDAE6"), item.Font)
          item.SubItems.Add(currentRow(FLD_DateFrom).ToString, item.ForeColor, ColorTranslator.FromHtml("#AAE4B8"), item.Font)
          item.SubItems.Add(currentRow(FLD_DateTo).ToString(), item.ForeColor, ColorTranslator.FromHtml("#FBF6B9"), item.Font)
          item.SubItems.Add(currentRow(FLD_Note).ToString(), item.ForeColor, ColorTranslator.FromHtml("#DAADEF"), item.Font)
          Me.mFrmBookings.BookingsList.Items.Add(item)
        Next
        Me.mFrmBookings.Refresh()
      End If

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    Finally
      Me.mFrmBookings.BookingsList.EndUpdate()
    End Try

  End Sub
  1. Azzeriamo la lista usando il metodo Clear della ListView.
  2. Generiamo una lista delle prenotazioni per l’ombrellone correntemente selezionato.
  3. Poniamo la ListView in modalità Update
  4. Per ogni riga di prenotazione, generiamo una riga sulla ListView ed aggiungiamo i campi della riga come Item e SubItem della lista. Per movimentare un pochino le cose, cambiamo il colore di sfondo degli elementi delle varie colonne.
  5. Aggiorniamo la form.
  6. Rimettiamo la ListView in modalità normale.

La form FrmBookingDetail
Per continuare a spiegare gli ultimi metodi inseriti nella FrmMain, ci serve la seconda form ausiliaria, ovvero la form FrmBookingDetail, che ci permetterà di implementare e spiegare le funzionalità di inserimento di una nuova prenotazione e di modifica di una prenotazione esistente.

Aggiungiamo alla form i seguenti controlli modificandone le proprietà nel designer:

  •  Una Label per visualizzare il codice dell’ombrellone per cui stiamo inserendo/modificando la prenotazione.
    •  Name = lblCode
    •  AutoSize = False
    •  Font
      •  Size = 10
      •  Bold = True
    •  Location = 27; 7
    •  Size = 183; 20
    •  Text = Codice
    •  TextAlign = Middle Left
  •  Una Label per la descrizione del DateTimePicker data inizio periodo.
    •  Name = lblDateFrom
    •  Text = Dalla Data:
    •  TextAlign = Middle Right
  •  Una Label (copia della precedente) per la descrizione del DateTimePicker data fine periodo.
    •  Name = lblDateTo
    •  Text = Alla Data:
  •  Un DateTimePicker per la data inizio periodo
    •  Name = dtpDateFrom
    •  Format = Short
    •  Size = 107; 20
  •  Un DateTimePicker (copia del precedente) per la data fine periodo
    •  Name = dtpDateTo
    •  Format = Short
  •  Una Label per il campo note
    •  Name = lblNote
    •  Text = Annotazioni:
    •  TextAlign = Middle Left
  •  Una TextBox per le annotazioni
    •  Name = txtNote
    •  Dock = Bottom
    •  Multiline = True
    •  Scrollbars = Vertical
    •  Size Height = 73
  •  Un Button per salvare le modifiche
    •  Name = btnSave
    •  Text = Salva
    •  DialogResult = OK
  •  Un Button (copia del precedente) per annullare le modifiche
    •  Name = btnCancel
    •  Text = Annulla
    •  DialogResult = Cancel
  •  Modifichiamo anche le proprietà della form e poniamo:
    •  AcceptButton = btnSave
    •  CancelButton = btnCancel
    •  Size = 373; 208
    •  Text = Aggiunge/modifica una Prenotazione
Dalla barra strumenti Layout, utilizziamo il tool Tab Order, indicato dalla freccia nella figura accanto, per predisporre l’ordine delle tabulazioni in modo visuale, cliccando sui vari controlli nell’ordine che desideriamo.
Nel caso sbagliassimo, possiamo ricominciare, dato che il numero impostato è virtuale fino a quando non li clicchiamo tutti.
Provate e divertitevi.
Potete trascurare i controlli non tabulabili di default come le Label.

Disegnata la nostra form, andiamo a scrivere il codice che la gestisce:

    private const string FMP_Code = "Cod. {0}";
    private const string TXT_InvalidPeriod = "Attenzione, nel periodo selezionato l'ombrellone " +
                                             "risulta prenotato o parzialmente prenotato, " + 
                                             "scegliere un periodo in cui \'e8 completamente libero.";
  Private Const FMP_Code As String = "Cod. {0}"
  Private Const TXT_InvalidPeriod As String = "Attenzione, nel periodo selezionato l'ombrellone " & _
                                              "risulta prenotato o parzialmente prenotato, " & _
                                              "scegliere un periodo in cui \'e8 completamente libero."

Le costanti. Ce ne servono due, uno è il pattern di formattazione del codice ombrellone sulla sua label, l’altra è il messaggio da dare all’utente se viene selezionato un periodo che risulta già prenotato per l’ombrellone.

    private string mCurrentCode;
    private int mCurrentID;
    private DataRow mCurrentRow;
    private FrmMain mMainForm;
  Private mCurrentCode As String
  Private mCurrentID As Integer
  Private mCurrentRow As DataRow
  Private mMainForm As FrmMain

I campi che useremo nella form:

  •  mCurrentCode - Codice dell’ombrellone per cui stiamo inserendo/modificando la prenotazione .
  •  mCurrentID - Codice della prenotazione se siamo in modifica, altrimenti 0.
  •  mCurrentRow - Riga della tabella prenotazioni che stiamo modificando/inserendo.
  •  mMainForm - La form principale, di cui dobbiamo utilizzare proprietà e metodi.
    public FrmBookingDetail(FrmMain pMainForm, int pID, string pCodice)
    {
      InitializeComponent();
      mMainForm = pMainForm;
      mCurrentID = pID;
      mCurrentRow = null;
      mCurrentCode = pCodice;
      this.lblCode.Text = string.Format(FMP_Code, mCurrentCode);
    }
  Public Sub New(ByVal mainForm As FrmMain, ByVal id As Integer, ByVal codice As String)
    InitializeComponent()
    mMainForm = mainForm
    mCurrentID = id
    mCurrentRow = Nothing
    mCurrentCode = codice
    Me.lblCode.Text = String.Format(FMP_Code, mCurrentCode)
  End Sub

Il costruttore della form. Questa form, per funzionare correttamente, ha bisogno di alcune fondamentali informazioni, pertanto, modifichiamo il suo costruttore standard aggiungendovi tre parametri:

  •  pMainForm - la form chiamante
  •  pID - l’ID della prenotazione se siamo in modifica, 0 altrimenti.
  •  pCodice - il codice dell’ombrellone per cui stiamo inserendo/modificando la prenotazione.
    public DateTime DateFrom
    {
      get
      {
        return dtpDateFrom.Value;
      }
      set
      {
        dtpDateFrom.Value = value;
      }
    }

    public DateTime DateTo
    {
      get
      {
        return dtpDateTo.Value;
      }
      set
      {
        dtpDateTo.Value = value;
      }
    }
  Public Property DateFrom() As DateTime
    Get
      Return dtpDateFrom.Value
    End Get
    Set(ByVal value As DateTime)
      dtpDateFrom.Value = value
    End Set
  End Property

  Public Property DateTo() As DateTime
    Get
      Return dtpDateTo.Value
    End Get
    Set(ByVal value As DateTime)
      dtpDateTo.Value = value
    End Set
  End Property

Proprietà. Per quanto non siano indispensabili, esponiamo come proprietà i valori dei due DateTimePicker, in modo che possano essere inizializzati alla chiamata della form con il valore del periodo selezionato nella form principale.

    private void FrmBookingDetail_Load(object sender, EventArgs e)
    {
      if (mCurrentID > 0)
      {
        mCurrentRow = mMainForm.GetBooking(mCurrentID);
        this.dtpDateFrom.Value = Convert.ToDateTime(mCurrentRow[FrmMain.FLD_DateFrom]);
        this.dtpDateTo.Value = Convert.ToDateTime(mCurrentRow[FrmMain.FLD_DateTo]);
        this.txtNote.Text = (string)mCurrentRow[FrmMain.FLD_Note];
      }
    }
  Private Sub FrmBookingDetail_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                                     Handles Me.Load
    If mCurrentID > 0 Then
      mCurrentRow = mMainForm.GetBooking(mCurrentID)
      Me.dtpDateFrom.Value = Convert.ToDateTime(mCurrentRow(FrmMain.FLD_DateFrom))
      Me.dtpDateTo.Value = Convert.ToDateTime(mCurrentRow(FrmMain.FLD_DateTo))
      Me.txtNote.Text = DirectCast(mCurrentRow(FrmMain.FLD_Note), String)
    End If
  End Sub

L’event handler del metodo Load della form. Questo metodo, quando il codice della prenotazione passato alla form è maggiore di zero, cioè in fase di modifica di una prenotazione, effettua le seguenti operazioni:

  1. Utilizza il metodo GetBooking della form principale per richiedere la DataRow della tabella prenotazioni che corrisponde all’ID passatogli.
  2. aggiorna i controlli della form con i valori trovati sulla DataRow.

Il metodo BuildMe

    public static FrmBookingDetail BuildMe(Icon pIcon, FrmMain pFrmMain, int pID, string pCodice)
    {
      FrmBookingDetail frmDett = new FrmBookingDetail(pFrmMain, pID, pCodice);
      frmDett.Icon = pIcon;
      return (frmDett);
    }
  Public Shared Function BuildMe(ByVal icon As Icon, ByVal frmMain As FrmMain, _
                                 ByVal id As Integer, ByVal codice As String) As FrmBookingDetail
    Dim frmDett As New FrmBookingDetail(frmMain, id, codice)
    frmDett.Icon = icon
    Return frmDett
  End Function

Questo metodo statico, inserito nella form, genera una nuova istanza dell’oggetto FrmBookingDetail che può essere poi utilizzato dalla form principale per l’inserimento e modifica dei dati.

    private void btnCancel_Click(object sender, EventArgs e)
    {
      this.Close();
    }
  Private Sub btnCancel_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                             Handles btnCancel.Click
    Me.Close()
  End Sub

L’event handler del bottone di annullamento dell’inserimento/modifica, che non fa altro che chiudere la form.

    private void btnSave_Click(object sender, EventArgs e)
    {
      if (mCurrentRow != null)
      {
        mCurrentRow[FrmMain.FLD_DateFrom] = dtpDateFrom.Value.Date;
        mCurrentRow[FrmMain.FLD_DateTo] = dtpDateTo.Value.Date;
        mCurrentRow[FrmMain.FLD_Note] = txtNote.Text;
        mMainForm.SaveBookings();
        Close();
      }
      else
      {
        ImageType checkType = mMainForm.GetImageType(mCurrentCode, dtpDateFrom.Value.Date,
                                                     dtpDateTo.Value.Date);
        if (checkType == ImageType.libero || checkType == ImageType.neutro)
        {
          mMainForm.AddBooking(mCurrentCode, dtpDateFrom.Value.Date, dtpDateTo.Value.Date,
                               txtNote.Text);
          mMainForm.SaveBookings();
          Close();
        }
        else
        {
          Warnings.Avviso(TXT_InvalidPeriod);
        }
      }
    }
  Private Sub btnSave_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                               Handles btnSave.Click
    If mCurrentRow IsNot Nothing Then
      mCurrentRow(FrmMain.FLD_DateFrom) = dtpDateFrom.Value.Date
      mCurrentRow(FrmMain.FLD_DateTo) = dtpDateTo.Value.Date
      mCurrentRow(FrmMain.FLD_Note) = txtNote.Text
      mMainForm.SaveBookings()
      Close()
    Else
      Dim checkType As ImageType = mMainForm.GetImageType(mCurrentCode, dtpDateFrom.Value.Date, _
                                                          dtpDateTo.Value.Date)
      If checkType = ImageType.libero OrElse checkType = ImageType.neutro Then
        mMainForm.AddBooking(mCurrentCode, dtpDateFrom.Value.Date, dtpDateTo.Value.Date, _
                             txtNote.Text)
        mMainForm.SaveBookings()
        Close()
      Else
        Warnings.Avviso(TXT_InvalidPeriod)
      End If
    End If
  End Sub

Questo metodo aggiorna la tabella prenotazioni che si trova sulla form principale,:

  1.  Se il campo mCurrentRow non è nullo, stiamo modificando una prenotazione e il campo è un riferimento all’oggetto DataRow della tabella TbBookings che si trova nella form principale. Pertanto, per aggiornare la prenotazione, non facciamo altro che
    •  modificare i campi della tabella
    •  Chiamare il metodo pubblico SaveBookings della form principale.
    •  Chiudere la form.
  2.  Altrimenti, siamo in fase di inserimento di una prenotazione nuova. Pertanto:
    •  Utilizziamo il metodo GetImageType della form principale per verificare se, nel periodo prescelto l’ombrellone è libero.
    •  Se l’ombrellone è libero, otterremo l’immagine libero oppure l’immagine neutro, in questo caso, utilizziamo il metodo AddBooking della form principale per aggiungere una nuova prenotazione e salviamo le prenotazioni, quindi chiudiamo la form.
    •  Se l’ombrellone non risulta libero, diamo avviso all’utente e non facciamo nulla.

Abbiamo utilizzato due metodi che non abbiamo ancora spiegato della nostra FrmMain, quindi vediamo come funzionano il metodo che acquisisce una prenotazione e quello che ne aggiunge una.

Il metodo GetBooking
Questo metodo, dato un ID restituisce la DataRow della prenotazione corrispondente oppure un null (Nothing)

    public DataRow GetBooking(int pID)
    {
      try
      {
        DataRow retRow = null;
        string filter = string.Format(FMP_FilterID, FLD_ID, pID);
        DataRow[] rows = this.mDtBookings.Select(filter);
        if (rows.Length > 0)
        {
          retRow = rows[0];
        }
        return (retRow);
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Public Function GetBooking(ByVal id As Integer) As DataRow

    Try
      Dim retRow As DataRow = Nothing
      Dim filter As String = String.Format(FMP_FilterID, FLD_ID, id)
      Dim rows As DataRow() = Me.mDtBookings.Select(filter)
      If rows.Length > 0 Then
        retRow = rows(0)
      End If
      Return retRow

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try

  End Function
  1. Predisponiamo una DataRow e inizializziamola a null.
  2. Predisponiamo il filtro usando un Format pattern per costruirne la stringa che sarà del tipo: “ID=n” dove n è l’ID numerico della prenotazione.
  3. Usiamo il metodo Select della DataTable delle prenotazioni per ottenere le righe che corrispondono all’ID cercato (dovrebbe essere una soltanto, ovviamente)
  4. Se la riga cercata esiste assegnamo la prima riga dell’array alla DataRow predisposta allo scopo.
  5. Ritorniamo quanto trovato al chiamante.

Il metodo AddBooking

    public void AddBooking(string pCode, DateTime pDateFrom, DateTime pDateTo, string pNote)
    {
      try
      {
        DataRow drP = mDtBookings.NewRow();
        drP[FLD_Code] = pCode;
        drP[FLD_DateFrom] = pDateFrom;
        drP[FLD_DateTo] = pDateTo;
        drP[FLD_Note] = pNote;
        mDtBookings.Rows.Add(drP);
        drP.AcceptChanges();
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Public Sub AddBooking(ByVal code As String, ByVal dateFrom As DateTime, ByVal dateTo As DateTime, _
                        ByVal note As String)

    Try
      Dim drP As DataRow = mDtBookings.NewRow
      drP(FLD_Code) = code
      drP(FLD_DateFrom) = dateFrom
      drP(FLD_DateTo) = dateTo
      drP(FLD_Note) = note
      mDtBookings.Rows.Add(drP)
      drP.AcceptChanges()

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try

  End Sub

Questo metodo genera una nuova prenotazione, a partire dai dati passati nei parametri:

  •  pCode = codice ombrellone
  •  pDateFrom = data inizio periodo
  •  pDateTo = data fine periodo
  •  pNote = annotazioni (su chi prenota ad esempio).

Per generare la prenotazione effettua le seguenti operazioni:

  1. Utilizza il metodo NewRow della tabella TbBookings (mDtBookings) per generare una DataRow nuova.
  2. Aggiorna i campi della DataRow con i dati passati.
  3. Aggiunge la riga generata alla collection Rows della DataTable.
  4. Chiama il metodo AcceptChanges per aggiornare le modifiche.
    AcceptChanges in realtà è ridondante, perché viene chiamato anche dal metodo SaveBookings, ma una volta in più in questo caso non fa male.

L’Event Handler dell’evento doppio click sulla lista prenotazioni
Se ben ricordate, quando abbiamo spiegato il metodo InitBookingsList, in quel metodo abbiamo assegnato un EventHandler all’evento doppio click della ListView che si trova sulla form FrmBookings ed elenca le prenotazioni dell’immagine selezionata.
Abbiamo visto come questa lista viene riempita alla selezione immagine e, a questo punto, vediamo come utilizziamo l’evento doppio click su una riga della lista prenotazioni per modificare una prenotazione.

    void BookingList_MouseDoubleClick(object sender, MouseEventArgs e)
    {
      try
      {
        ListView lvw = (ListView)sender;
        if (lvw.SelectedItems != null)
        {
          int idPrenotazione = int.Parse(lvw.SelectedItems[0].Text);
          if (idPrenotazione > 0)
          {
            FrmBookingDetail frmUpdate = 
                      FrmBookingDetail.BuildMe(this.Icon, this, idPrenotazione, mSelectedUmbrella);
            if (frmUpdate.ShowDialog() == DialogResult.OK)
            {
              this.dtpDateFrom.Value = frmUpdate.DateFrom;
              this.dtpDateTo.Value = frmUpdate.DateTo;
              UpdateImages();
              ViewBookings();
            }
          }
        }
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
      System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub BookingList_MouseDoubleClick(ByVal sender As Object, ByVal e As MouseEventArgs)

    Try
      Dim lvw As ListView = DirectCast(sender, ListView)
      If lvw.SelectedItems IsNot Nothing Then
        Dim idPrenotazione As Integer = Integer.Parse(lvw.SelectedItems(0).Text)
        If idPrenotazione > 0 Then
          Dim frmUpdate As FrmBookingDetail = _
                           FrmBookingDetail.BuildMe(Me.Icon, Me, idPrenotazione, mSelectedUmbrella)
          If frmUpdate.ShowDialog = Windows.Forms.DialogResult.OK Then
            Me.dtpDateFrom.Value = frmUpdate.DateFrom
            Me.dtpDateTo.Value = frmUpdate.DateTo
            UpdateImages()
            ViewBookings()
          End If
        End If
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try

  End Sub
  1. Per prima cosa, effettuiamo un cast dell’oggetto che ha scatenato l’evento e lo trasformiamo in ciò che è, ovvero una ListView.
  2. Se esiste un elemento selezionato (il doppio click seleziona l’elemento, ma è sempre meglio controllare).
  3. Leggiamo l’ID della prenotazione dal testo della prima colonna dell’Item selezionato.
  4. Se è un ID valido
  5. Generiamo un istanza della form Update
  6. Usiamo il metodo ShowDialog per visualizzarla come finestra modale.
  7. Se l’utente è uscito premendo OK e quindi in qualche modo ha modificato la prenotazione
  8. Aggiorniamo le date di selezione della form principale con quelle appena inserite.
  9. Aggiorniamo la visualizzazione della spiaggia.
  10. Aggiorniamo la finestra con la lista prenotazioni.

L’event handler del Click del btnNewBooking
Per terminare le nostre fatiche, vediamo come utilizzare il bottone btnNewBooking per aggiungere una nuova prenotazione.

    private void btnNewBooking_Click(object sender, EventArgs e)
    {
      try
      {

        FrmBookingDetail frmAdd = FrmBookingDetail.BuildMe(this.Icon, this, 0, mSelectedUmbrella);

        frmAdd.DateFrom = this.dtpDateFrom.Value.Date;
        frmAdd.DateTo = this.dtpDateTo.Value.Date;

        if (frmAdd.ShowDialog() == DialogResult.OK)
        {
          this.dtpDateFrom.Value = frmAdd.DateFrom;
          this.dtpDateTo.Value = frmAdd.DateTo;
          UpdateImages();
          ViewBookings();
        }

      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub btnNewBooking_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                                                                          Handles btnNewBooking.Click
    Try
      Dim frmAdd As FrmBookingDetail = FrmBookingDetail.BuildMe(Me.Icon, Me, 0, mSelectedUmbrella)

      frmAdd.DateFrom = Me.dtpDateFrom.Value.Date
      frmAdd.DateTo = Me.dtpDateTo.Value.Date

      If frmAdd.ShowDialog = Windows.Forms.DialogResult.OK Then
        Me.dtpDateFrom.Value = frmAdd.DateFrom
        Me.dtpDateTo.Value = frmAdd.DateTo
        UpdateImages()
        ViewBookings()
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try

  End Sub
  1. Generiamo un’istanza della form di aggiunta dettagli usando il metodo BuildMe passandogli i dati dell’ombrellone corrente e l’ID=0 perché è una nuova prenotazione.
  2. Inizializziamo le date della form con quelle attualmente selezionate sulla spiaggia virtuale.
  3. Usiamo il metodo ShowDialog per mostrare la finestra e attendere l’azione dell’utente.
  4. Se l’utente esce con un OK
  5. Aggiorniamo le date di selezione della spiaggia virtuale con quelle della prenotazione generata.
  6. Aggiorniamo la visualizzazione della spiaggia virtuale.
  7. Aggiorniamo la visualizzazione delle prenotazioni per l’ombrellone selezionato.

Con questo, se non abbiamo dimenticato nulla di fondamentale, dovremmo avere a disposizione tutta l’applicazione per gestire la nostra spiaggia virtuale. il cui aspetto a video somiglia a questo (per ragioni di spazio le finestre sono state disposte diversamente):

Both comments and pings are currently closed.

Comments are closed.