Trentunesima lezione - Impariamo a creare e usare i controlli Active x

Nella lezione precedente abbiamo visto come correggere alcuni bug presenti nella prima versione del nostro "Campo Mino": ora vediamo come migliorare ulteriormente l'applicazione costruendo un controllo personalizzo che abbia le funzioni delle "mine". Visual Basic infti dà la possibilità al programmore di creare un controllo (come potrebbe essere ad es. il textbox o la label) che abbia carteristiche definite dallo stesso programmore in modo da poterlo adtare alle sue esigenze; tali controlli sono denomini "controlli ActiveX" perché sfruttano la tecnologia ActiveX crea 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 creo con Visual Basic può essere utilizzo 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 istanzia una o più volte dalle applicazioni client che conterranno quel controllo; la classe è definita in un file *.ctl, che viene compilo in un file *.ocx. Una volta creo il controllo, lo sviluppore che lo utilizza può collocarlo su un form come qualsiasi altro controllo, creando così un'istanza di progettazione del controllo; essa può essere programma tramite l'interfaccia esposta dal controllo: ad es. il programmore può inserire codice negli eventi (se ce ne sono) generi dal controllo, e può usarne i metodi e le proprietà. Quando invece l'applicazione client viene eseguita, viene genera un'istanza di esecuzione del controllo, la quale esegue il codice incapsulo 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 personalizzo. 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 genere 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 generi dal mouse o dalla tastiera, gli eventi di inizializzazione e terminazione del controllo, ecc. L'interazione con il contenitore del controllo è però supporta dall'oggetto Extender, che fornisce alcune proprietà di estensione, cioè le carteristiche dell'interfaccia del componente ActiveX controlle dal suo contenitore. Ad es. un controllo ActiveX non può sapere a priori quale sarà il contenitore nel quale sarà colloco, 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'utilizzore del controllo non si accorge della differenza, perché per lui tutte queste proprietà sono fornite dal controllo ActiveX; invece lo sviluppore del controllo deve fare un po' più tenzione. Infti, 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 determino 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 supporta dal contenitore, si verificherà un errore (più precisamente l'errore 438: "L'oggetto non supporta la proprietà o il metodo"): pertanto è opportuno che lo sviluppore 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 è sto effettivamente colloco nel contenitore, ovvero quando è sta crea un'istanza del controllo; in altre parole, l'oggetto Extender non è disponibile nell'evento Initialize dell'oggetto UserControl. Nuralmente Visual Basic fornisce per ogni oggetto Extender non solo le proprietà standard indice 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 carteristiche 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 tenzione 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'utilizzore 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: infti, 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.
Do che il controllo ActiveX che stiamo per costruire sarà da utilizzare nel campo mino, apriamo il progetto relivo 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 Mino apparirà in grassetto perché è il progetto di avvio (ovvero quello che viene avvio 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 relivo al controllo ActiveX è identifico (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 personalizza: 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 trtasse di un form, un CommandButton; questo pulsante, che rappresenta un controllo (in questo caso IL controllo) costitutivo del controllo ActiveX, dovrà avere un nome approprio, ad es. "cmdMina". Aprendo l'editor del codice relivo alla classe Mina.ctl vi accorgerete che sono elenci due oggetti: cmdMina e UserControl, ciascuno con i suoi eventi.
Nella casella degli strumenti, inoltre, è sta aggiunta automicamente un'icona che rappresenta il nostro controllo ActiveX; proviamo a inserire il nostro controllo Mina all'interno della finestra del Campo Mino come faremmo con un qualunque altro controllo (se l'icona dell'ActiveX risulta disabilita è perché la finestra di progettazione dello UserControl è ancora aperta: chiudetela). Ora, quasi certamente non vi sarete preoccupi della posizione del pulsante cmdMina all'interno dello UserControl, cosicché disegnando il controllo Mina all'interno del form del Campo Mino avete ottenuto un risulto diciamo insoddisfacente; poco importa, basta ridimensionare il pulsante sfruttando l'evento Resize. Quando il controllo Mina è sto inserito nel form, è sta crea un'istanza di progettazione del controllo, con un'istanza sia dell'oggetto UserControl, sia dell'oggetto cmdMina: pertanto quando la Mina è colloca sul form saranno generi 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à estamente 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 fto, 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 ridimensiono 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 crea un'istanza di progettazione del controllo, ma anche quando viene crea 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 noto che all'istanza di progettazione del controllo Mina è sto assegno per default il nome "Mina1" da Visual Basic: controllando gli eventi associi 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) è incapsula 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 visualizzore oggetti (facendo tenzione che il progetto tivo sia quello del campo mino 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 è configuro per essere un pulsante di comando, per cui quelle proprietà non avrebbero senso; infti la proprietà DefaultCancel dello UserControl è per default imposta 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 delego per lo stesso evento del controllo ActiveX: la delega è il meccanismo che consente di esporre l'interfaccia dello UserControl o dei controlli costitutivi all'utilizzore del controllo ActiveX. Il fto che queste interfacce non siano automicamente disponibili all'utente può sembrare una seccura per lo sviluppore del controllo, che deve provvedere a delegare tutte quelle che gli sembra opportuno, ma consente la massima libertà nella creazione dell'interfaccia appropria 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 genero dall'evento Resize dell'oggetto UserControl:

Prive 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 Mino, 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 guida 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 dichiara 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 specifico dall'utilizzore del controllo, segnala tramite l'istruzione PropertyChanged che il valore della proprietà è cambia, 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'utilizzore 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'utilizzore del controllo possa scegliere liberamente se distivarlo completamente o soltanto "in parte". Questa soluzione va bene nel nostro caso specifico, ma nuralmente potrebbe non essere adta a qualunque situazione: lo sviluppore 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'utilizzore sappia estamente 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 intercetto tramite l'evento MouseDown; ora l'intercettazione non viene più fta a livello dell'evento genero nell'applicazione Campo Mino, ma direttamente nel controllo ActiveX: in altri termini, non è più l'utilizzore del controllo a preoccuparsi dell'intercettazione del click destro, ma è il suo sviluppore, 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 distivo anche se lo UserControl è ancora tivo; se il click destro fosse intercetto solo nell'evento MouseDown di cmdMina non avremmo risolto il problema che abbiamo incontro nella scorsa lezione. Infti, se cmdMina è tivo, 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 è distivo, gli input del mouse andranno solo sullo UserControl e ovviamente non su cmdMina: perciò è necessario intercettare anche l'evento UserControl_MouseDown:

Prive 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

Prive Sub cmdMina_Click()
	RaiseEvent ClickLeft
End Sub

Prive 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 genero dall'evento cmdMina_MouseDown, a discrezione dello sviluppore. Ora l'utilizzore del controllo può scrivere del codice come questo:

Prive Sub Mina1_ClickRight()
	Mina1.EnabledCmd = Not Mina1.EnabledCmd
End Sub

Cliccando col tasto destro sull'istanza Mina1, l'utilizzore tiva o distiva automicamente il solo cmdMina del controllo ActiveX, lasciando tivo 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 fto nella precedente lezione, perché il click sinistro viene genero solo quando cmdMina è tivo. Inserendo un'istruzione del tipo: Debug.Print "pippo" nell'evento Mina1_ClickLeft, ci si può rendere conto meglio di come funziona il procedimento implemento.



Note sul corso:
I diritti di ognuna delle lezioni presente in queste pagine appartengono all'autore Giorgio Abraini. La riproduzione e la divulgazione delle stesse sono consentite solamente dietro citazione di fonte ed autore.
Per suggerimenti, consigli o richieste conttare giorgio102@libero.it.
Fonte : VBItalia.it