simply4you.it simply4you.it

   Home
     Articoli, Tutorial...
       Visual Basic
         Lezione 32
Indice
Lez. 01
Lez. 02
Lez. 03
Lez. 04
Lez. 05
Lez. 06
Lez. 07
Lez. 08
Lez. 09
Lez. 10
Lez. 11
Lez. 12
Lez. 13
Lez. 14
Lez. 15
Lez. 16
Lez. 17
Lez. 18
Lez. 19
Lez. 20
Lez. 21
Lez. 22
Lez. 23
Lez. 24
Lez. 25
Lez. 26
Lez. 27
Lez. 28
Lez. 29
Lez. 30
Lez. 31
Lez. 32
Lez. 33


Autore
Giorgio Abraini

E-Mail
giorgio102@libero.it

VBItalia.it



Commenta anche tu !  


LINK Visual Basic













   Invia il link di questo articolo ad un amico  Invia ad un amico Visualizza la versione stampabile di questo articolo  Stampa Commenta questo Articolo  Commenta Parlane in Chat  Chat   Parlane sul Forum  Forum

Corsi Programmazione in aula:
ASP, Dreamweaver MX, Flash MX, HTML, Introduzione alla programmazione strutturata, JSP/Servlet, JavaScript, Linguaggio C, Linux programmazione shell, Oracle PL/SQL, Oracle Portal 9.0.2, PHP, Perl, Programmazione Java, Programmazione Java avanzata, Programmazione web Corso avanzato, Programmazione web Corso base, Programmazione web Corso intermedio, XML


Trentaduesima lezione - Uso avanzo di controlli Active X

Nella scorsa lezione abbiamo visto come creare una mina personalizza in grado di generare tre eventi: Resize, ClickLeft e ClickRight; gli ultimi due devono essere sfrutti dal progetto Campo Mino per abilitare/disabilitare le mine. Prima di procedere alla sostituzione delle vecchie mine (command button) con le nuove mine (usercontrol activex), aggiungiamo qualche carteristica che può tornarci utile: nella precedente versione del Campo Mino avevamo utilizzo la proprietà Tag dei pulsanti per sapere se nascondevano una mina o no; ora che abbiamo un controllo apposito possiamo aggiungere la proprietà adta per questa informazione.
Nella sezione delle dichiarazioni generali aggiungiamo una variabile priva denomina mlNumeroMine: do che dovrà contenere un numero intero, Long sembra essere il tipo adto:

Option Explicit

Prive mlNumeroMine As Long

Public Event Resize()
Public Event ClickLeft()
Public Event ClickRight()

Aggiungiamo anche una proprietà NumeroMine per rendere accessibile l'informazione al client che userà il controllo:

Public Property Get NumeroMine() As Long
	NumeroMine = mlNumeroMine
End Property

Public Property Let NumeroMine(ByVal vNewValue As Long)
	mlNumeroMine = vNewValue
End Property

mlNumeroMine conterrà il numero di mine circostanti ogni specifica istanza del controllo; il valore -1 indicherà la presenza di una mina proprio sotto l'istanza considera: la proprietà NumeroMine quindi fa le veci della proprietà Tag in precedenza utilizza. Già che ci siamo, deleghiamo la proprietà Caption del pulsante del nostro controllo ActiveX in modo che sia accessibile dagli utilizzori del componente:

Public Property Get Caption() As String
	Caption = cmdMina.Caption
End Property

Public Property Let Caption(ByVal vNewValue As String)
	cmdMina.Caption = vNewValue
End Property

Dobbiamo inoltre delegare anche la proprietà Picture, traverso la quale viene visualizza una bandiera sulle mine disabilite:

Public Property Get Picture() As IPictureDisp
	Set Picture = cmdMina.Picture
End Property

Public Property Set Picture(ByVal vNewValue As IPictureDisp)
	cmdMina.Picture = vNewValue
End Property

