Oggi
cominciamo un nuovo argomento: gli oggetti. Un oggetto è
un insieme di codice e dati che è possibile utilizzare
indipendentemente da altri oggetti poiché rappresenta
un insieme a sé stante. Esempi tipici ed evidenti di
oggetti sono i controlli che abitualmente inseriamo su un
form: pulsanti, caselle di testo, checkbox e così via.
Ma anche lo stesso form è un oggetto, così come
il font di un testo, un database, un documento word o un grafico
di excel. Fondamentalmente, un oggetto in Visual Basic è
rappresentato da una classe che ne definisce le caratteristiche:
in particolare, la classe di un oggetto definisce le sue proprietà,
i suoi metodi e i suoi eventi. Per rendersi conto in modo
semplice ed efficace di cosa ciò significhi, basta
dare un'occhiata al "visualizzatore oggetti" (o
"object browser"), che potete aprire premendo F2
dall'IDE o tramite il menù "visualizza";
se date un'occhiata alle voci con l'icona del modulo di classe
vi
accorgerete che essa è associata non solo ai normali
controlli visibili sulla casella degli strumenti, ma anche
all'oggetto ErrObject, all'oggetto StdFont, all'oggetto App
ecc.
Tutti questi oggetti hanno un'interfaccia (un insieme di metodi,
eventi, proprietà) definiti nelle rispettive classi,
che quindi ne rappresentano il "modello" valido
per tutte le istanze dell'oggetto.
Infatti, quando un programmatore manipola un oggetto tramite
la sua interfaccia, in realtà non a che fare direttamente
con la classe dell'oggetto, ma con una sua istanza, ovvero
con una copia direttamente utilizzabile.
Se mi è consentito un paragone filosofico, il rapporto
che c'è tra una classe e le sue istanze è in
qualche modo analogo a quello che intercorre tra le "idee"
platoniche e le sue manifestazioni concrete sul nostro mondo.
Ogni istanza di una classe è indipendente dalle altre,
così che in uno stesso form è possibile inserire
ad es. 4 textbox senza che debba esserci alcuna reciproca
connessione tra di essi: ognuna di queste istanze è
gestibile in modo del tutto indipendente dalle altre, ma la
loro interfaccia è comune perché è definita
per tutte dalla medesima classe TextBox.
L'indipendenza reciproca delle istanze, naturalmente, non
impedisce che esse possano interagire tra di loro e con altri
tipi di oggetti.
La classe, oltre a contenere il codice per la gestione dell'interfaccia,
contiene anche dati da esporre, di norma, tramite le proprietà;
tali dati si dice che sono "incapsulati" nell'oggetto,
e ovviamente sono replicati per ognuna delle sue istanze.
Per creare un oggetto, quindi, è sufficiente inserire
in un progett un modulo di classe, definirne le proprietà,
i metodi e gli eventi e scrivere il codice e i dati necessari
per consentirne la corretta funzionalità: vediamo come
fare queste dichiarazioni con una semplice classe di esempio.
Creiamo una classe di nome "clsUomo" che rappresenta
un essere umano: tra le proprietà attribuibili ad esso
ci può essere ad es. il nome, il peso, l'altezza, il
sesso ecc.
Per fare in modo che l'interfaccia della classe Uomo esponga
la proprietà "Nome" occorre dichiarare questa
proprietà nel modo seguente:
Public
Property Get
Nome() As String
Nome = sName
End Property
Public
Property Let
Nome(sNewName As String)
sName = sNewName
End Property
|
Occorre
innanzitutto notare che le dichiarazioni sono due: una per
leggere il valore della proprietà (Property Get),
e una per modificare tale valore (Property Let); la
Property Get funziona sostanzialmente come una funzione, che
restituisce il contenuto di una variabile, in questo caso
sName; solitamente essa è dichiarata come variabile
privata del modulo di classe:
Quando
invece il programmatore accede alla proprietà Nome per
modificarne il valore, viene richiamata la procedura Property
Let, a cui viene passato come argomento il nuovo nome da assegnare
alla proprietà, e tale valore è assegnato via
codice alla variabile di modulo sName, in modo che una successiva
chiamata della Property Get restituisca il nome appena modificato.
Esiste anche un altro tipo di routine per modificare una proprietà:
si tratta della Property Set, che va utilizzata qualora
la proprietà faccia riferimento a un oggetto; questo
accade perché Visual Basic richiede di utilizzare l'istruzione
Set per assegnare un riferimento a un oggetto, mentre per i
tipi di dati fondamentali non è richiesta alcuna istruzione
particolare (l'istruzione Let era necessaria nelle vecchie versioni
del basic ed è stata mantenuta per compatibilità).
Per quale motivo è opportuno utilizzare una coppia di
routine Property anziché dichiarare a livello di modulo
una variabile pubblica Nome? Anche questo secondo metodo consentirebbe
di leggere e scrivere a proprio piacimento il valore della variabile;
ma il problema è proprio questo: usando una variabile
pubblica non ci sarebbe alcun controllo sulla correttezza dell'uso
della "proprietà" Nome.
Uno potrebbe assegnare a questa variabile qualunque valore,
anche palesemente inadatto a descrivere il nome di un essere
umano; invece implementando una coppia di routine Property è
possibile scrivere del codice atto ad evitare che a tale proprietà
siano assegnati valori non adeguati, ad es. nomi del tipo "123&g#",
oppure nomi troppo lunghi o troppo corti. Inoltre, con una variabile
pubblica non sarebbe possibile implementare una proprietà
di sola lettura; cosa che risulta invece molto semplice con
le routine property: basta infatti cancellare la routine Property
Let (o Property Set)! Ad es. la proprietà "Sesso"
potrebbe essere una tipica proprietà di sola lettura
(a meno di esigenze particolari):
Public
Property Get
Sesso() As Boolean
Sesso = True
End Property |
(in
questo caso bisogna solo decidere se il valore True corrisponde
a maschio o femmina: eventualmente è possibile utilizzare
un tipo di dati diverso dal booleano).
Come le proprietà descrivono le caratteristiche di un
oggetto, i metodi rappresentano le azioni che l'oggetto può
compiere: un essere umano, ad es., può correre, mangiare,
dormire, pensare ecc.
Ciascuna di queste azioni può essere eseguita tramite
un apposito metodo; ad es. possiamo implementare un metodo "Scrivi"
che consenta di scrivere qualcosa su un certo form: tale metodo
non sarà altro che una procedura Sub pubblica, la quale
accetterà come parametri in ingresso la frase da scrivere
e il form su cui scrivere (eventualmente si potrebbero aggiungere
parametri aggiuntivi come le coordinate o il font della frase
da scrivere; la proprietà AutoRedraw del form deve essere
impostata a True):
Public
Sub
Scrivi (frmScrivi As Form,
sFrase As String)
frmScrivi.Print sFrase
End Sub |
Se
si desidera che il metodo restituisca un valore (ad es. per
controllare la corretta esecuzione del metodo), si può
usare una funzione al posto di una subroutine.
Infine, gli eventi sono quegli elementi dell'interfaccia che
non sono richiamati da chi utilizza l'oggetto ma sono generati
direttamente dall'oggetto; quando si verifica qualcosa che si
ritiene opportuno segnalare al client (ovvero al componente
che utilizza l'oggetto), è l'oggetto stesso che genera
l'evento corrispondente.
Per un essere umano, i tipici eventi potrebbero essere "Nascita",
"Morte", "Matrimonio", ecc. Per poter generare
un evento, in una classe deve essere innanzitutto presente la
sua dichiarazione:
Public
Event
Nascita(dtData As Date, bSesso
As Boolean) |
La
generazione vera e propria dell'evento avviene però con
l'istruzione RaiseEvent; supponiamo ad es. che l'interfaccia
della classe clsUomo esponga il metodo "GeneraFiglio":
in tale evento sarà generato l'evento Nascita:
Public
Sub
GeneraFiglio(sNome As String)
Dim fSesso As
Single
Dim bSesso As
Boolean
fSesso=Rnd
If fSesso>0.5 Then
bSesso = True
End If
RaiseEvent Nascita(Date, bSesso)
End Sub
|
Il sesso del nascituro viene determinato casualmente con la
funzione Rnd, dopodiché viene ufficializzata la nascita
del figlio in data odierna: quando ciò accade, nel client
l'esecuzione passerà alla routine Sub Nascita che avrà
per argomenti la data di nascita e il sesso del neonato. Ad
es. in questa routine si potrebbe creare una nuova istanza della
classe Uomo con le caratteristiche del neonato.
Per inserire una proprietà, un metodo o un evento a una
classe si può anche ricorrere alla funzione "Inserisci
routine" del menù "Strumenti"; tutto quello
che occorre fare è selezionare il tipo di routine da
inserire: "Property" per le proprietà, "Event"
per gli eventi, "Sub" o "Function" per i
metodi.
Finora abbiamo visto la costruzione dell'interfaccia tramite
una classe: ora vediamo come utilizzare questa classe dal lato
client. Prima di tutto bisogna dichiarare una o più variabili
di tipo clsUomo:
Dim
uPrimoUomo As clsUomo
Dim uSecondoUomo As
clsUomo |
La "u" di prefisso sta a indicare che la variabile
è di tipo "Uomo": si tratta di una convenzione
ad hoc per distinguere queste variabili dalle altre. Poi bisogna
impostare un riferimento alla classe Uomo; infatti, come dicevo
prima, l'utilizzatore dell'oggetto Uomo non lavora direttamente
sulla classe che lo definisce ma sulle sue singole istanze:
istanze che sono rappresentate dalle variabili di tipo Uomo
e che diventano concretamente utilizzabili solo quando contengono
un riferimento alla classe. Per fare ciò si utilizza
l'istruzione Set:
Set
uPrimoUomo = New clsUomo
Set uSecondoUomo = New
clsUomo |
La parola chiave New serve a specificare che abbiamo bisogno
di una nuova istanza della classe clsUomo; senza questa parola
chiave l'istruzione Set non funzionerebbe, perché clsUomo
non è direttamente referenziabile da una variabile. È
invece possibile specificare New nella stessa dichiarazione
delle variabili:
Dim
uPrimoUomo As New clsUomo
Dim uSecondoUomo As
New clsUomo |
Così facendo sarà lo stesso Visual Basic a impostare
il riferimento a una nuova istanza della classe per ogni variabile
in occasione del suo primo utilizzo nel codice; quando Visual
Basic per la prima volta incontrerà una istruzione che
invoca una proprietà o un metodo dell'oggetto, ad es.:
uSecondoUomo.Nome
= "Piergiovanni" |
provvederà a creare una nuova istanza della classe Uomo,
ad assegnare alla variabile uSecondoUomo un riferimento a tale
istanza, e a modificare il valore della proprietà Nome
di questa istanza.
La successiva istruzione che coinvolge la variabile uSecondoUomo
farà riferimento all'istanza già creata e assegnata
alla variabile, finché non sarà implicitamente
o esplicitamente distrutta. In questo modo, però, non
si ha un pieno controllo sul momento in cui l'istanza della
classe viene creata, proprio perché questo compito è
lasciato a Visual Basic; invece spesso è importante che
sia il programmatore a decidere quando l'istanza deve essere
creata utilizzando l'apposita istruzione Set, onde evitare ambiguità
nel codice. Allo stesso modo, è importante che il programmatore
provveda a distruggere esplicitamente il riferimento all'istanza
della classe quando questa istanza non è più necessaria;
a questo scopo si usa ancora l'istruzione Set:
La parola chiave Nothing specifica a Visual Basic che l'istanza
della classe a cui faceva riferimento la variabile uPrimoUomo
deve essere distrutta: in questo modo la quantità di
memoria occupata da quell'istanza (ricordo che ogni istanza
rappresenta una copia della classe) può essere liberata;
la distruzione del riferimento all'istanza è quindi una
buona abitudine utile soprattutto nel caso in cui si abbia a
che fare con classi complesse e scarse quantità di risorse
sul proprio pc. La parola chiave Nothing può essere utilizzata
anche per testare se una variabile contiene già un riferimento
ad un'istanza della classe oppure no:
If
uPrimoUomo Is Nothing Then
Set uPrimoUomo = New
clsUomo
End If |
In questo caso si usa l'operatore "Is" e non "="
perché la condizione di uguaglianza non avrebbe significato
in questo contesto, dato che Nothing non è una costante
con un valore particolare che è possibile confrontare
direttamente col contenuto di una variabile. Invece l'operatore
Is con la parola chiave Nothing verifica se la variabile uPrimoUomo
fa riferimento a un oggetto valido ed esistente oppure no.
Una volta creata una nuova istanza della classe ed assegnato
un suo riferimento alla variabile considerata, è possibile
utilizzare le proprietà e i metodi esposti dall'interfaccia
dell'oggetto. Per poter usufruire anche degli eventi dell'oggetto,
occorre che la variabile che contiene il riferimento all'istanza
sia stata dichiarata con la parola chiave WithEvents:
Dim
WithEvents uPrimoUomo As
clsUomo |
Se la classe Uomo espone degli eventi, questi saranno visibili
nella finestra del codice selezionando "uPrimoUomo"
dalla casella degli oggetti in alto a sinistra, e uno degli
eventi esposti dalla casella delle procedure in alto a destra:
In questa routine il programmatore può gestire l'evento,
esattamente come si farebbe con l'evento Load del Form, o con
l'evento Click di un pulsante. I metodi dell'oggetto vengono
richiamati esattamente come si farebbe con altre funzioni o
routine, utilizzando la sintassi:
nomeoggetto.metodo
parametri |
dove nomeoggetto indica il nome della variabile che contiene
il riferimento a un'istanza dell'oggetto, ad es.:
uPrimoUomo.Scrivi
Me, "Ciao" |
Identica sintassi vale per le proprietà, come si è
visto negli esempi precedenti; l'unica particolarità
è che quando si assegna un nuovo valore alla proprietà,
questo viene passato come parametro alla routine Property Let
(o Set), anche se ciò non è immediatamente visibile
al programmatore che utilizza l'oggetto. Ovviamente, come per
tutte le procedure, è possibile prevedere specifici argomenti:
ad es., per la proprietà Nome potrebbe essere utile specificare
se si vuole assegnare il primo o il secondo nome, oppure se
si vuole assegnare solo il nome di battesimo o anche il cognome,
ecc. In tal caso la routine Property Let avrà sempre
un argomento in più rispetto alla corrispondente Property
Get: questo argomento in più è proprio il nuovo
valore da assegnare alla proprietà. Ad es.:
Public
Property Get
Nome (bNomeCompleto As Boolean)
As String
If bNomeCompleto Then
Nome = sNome & " " & sCognome
Else
Nome = sNome
End If
End Property
Public
Property Let
Nome (bNomeCompleto As Boolean,
sNewName As String)
Dim nSpace As
Integer
If
bNomeCompleto Then
nSpace = InStr(1, sNewName, " ")
sNome = Left$(sNewName, nSpace - 1)
sCognome = mid$(sNewName, nSpace+1)
Else
sNome = sNewName
End If
End Property
|
La lettura e scrittura di questa proprietà può
avvenire ad es. nel modo seguente:
uPrimoUomo.Nome(True)
= "Paperino Paolino"
sNome = uPrimoUomo.Nome(False)
'sNome="Paperino" |
Nella prima istruzione la stringa "Paperino Paolino"
diventerà il valore del parametro sNewName della routine
Property Let. È importante che i tipi di dati coinvolti
siano congruenti: ovvero, se la proprietà è di
tipo stringa (Property Get
as String), l'ultimo parametro
della routine Property Let deve essere anch'esso di tipo stringa
(Property Let
(sNewName as String)), altrimenti si ottiene
un "Type Mismatch".
L'IDE di Visual Basic consente inoltre di specificare quale,
tra le proprietà o gli eventi di una classe, deve essere
la proprietà o l'evento predefinito: per fare ciò
occorre (previa attivazione della finestra del codice del modulo
di classe) selezionare la voce "Attributi routine"
dal menù "Strumenti"; nella finestra di dialogo
è possibile scegliere una delle proprietà, metodi
o eventi disponibili nell'interfaccia della classe e, tramite
le "opzioni" della finestra di dialogo, attivare o
disattivare la casella di controllo "predefinita nell'interfaccia
utente". Quando una proprietà è predefinita,
è possibile evitare di scriverne il nome nel codice,
ad es. l'istruzione:
è equivalente a:
perché
la proprietà Number è quella predefinita per
l'oggetto Err. Ciò può risultare a volte comodo
per il programmatore, ma in generale io sconsiglierei la seconda
sintassi, perché non rende immediatamente chiaro che
si sta utilizzando una proprietà, né quale proprietà
si sta utilizzando; non tutti infatti possono sapere che la
proprietà predefinita dell'oggetto Err è Number.
La prima sintassi invece è esplicita e più facilmente
leggibile; inoltre è anche più veloce perché
evita a Visual Basic di capire da solo quale proprietà
deve richiamare.
Un evento predefinito, ovviamente, non è un evento
che si verifica "per default" quando ricorre chissà
quale condizione: l'evento predefinito di un oggetto è
semplicemente quello che appare nella finestra del codice
quando si seleziona l'oggetto dalla casella in alto a sinistra,
o quando si fa doppio click su un controllo.
Ad es., l'evento Click è quello predefinito
per il pulsante di comando, l'evento Load è quello
predefinito per il form, l'evento Change è quello predefinito
per la casella di testo. La casella di controllo "predefinita
nell'interfaccia utente" non è attiva per i metodi,
per ovvi motivi.
La stessa finestra di dialogo permette di dare una descrizione
ad ogni elemento dell'interfaccia: tale descrizione sarà
mostrata nel visualizzatore oggetti e, per le proprietà,
anche nella parte inferiore della finestra delle proprietà;
inoltre è possibile nascondere il membro al visualizzatore
oggetti, scegliere se mostrare o nascondere una proprietà
nella finestra delle proprietà e in quale categoria,
e altre opzioni. La medesima finestra di dialogo è
visualizzabile anche tramite il visualizzatore oggetti, selezionando
un membro dell'interfaccia e scegliendo "proprietà"
dal menù che compare premendo il tasto destro del mouse.
|