Nella
lezione precedente abbiamo visto come correggere alcuni bug
presenti nella prima versione del nostro "Campo Minato":
ora vediamo come migliorare ulteriormente l'applicazione costruendo
un controllo personalizzato che abbia le funzioni delle "mine".
Visual Basic infatti dà la possibilità al programmatore
di creare un controllo (come potrebbe essere ad es. il textbox
o la label) che abbia caratteristiche definite dallo stesso
programmatore in modo da poterlo adattare alle sue esigenze;
tali controlli sono denominati "controlli ActiveX"
perché sfruttano la tecnologia ActiveX creata da Microsoft
per estendere la precedente tecnologia OLE; oltre ai controlli,
esistono anche i server ActiveX e i documenti ActiveX. Questa
tecnologia permette una maggiore integrazione con internet
ed è sfruttabile da tutti i linguaggi di programmazione
che la supportano, così che un controllo ActiveX creato
con Visual Basic può essere utilizzato ad es. anche
tramite il Visual C++ o il Borland Delphi.
Quando si crea un controllo ActiveX in effetti si definisce
la classe del controllo, classe che sarà poi istanziata
una o più volte dalle applicazioni client che conterranno
quel controllo; la classe è definita in un file *.ctl,
che viene compilato in un file *.ocx. Una volta creato il
controllo, lo sviluppatore che lo utilizza può collocarlo
su un form come qualsiasi altro controllo, creando così
un'istanza di progettazione del controllo; essa può
essere programmata tramite l'interfaccia esposta dal controllo:
ad es. il programmatore può inserire codice negli eventi
(se ce ne sono) generati dal controllo, e può usarne
i metodi e le proprietà. Quando invece l'applicazione
client viene eseguita, viene generata un'istanza di esecuzione
del controllo, la quale esegue il codice incapsulato in esso
e interagisce col suo contenitore (tipicamente un form) e
con gli altri controlli eventualmente presenti.
Per creare un controllo ActiveX occorre selezionare "Controllo
ActiveX" dalla finestra di apertura di un nuovo progetto:
Visual Basic creerà una finestra di progettazione del
tutto analoga a quella di un form; essa rappresenta l'oggetto
UserControl, che è la base su cui viene costruito ogni
controllo personalizzato. All'interno dello UserControl, come
in un form, è possibile collocare i controlli costitutivi
del componente ActiveX; ogni istanza del componente, pertanto,
conterrà un'istanza dell'oggetto UserControl e dei
vari controlli disposti al suo interno, così come quando
al caricamento di un form durante l'esecuzione di un'applicazione
vengono generate istanze di tutti i controlli presenti nel
form. L'oggetto UserControl fornisce l'interfaccia per far
interagire il controllo ActiveX con i suoi controlli costitutivi
eventualmente in risposta ad azioni dell'utente: ad es. fornisce
gli eventi generati dal mouse o dalla tastiera, gli eventi
di inizializzazione e terminazione del controllo, ecc.
L'interazione con il contenitore del controllo è però
supportata dall'oggetto Extender, che fornisce alcune
proprietà di estensione, cioè le caratteristiche
dell'interfaccia del componente ActiveX controllate dal suo
contenitore.
Ad es. un controllo ActiveX non può sapere a priori
quale sarà il contenitore nel quale sarà collocato,
per cui una proprietà come Parent, che appunto identifica
il contenitore del controllo, non può essere gestita
dal controllo stesso ma deve essere gestita dal suo contenitore:
ciò avviene tramite l'oggetto Extender, accessibile
tramite l'apposita proprietà dell'oggetto UserControl.
Lo stesso dicasi per proprietà come Left o Top, che
indicano la posizione del controllo all'interno del contenitore:
è intuibile che dev'essere il contenitore a gestirle,
e anche questo avviene tramite l'oggetto Extender.
L'utilizzatore del controllo non si accorge della differenza,
perché per lui tutte queste proprietà sono fornite
dal controllo ActiveX; invece lo sviluppatore del controllo
deve fare un po' più attenzione. Infatti, secondo le
specifiche ActiveX definite da Microsoft ogni oggetto Extender
dovrebbe fornire almeno le proprietà Cancel, Default,
Name, Parent e Visible, ma può darsi che un determinato
contenitore non gestisca queste proprietà o altre non
standard, come ad es. Index, o Left o Enabled. In tal caso,
se si tenta di accedere a una proprietà di estensione
non supportata dal contenitore, si verificherà un errore
(più precisamente l'errore 438: "L'oggetto non
supporta la proprietà o il metodo"): pertanto
è opportuno che lo sviluppatore del controllo usi codice
di intercettazione degli errori quando tenta di accedere a
proprietà di estensione. Inoltre, proprio perché
l'oggetto Extender dipende dal contenitore, esso diventa disponibile
solo quando il controllo è stato effettivamente collocato
nel contenitore, ovvero quando è stata creata un'istanza
del controllo; in altre parole, l'oggetto Extender non è
disponibile nell'evento Initialize dell'oggetto UserControl.
Naturalmente Visual Basic fornisce per ogni oggetto Extender
non solo le proprietà standard indicate sopra ma anche
altre (ad es. Tag, TabStop, TooltipText) insieme ad alcuni
metodi ed eventi (ad es. il metodo SetFocus o l'evento GetFocus).
Altre importanti proprietà per una corretta interazione
del controllo con il contenitore sono fornite dall'oggetto
AmbientProperties, accessibile tramite la proprietà
Ambient dell'oggetto UserControl: queste proprietà
di ambiente definiscono alcune caratteristiche dell'ambiente
in cui si trova il controllo ActiveX, ad es. le proprietà
BackColor e ForeColor suggeriscono colori di sfondo e di primo
piano per fare in modo che il controllo ActiveX si presenti
in modo uniforme con il suo contenitore. In particolare, occorre
prestare attenzione alla proprietà DisplayName, che
restituisce il nome dell'istanza del controllo (differisce
dalla proprietà UserControl.Name perché quest'ultima
definisce il nome del controllo e non della sua particolare
istanza), e alla proprietà UserMode, che indica se
ci si trova in fase di esecuzione (UserMode=True) o di progettazione
(UserMode=False).
Se ci si trova in fase di progettazione, significa che l'istanza
non è in modalità "utente" (per utente
si intende qui l'utilizzatore finale del controllo, ovvero
chi usa l'applicazione) e quindi la proprietà UserMode
è False. Tale proprietà risulta molto utile
quando certe porzioni di codice vanno eseguite solo in fase
di esecuzione: infatti, anche se a prima vista può
sembrare strano, il codice di un controllo ActiveX può
essere eseguito anche in fase di progettazione. Alcuni esempi
chiariranno le nozioni fin qui esposte.
Dato
che il controllo ActiveX che stiamo per costruire sarà
da utilizzare nel campo minato, apriamo il progetto relativo
al gioco e aggiungiamo un nuovo progetto scegliendo "Controllo
ActiveX":
Visual Basic creerà un "gruppo" di progetti
visibile nella finestra "gestione progetti", in
cui l'originario Campo Minato apparirà in grassetto
perché è il progetto di avvio (ovvero quello
che viene avviato quando si preme F5); è possibile
impostare un progetto come progetto di avvio cliccando col
tasto destro del mouse sulla voce del progetto e selezionando
l'apposita voce dal menù di contesto. Il progetto relativo
al controllo ActiveX è identificato (oltre che dall'icona)
da un oggetto UserControl a cui corrisponde un file *.ctl;
possiamo dare "Mina" come nome dell'oggetto, visto
che il controllo serve appunto a creare una mina personalizzata:
questo sarà il nome della classe (come "TextBox"),
poi ogni istanza dell'oggetto avrà il suo nome particolare,
ad es. Mina1, Mina2 (come TextBox1, TextBox2) ecc.
Poiché la mina è costituita essenzialmente da
un pulsante, all'interno della finestra di progettazione dello
UserControl inseriamo, come se si trattasse di un form, un
CommandButton; questo pulsante, che rappresenta un controllo
(in questo caso IL controllo) costitutivo del controllo ActiveX,
dovrà avere un nome appropriato, ad es. "cmdMina".
Aprendo l'editor del codice relativo alla classe Mina.ctl
vi accorgerete che sono elencati due oggetti: cmdMina e UserControl,
ciascuno con i suoi eventi.
Nella
casella degli strumenti, inoltre, è stata aggiunta
automaticamente un'icona che rappresenta il nostro controllo
ActiveX;
proviamo a inserire il nostro controllo Mina all'interno della
finestra del Campo Minato come faremmo con un qualunque altro
controllo (se l'icona dell'ActiveX risulta disabilitata è
perché la finestra di progettazione dello UserControl
è ancora aperta: chiudetela).
Ora, quasi certamente non vi sarete preoccupati della posizione
del pulsante cmdMina all'interno dello UserControl, cosicché
disegnando il controllo Mina all'interno del form del Campo
Minato avete ottenuto un risultato diciamo insoddisfacente;
poco importa, basta ridimensionare il pulsante sfruttando
l'evento Resize.
Quando il controllo Mina è stato inserito nel form,
è stata creata un'istanza di progettazione del controllo,
con un'istanza sia dell'oggetto UserControl, sia dell'oggetto
cmdMina: pertanto quando la Mina è collocata sul form
saranno generati gli eventi di inizializzazione del controllo
e anche l'evento Resize; se all'interno di questo evento scriviamo
il seguente codice:
With
cmdMina
.Left = 0
.Top = 0
.Height = UserControl.Height
.Width = UserControl.Width
End With |
vedremo
che all'inserimento della Mina sul form il pulsante cmdMina
avrà esattamente le dimensioni che vogliamo dargli
noi disegnando il controllo sul form, perché è
il controllo stesso che obbliga cmdMina ad avere quelle dimensioni
in occasione dell'evento Resize.
Questa è anche la dimostrazione del fatto, cui accennavo
prima, che il codice di un controllo ActiveX è eseguito
anche in fase di progettazione, oltre che a run-time. Ora,
poiché di norma non è possibile ridimensionare
i controlli in fase di esecuzione, dovremmo assicurarci che
il codice dell'evento Resize venga eseguito solo in fase di
progettazione: a questo scopo torna utile la proprietà
UserMode descritta sopra:
If
Not UserControl.Ambient.UserMode
Then
With cmdMina
.Left = 0
.Top = 0
.Height = UserControl.Height
.Width = UserControl.Width
End With
End If |
Il
pulsante cmdMina viene ridimensionato solo se UserMode è
falso, ovvero solo se siamo in fase di progettazione; questo
però sarebbe un errore, perché in realtà
il ridimensionamento avviene non solo quando viene creata
un'istanza di progettazione del controllo, ma anche quando
viene creata un'istanza di esecuzione: perciò è
meglio togliere la condizione If (potete rendervi conto voi
stessi della differenza eseguendo il progetto con e senza
la condizione). Ora, avrete notato che all'istanza di progettazione
del controllo Mina è stato assegnato per default il
nome "Mina1" da Visual Basic: controllando gli eventi
associati all'oggetto Mina1 noterete anche che manca l'evento
Resize, che invece esiste per quasi tutti i controlli standard.
Questo avviene perchè l'interfaccia dello UserControl
(come anche del pulsante cmdMina) è incapsulata all'interno
del controllo ActiveX e quindi non visibile di per sé
all'utente che utilizza il controllo; gli eventi, le proprietà
e i metodi dell'oggetto Mina disponibili per default sono
solo quelli provvisti dall'oggetto Extender, e li potete vedere
utilizzando il visualizzatore oggetti (facendo attenzione
che il progetto attivo sia quello del campo minato e non quello
del controllo ActiveX) e selezionando l'oggetto "Mina".
Mancano le proprietà Cancel e Default, che pure fanno
parte delle proprietà standard che ogni oggetto Extender
dovrebbe fornire, semplicemente perché il nostro controllo
ActiveX non è configurato per essere un pulsante di
comando, per cui quelle proprietà non avrebbero senso;
infatti la proprietà DefaultCancel dello UserControl
è per default impostata su False.
Per rendere visibile anche all'utente l'evento Resize
(o altri elementi dell'interfaccia) dello UserControl, occorre
dichiarare un apposito evento Resize che faccia da delegato
per lo stesso evento del controllo ActiveX: la delega è
il meccanismo che consente di esporre l'interfaccia dello
UserControl o dei controlli costitutivi all'utilizzatore del
controllo ActiveX. Il fatto che queste interfacce non siano
automaticamente disponibili all'utente può sembrare
una seccatura per lo sviluppatore del controllo, che deve
provvedere a delegare tutte quelle che gli sembra opportuno,
ma consente la massima libertà nella creazione dell'interfaccia
appropriata che il controllo deve avere per chi lo utilizza.
Tornando a noi, dobbiamo quindi dichiarare un evento Resize
nella sezione delle dichiarazioni dello UserControl:
Option
Explicit
Public
Event
Resize()
|
Questo
sarà un evento appartenente all'interfaccia del controllo
ActiveX Mina e dovrà essere generato dall'evento Resize
dell'oggetto UserControl:
Private
Sub
UserControl_Resize()
With
cmdMina
.Left = 0
.Top = 0
.Height = UserControl.Height
.Width = UserControl.Width
End With
RaiseEvent Resize
End
Sub
|
Ora,
nel form del Campo Minato, l'oggetto Mina1, istanza del controllo
Mina, disporrà anche dell'evento Resize. Volendo evitare
di seguire la procedura di delega manualmente per tutti (o
quasi) gli elementi dell'interfaccia del controllo ActiveX,
è possibile avvalersi della creazione guidata disponibile
come aggiunta nell'IDE di Visual Basic.
Come visto nella scorsa lezione, gli eventi che più
ci interessano sono MouseDown e Click, unitamente alla proprietà
Enabled; questa proprietà non figura inizialmente tra
quelle esposte dall'interfaccia del controllo Mina perché,
nonostante sia fornita dall'oggetto Extender, è necessario
che sia esplicitamente dichiarata come delega dell'omonima
proprietà dello UserControl:
Public
Property Get
Enabled() As Boolean
Enabled = UserControl.Enabled
End Property
Public
Property Let
Enabled(ByVal vNewValue As Boolean)
UserControl.Enabled = vNewValue
PropertyChanged "Enabled"
End Property
|
La
Property Get non fa altro che esportare il valore corrente
della corrispondente proprietà dell'oggetto UserControl,
mentre la Property Let, oltre a impostare tale valore
secondo quanto specificato dall'utilizzatore del controllo,
segnala tramite l'istruzione PropertyChanged che il
valore della proprietà è cambiata, in modo che
Visual Basic possa memorizzare l'impostazione e assegnarla
correttamente alla proprietà quando crea l'istanza
di esecuzione. PropertyChanged è un'istruzione che
serve in particolar modo in fase di progettazione, affinché
Visual Basic aggiorni correttamente le impostazioni delle
proprietà per quelle istanze che hanno subito modifiche.
L'implementazione della proprietà Enabled descritta
sopra è quella standard, ma a noi serve qualcosa di
leggermente diverso: quello che dovremmo abilitare o disabilitare
non è tanto il controllo ActiveX Mina ma piuttosto
il pulsante cmdMina, perché in ogni caso dobbiamo essere
in grado di intercettare almeno il click col tasto destro
per issare o ammainare la bandiera sul pulsante.
Tuttavia, l'utilizzatore del componente ActiveX dovrebbe poter
comunque disabilitare, se lo desidera, l'intero controllo
e non semplicemente i suoi controlli costitutivi. Una soluzione
a questo problema è delegare le proprietà Enabled
sia dell'oggetto UserControl sia dell'oggetto cmdMina, in
modo che l'utilizzatore del controllo possa scegliere liberamente
se disattivarlo completamente o soltanto "in parte".
Questa soluzione va bene nel nostro caso specifico, ma naturalmente
potrebbe non essere adatta a qualunque situazione: lo sviluppatore
deve valutare caso per caso se è opportuno esporre
membri dell'interfaccia dei controlli costitutivi. Inseriamo
quindi la proprietà EnabledCmd:
Public
Property
Get EnabledCmd() As Boolean
EnabledCmd = cmdMina.Enabled
End Property
Public
Property Let
EnabledCmd(ByVal vNewValue As
Boolean)
cmdMina.Enabled = vNewValue
PropertyChanged "EnabledCmd"
End Property
|
Inoltre,
per la nostra Mina risulta molto utile generare un apposito
evento per il click destro, in modo che l'utilizzatore sappia
esattamente quando occorre disabilitare solo il cmdMina e
non l'intero UserControl; per far ciò dichiariamo due
eventi "click":
Option
Explicit
Public
Event
Resize()
Public Event ClickLeft()
Public Event ClickRight()
|
ClickLeft
corrisponde al normale evento Click, ClickRight corrisponde
al click col tasto destro che in precedenza avevamo intercettato
tramite l'evento MouseDown; ora l'intercettazione non viene
più fatta a livello dell'evento generato nell'applicazione
Campo Minato, ma direttamente nel controllo ActiveX: in altri
termini, non è più l'utilizzatore del controllo
a preoccuparsi dell'intercettazione del click destro, ma è
il suo sviluppatore, che fornisce direttamente l'evento ClickRight
nell'interfaccia del controllo.
L'intercettazione deve avvenire nell'evento MouseDown sia
dello UserControl sia di cmdMina, perché quest'ultimo
potrebbe essere disattivato anche se lo UserControl è
ancora attivo; se il click destro fosse intercettato solo
nell'evento MouseDown di cmdMina non avremmo risolto il problema
che abbiamo incontrato nella scorsa lezione.
Infatti, se cmdMina è attivo, tutti gli input del mouse
andranno su di esso senza generare gli eventi corrispondenti
di UserControl: quindi è necessario intercettare l'evento
cmdMina_MouseDown; invece se cmdMina è disattivo,
gli input del mouse andranno solo sullo UserControl e ovviamente
non su cmdMina: perciò è necessario intercettare
anche l'evento UserControl_MouseDown:
Private
Sub
UserControl_MouseDown(Button As
Integer, Shift As Integer,
x As Single, y As
Single)
If Button = vbRightButton
Then
RaiseEvent ClickRight
End If
End Sub
Private
Sub
cmdMina_Click()
RaiseEvent ClickLeft
End Sub
Private
Sub cmdMina_MouseDown(Button As
Integer, Shift As Integer,
X As Single, Y As
Single)
If Button = vbRightButton
Then
RaiseEvent ClickRight
End If
End Sub
|
Anche
l'evento ClickLeft potrebbe essere generato dall'evento cmdMina_MouseDown,
a discrezione dello sviluppatore. Ora l'utilizzatore del controllo
può scrivere del codice come questo:
Private
Sub
Mina1_ClickRight()
Mina1.EnabledCmd = Not Mina1.EnabledCmd
End Sub |
Cliccando
col tasto destro sull'istanza Mina1, l'utilizzatore attiva
o disattiva automaticamente il solo cmdMina del controllo
ActiveX, lasciando attivo il controllo in se stesso e permettendo
così l'intercettazione di un ulteriore click col tasto
destro; in questo modo non occorre più "inibire"
il click sinistro utilizzando un flag come avevamo fatto nella
precedente lezione, perché il click sinistro viene
generato solo quando cmdMina è attivato. Inserendo
un'istruzione del tipo: Debug.Print "pippo" nell'evento
Mina1_ClickLeft, ci si può rendere conto meglio di
come funziona il procedimento implementato.
|