Come avevamo già visto in precedenza (e come si può capire consultando il visualizzore oggetti), la proprietà Picture dell'oggetto CommandButton è un'istanza della classe IPictureDisp: dello stesso tipo dovrà pertanto essere la proprietà Picture aggiunta al nostro controllo ActiveX. Trtandosi di un oggetto, la routine di impostazione della proprietà dovrà essere una Property Set e non una Property Let; per lo stesso motivo si utilizza l'istruzione Set nel codice della routine Property Get. Nuralmente il pulsante contenuto nello UserControl dovrà avere la proprietà Style imposta a 1-Graphical.
Prima di sostituire le vecchie mine con le nuove, sarebbe bene fare in modo che il caricamento dei controlli avvenga in modo automico anziché disporli manualmente sul form: finché sono 16 si può anche fare a mano, ma il giorno in cui si decidesse di portarli a 100… La cosa importante è che ci sia almeno un'istanza disegna sul form, con indice zero, come se fosse il primo elemento di una mrice di controlli; poi usando l'istruzione Load se ne possono caricare quanti si vuole. Per semplificarci la vita definiamo nel form Form1 un paio di costanti per memorizzare il numero di "righe" e "colonne" della mrice di pulsanti "Mina": il concetto di righe e colonne ha a che fare con la loro disposizione spaziale nel form, non con le dimensioni della mrice di controlli, poiché tale mrice è unidimensionale; continuando con gli esempi finora fti, i pulsanti saranno 16 divisi in 4 righe e 4 colonne:

Prive Const mlNUMERO_RIGHE As Long = 4
Prive Const mlNUMERO_COLONNE As Long = 4

Nella routine di inizializzazione del form dovremo perciò caricare 15 pulsanti (il primo esiste già e ha indice zero imposto in fase di progettazione) disponendoli in una mrice 4 x 4:

Prive Sub Form_Load()
Dim lContRighe As Long
Dim lContColonne As Long
Dim lContPulsanti As Long

MaxContaMine = 13
lContPulsanti = 0

For lContRighe = 1 To mlNUMERO_RIGHE
    
    If lContPulsanti Then
        lContPulsanti = lContPulsanti + 1
        Load Mina(lContPulsanti)
        With Mina(lContPulsanti)
            .Enabled = False
            .Visible = True
            .Left = Mina(lContPulsanti - mlNUMERO_COLONNE).Left
            .Top = Mina(lContPulsanti - 1).Top _
			+ Mina(lContPulsanti - 1).Height
        End With
    End If
    
    For lContColonne = 2 To mlNUMERO_COLONNE
        
        lContPulsanti = lContPulsanti + 1
        Load Mina(lContPulsanti)
        With Mina(lContPulsanti)
            .Enabled = False
            .Visible = True
            .Left = Mina(lContPulsanti - 1).Left _
			+ Mina(lContPulsanti - 1).Width
            .Top = Mina(lContPulsanti - 1).Top
        End With
    
    Next lContColonne
Next lContRighe

End Sub

