Ieri sera, dopo una giornata di lavoro, mi sono fatto
una bella partita a Quake 4, giusto per sfogarmi un po'. Dopo, ho dato una mano
a mio fratello Omar che sta sviluppando un'applicazione WinForms in VB.NET per
la gestione di un negozio di abbigliamento. Siamo rimasti in ballo fino a
mezzanotte scrivendo codice, parlando, discutendo (leggere confrontarsi al
limite del litigio) e analizzando diversi problemi che abbiamo avuto.
Voglio riportare sul mio blog parte di quello di cui abbiamo discusso
essenzialmente potrebbe servire a qualcun'altro, ma magari mi
sbaglio.
Quello di cui voglio parlare è espresso bene nel titolo di questo post. In
condizioni normali, infatti, gli eventi di una Windows Form (WF) si scatenano a
run-time. Ad esempio, se gestisco l'evento Load della WF, mi
aspetto che il codice di questo evento si scateni a run-time, all'atto della
creazione della WF stessa. Non è sempre così. Vediamo perchè.
Scenario
A mio fratello piace molto il meccanismo della
Visual Inheritance offerto da .NET. Nel progetto, ha creato una form
FormBase che
funge da template per tutte le altre WF
dell'applicazione. FormBase contiene una toolbar (in alto)
ed altri controlli classici. Giustamente, mio fratello si è detto: a me non interessa creare FormBase solo per l'aspetto estetico, mi
interessa anche perchè io posso scrivere codice di cui avrò sempre
bisogno. Quindi, ha gestito l'evento Load di FormBase creando un
SqlConnection, un SqlDataAdapter, un dataset tipizzato con 2 DataTable
relazionate fra loro, popolandolo e diverse cose ancora. Ok, a posto. Da notare
che (a mio avviso) questa logica è più che corretta: nel momento in cui creo
un'istanza di FormBase, il dataset viene riempito.
Dov'è il problema con cui ci siamo scontrati?
Tutto
questo ragionamento fila, ma c'è un ma. Io in realtà non istanzierò mai
un oggetto FormBase. Istanzierò in realtà una
WF che eredita da FormBase. Mio fratello ha aggiunto al
progetto tante WF che, appunto, ereditano da FormBase: FormCollezioni,
FormMagazzino, FormArticoli, bla bla bla. Quindi, per esempio, io scriverò
qualcosa tipo:
Dim frm As New FormCollezioni
frm.Show()
frm.Dispose()
Dove, appunto, FormCollezioni è una WF che deriva da FormBase.
Ed ecco qui
il bandolo della matassa.
Se nell'IDE di VS.NET apro in progettazione la form
FormCollezioni, si scatena l'evento Load di
FormBase. Con tutte le problematiche connesse. Oltre
all'assurdità che a design-time, per esempio, viene istanziato un
dataset che, in questo momento, non serve proprio a nulla.
Il ragionamento generale è...
Quando sono in
progettazione di una WF che eredita, si scatenano gli eventi della classe base
dalla quale stiamo derivando. Ma, se ci pensiamo bene, è quello che accade in
ogni momento mentre sviluppiamo. Per esempio, se ho una WF vuota e ci
posiziono sopra una TextBox, chi la disegna realmente sulla WF? La
risposta è: il framework. Ma in base a cosa? In base al fatto che si scatena
l'evento OnPaint della classe base (ovvero
System.Windows.Forms.TextBox). Se continuiamo a pensarci bene, è un po' quello
che accade quando progettiamo un nostro Windows Control: noi
facciamo l'override di OnPaint, e in effetti sulla WF che ospita il nostro
controllo l'evento si scatena. Tant'è vero che anche a design-time
vediamo il nostro controllo che si modifica in real-time mentre ci stiamo
lavorando su.
Spero di ricevere pareri autorevoli su quello che sto per
dire.
Il ragionamento che mi sono fatto è il seguente: se so che la
mia FormBase verrà ereditata da qualcuno, evito (dove posso) di
usare gli eventi di sistema, proprio perchè poi nella form ereditata mi si
scatenano. Io ieri sera ho fatto l'override del metodo Show: a
disegn-time ovviamente nessuno chiama questo metodo. Facendone l'override, ho
creato i componenti accennati prima (SqlConnection, SqlDataAdapter, DataSet,
etc.).
Un'altra soluzione potrebbe essere: evito sempre di usare gli eventi per i
motivi detti prima. Mi faccio un metodo custom (supponiamo
Inizializza) che fa tutto quello che deve fare. Però poi ho un
problema: mi devo ricordare nella WF ereditata di chiamare questo metodo
Inizializza() prima di fare Show(). Per cui, ho preferito
giocare con l'override di Show() spiegato qui sopra.