Nella routine vengono utilizzi tre contori: uno per le righe, uno per le colonne, uno per i pulsanti; mentre i primi due sono gestiti dai rispettivi cicli, il terzo serve per la mrice dei controlli vera e propria. Il ciclo per riga non fa altro che impostare le coordine del primo pulsante di ogni riga in base a quelle del primo pulsante della riga precedente (il nuovo pulsante viene messo sotto): queste impostazioni sono nuralmente salte a piè pari se l'indice lContPulsanti è ancora a zero, perché in tal caso stiamo trtando il pulsante Mina(0) che abbiamo già inserito in fase di progettazione. All'interno del ciclo per riga c'è il ciclo per colonna, che ripete sostanzialmente le stesse istruzioni con la differenza che le coordine dipendono dal pulsante precedente nella medesima riga (il nuovo pulsante viene messo affianco). Tutti i pulsanti vengono inizialmente disabiliti, poiché l'abilitazione avverrà nel momento in cui il giocore decide di iniziare una nuova partita. Volendo modificare la disposizione dei pulsanti basterà cambiare il valore assegno alle costanti mlNUMERO_RIGHE e mlNUMERO_COLONNE.
Se prove ad avviare il progetto vi dovreste accorgere di una cosa curiosa: tutti i pulsanti sono disabiliti tranne il primo. Ciò avviene perché l'istruzione Enabled=False viene ignora quando lContPulsanti è uguale a zero; per evitare l'inconveniente si può forzare il programma ad eseguire quell'istruzione (utilizzando ad es. la clausola Else nel costrutto If…Then) oppure si può impostare direttamente a False la proprietà Enabled del pulsante Mina(0) in fase di progettazione. In realtà questa seconda procedura non funziona, per un motivo molto semplice: Visual Basic non salva il valore della proprietà modifico dall'utente. Mentre le proprietà gestite direttamente dall'Extender (ad es. Left e Top) sono automicamente salve da Visual Basic quando l'utente le cambia in fase di progettazione, le altre proprietà (come appunto Enabled, che abbiamo delego con le routine Property) necessitano di un salvaggio esplicito: tale salvaggio, e la corrispondente lettura del valore modifico, avvengono rispettivamente negli eventi WriteProperties e ReadProperties dell'oggetto UserControl. In prica, ogni volta che l'istanza del pulsante che abbiamo inserito nel form viene distrutta (ad es. quando si chiude la finestra di progettazione del form), viene genero l'evento WriteProperties che provvede a salvare i valori correnti delle proprietà del componente. Quando invece l'istanza viene ricrea (ad es. ritivando la finestra di progettazione in precedenza chiusa), viene genero l'evento ReadProperties, che assegna i valori delle proprietà secondo le ultime impostazioni salve. Tutti questi valori sono fisicamente memorizzi nel file *.frm che definisce il form dell'applicazione con i controlli disegni al suo interno: è un semplice file di testo che contiene, per ogni controllo, i valori delle proprietà (non tutte) ed eventualmente le variabili e tutto il codice che vediamo nella finestra del codice corrispondente al form. Ai file *.frm per i form corrispondono i file *.ctl per i controlli ActiveX.
Nuralmente il file su disco viene salvo solo alla chiusura del progetto; tutte le modifiche effettue mentre il progetto è ancora aperto sono mantenute in memoria ma non effettivamente scritte sul disco.
Ora, affinché le nostre proprietà personalizze possano "ricordare" l'ultimo valore assegno dall'utente, dobbiamo inserire del codice apposito nei suddetti eventi ReadProperties e WriteProperties, ad es:

Prive Sub UserControl_ReadProperties(PropBag As PropertyBag)
On Error Resume Next

With PropBag
    Enabled = .ReadProperty("Enabled", True)
    EnabledCmd = .ReadProperty("EnabledCmd", True)
    Caption = .ReadProperty("Caption")
End With

End Sub

Prive Sub UserControl_WriteProperties(PropBag As PropertyBag)
With PropBag
Call .WriteProperty("Enabled", UserControl.Enabled, True)
Call .WriteProperty("EnabledCmd", cmdMina.Enabled, True)
Call .WriteProperty("Caption", cmdMina.Caption)
End With
End Sub

L'oggetto PropertyBag, argomento di entrambi gli eventi, è quello che contiene le informazioni sui valori delle proprietà e che consente di leggerle/scriverle tramite i metodi "ReadProperty" e "WriteProperty": il primo vuole come argomenti il nome della proprietà ed eventualmente il valore di default, da usare nel caso in cui non sia disponibile un valore salvo; il secondo invece, oltre al nome della proprietà, vuole anche il valore di essa da salvare, ed eventualmente ancora il valore di default. L'ultimo parametro (valore di default) è importante perché il valore della proprietà è effettivamente salvo solo se è diverso da quello di default, in modo da risparmiare tempo e spazio. L'oggetto PropertyBag è gestito direttamente da Visual Basic, il programmore deve solo preoccuparsi di leggere/salvare le proprietà che gli interessano: un valido aiuto in questo compito è do dalla aggiunta "Creazione guida interfaccia controlli ActiveX" (menù aggiunte), che inserisce automicamente il codice necessario per le proprietà selezione. Nell'evento ReadProperties è sta aggiunta un'istruzione di gestione degli errori come suggerito dalla guida, per evitare problemi nel caso in cui un furbastro abbia modifico manualmente il file *.frm inserendo valori non validi per una certa proprietà. Non tutte le proprietà necessitano di essere salve: ad es. la proprietà NumeroMine non ha bisogno di essere salva, perché il suo valore è genero in fase di esecuzione dall'applicazione client, non è modifico dall'utente in fase di progettazione. C'è poi un terzo evento, InitProperties, che viene genero quando l'istanza del componente è crea per la prima volta (ad es. quando il pulsante è disegno sul form): infti in questo caso non esistono valori delle proprietà precedentemente salvi, perciò più che "leggere" le proprietà occorre "inizializzarle": anche in questo caso torna molto utile il valore di default, che è opportuno specificare sempre, se possibile.
Ora, impostando a False la proprietà Enabled del pulsante Mina(0), questo sarà disabilito come gli altri all'avvio del progetto, perché all'avvio del progetto l'istanza di progettazione viene distrutta, generando l'evento WriteProperties che salva il valore corrente della proprietà Enabled; quando viene crea l'istanza di esecuzione, l'evento ReadProperties assegna a tale proprietà il valore appena salvo.
Tornando alla sostituzione delle mine vecchie con le nuove, un'altra routine da modificare è quella dell'inizio di una nuova partita (mnuNew_Click): pricamente si trta solo di sostituire la proprietà Tag con la proprietà NumeroMine, e la parola chiave "True" con il valore -1 (anche se in realtà è la stessa cosa). Già che ci siamo approfittiamone anche per usare le costanti per il numero di righe e colonne anziché i valori letterali come 4 o 16:

Prive Sub mnuNew_Click()
'variabile temporanea per eseguire i controlli
Dim t As Integer 
'contore
Dim i As Integer
'coordine del pulsante con la mina
Dim X As Integer, Y As Integer
'coordine dei pulsanti circostanti quello con la mina
Dim x1 As Integer, y1 As Integer

Randomize Timer
PosMine(0) = Int(Rnd * mlNUMERO_RIGHE * mlNUMERO_COLONNE)
Mina(PosMine(0)).NumeroMine = -1

'Estrai:
t = Int(Rnd * mlNUMERO_RIGHE * mlNUMERO_COLONNE)
If t = PosMine(0) Then
GoTo Estrai
Else
    PosMine(1) = t
    Mina(PosMine(1)).NumeroMine = -1
End If

'EstraiDiNuovo:
t = Int(Rnd * mlNUMERO_RIGHE * mlNUMERO_COLONNE)
If t = PosMine(0) Or t = PosMine(1) Then
    GoTo EstraiDiNuovo
Else
    PosMine(2) = t
    Mina(PosMine(2)).NumeroMine = -1
End If

'aggiorna le proprietà NumeroMine dei pulsanti
For i = 0 To (mlNUMERO_RIGHE * mlNUMERO_COLONNE - 1)
    Mina(i).NumeroMine = 0
Next i
For i = 0 To 2
    Mina(PosMine(i)).NumeroMine = -1
	'riga in cui si trova la mina
    X = Int(PosMine(i) / mlNUMERO_COLONNE)
	'colonna in cui si trova la mina
    Y = (PosMine(i) Mod mlNUMERO_COLONNE)
    For x1 = IIf(X = 0, 0, X - 1) To _
    IIf(X = mlNUMERO_RIGHE - 1, mlNUMERO_RIGHE - 1, X + 1)
        For y1 = IIf(Y = 0, 0, Y - 1) To _
        IIf(Y = mlNUMERO_COLONNE - 1, mlNUMERO_COLONNE - 1, Y + 1)
            If Mina(mlNUMERO_COLONNE * x1 + y1).NumeroMine > -1 Then
                Mina(mlNUMERO_COLONNE * x1 + y1).NumeroMine =
                Mina(mlNUMERO_COLONNE * x1 + y1).NumeroMine + 1
            End If
        Next y1
    Next x1
Next i

For i = 0 To (mlNUMERO_RIGHE * mlNUMERO_COLONNE - 1)
    With Mina(i)
        .Caption = ""
        .Enabled = True
        .EnabledCmd = True
        Set .Picture = LoadPicture()
    End With
Next i

ContaMine = 0
lblTempo.Caption = ""
lblMine.Caption = "3"
ContaSecondi = 0
ContaBandiere = 0
Timer1.Enabled = True
End Sub

Un analogo trtamento va fto con le routine Mina_Click (che ora diventa Mina_ClickLeft) e Mina_MouseDown (che ora diventa Mina_ClickRight):

Prive Sub Mina_ClickLeft(Index As Integer)
'coordine del pulsante premuto
Dim X As Integer, Y As Integer
'coordine dei pulsanti circostanti quello premuto
Dim x1 As Integer, y1 As Integer

If (ContaMine < MaxContaMine) And _
	(Mina(Index).Picture.Handle = 0) Then
	'nessuna mina esplosa, almeno una mina circostante
    If Mina(Index).NumeroMine > 0 Then
        If Len(Mina(Index).Caption) = 0 Then
            ContaMine = ContaMine + 1
        End If
        Mina(Index).Caption = CStr(Mina(Index).NumeroMine)
	'mina esplosa
    ElseIf Mina(Index).NumeroMine = -1 Then
        Mina(Index).Caption = "M"
        For X = 0 To 2
            Mina(PosMine(X)).Caption = "M"
        Next X
        Timer1.Enabled = False
        ContaMine = MaxContaMine
        Exit Sub
    Else
	'mina(index).NumeroMine=0 (nessuna mina _
     esplosa, nessuna mina circostante)
		'riga in cui si trova il pulsante premuto
        X = Int(Index / mlNUMERO_COLONNE)
		'colonna in cui si trova il pulsante premuto
        Y = Index Mod mlNUMERO_COLONNE
        For x1 = IIf(X = 0, 0, X - 1) To _
        IIf(X = mlNUMERO_RIGHE - 1, mlNUMERO_RIGHE - 1, X + 1)
            For y1 = IIf(Y = 0, 0, Y - 1) To _
            IIf(Y = mlNUMERO_COLONNE - 1, mlNUMERO_COLONNE - 1, Y + 1)
                If Len(Mina(mlNUMERO_COLONNE * x1 + y1).Caption) = 0 Then
                    ContaMine = ContaMine + 1
                End If
                Mina(mlNUMERO_COLONNE * x1 + y1).Caption = 
                CStr(Mina(mlNUMERO_COLONNE * x1 + y1).NumeroMine)
            Next y1
        Next x1
    End If
    
    If ContaMine = MaxContaMine Then
        MsgBox "HAI VINTO!"
        Timer1.Enabled = False
    End If
End If

End Sub

Prive Sub Mina_ClickRight(Index As Integer)
With Mina(Index)
If .Picture.Handle = 0 Then  'nessuna bandiera sul pulsante
	Set .Picture = LoadPicture("BandieraCampoMino.ico")
    ContaBandiere = ContaBandiere + 1
    .EnabledCmd = False
Else 'il pulsante ha già la bandiera
    Set .Picture = LoadPicture()
    ContaBandiere = ContaBandiere - 1
    .EnabledCmd = True
End If
End With

lblMine.Caption = CStr(3 - ContaBandiere)

End Sub

Ora che sembra finalmente tutto a posto, sorge però un problema: lo vedremo la prossima lezione.



<< Lezione Precedente (31 Lezione) Lezione Successiva (33 Lezione) >>




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



   Invia il link di questo articolo ad un amico  Invia ad un amico Visualizza la versione stampabile di questo articolo  Stampa Commenta questo Articolo  Commenta Parlane in Chat  Chat   Parlane sul Forum  Forum