Connessione al database con Visual Basic NET? Lo trovi su Opentraining.it Visual Basic Italia
PRINCIPALE > CORSO DI VISUAL BASIC

Eseguire una ricerca veloce nell' archivio delle risorse di Visual Basic Italia®: 

Preso dall'archivio...

Premere il pulsante sotto per accedere direttamente ad un articolo o ad un esempio preso in modo casuale dall'archivio.



Ultimo e-book pubblicato:

"INTRODUZIONE AI CSS"

Lo scopo del CSS language è quello di controllare lo stile dei vari elementi che concorrono a formare un
documento HTML.
Si può operare in tre modi collegamento ad un foglio di stile esterno;definizione degli stili all'inizio
del documento utilizzo della proprietà style all'interno di un Tag HTML (embedded style). Questo e-book introduttivo
servirà per apprendere tali nozioni fondametali dei fogli di stile.

Prezzo: € 0.0.
Presentazione:
REAL SOFTWARE RILASCIA LA VERSIONE 5.0 di REALbasic per Windows






Gorizia, 5 maggio 2003 - Active, distributore in esclusiva di REALSoftware, Austin, Tx, annuncia la disponibilità di REALbasic 5.0 per Windows, uno strumento per lo sviluppo semplice da usare che permette agli utenti Windows di tutti i livelli di creare applicazioni personalizzate e di compilarle sia per la piattaforma Windows che per quella Macintosh.
[>>]

http://www.active-software.com

 

Contatti. Utilizzare l'email generica per domande relative al sito:
Porre domande relative al sito
oppure scrivere ad un responsabile di area.
Responsabile del sito: >Andrea Martelli
Responsabile area "Corso di VB":
> Giorgio Abraini

Corso di Visual Basic: lezione 30

Questa lezione, consultata da 15287 utenti, è stata giudicata di ottimi contenuti , con un'esposizione perfettamente comprensibile e con un livello di approfondimento ottimo da 38 votanti.


Lezione 30

Per mettere in pratica qualcuna delle nozioni viste nella lezione precedente torniamo a un vecchio progetto: il campo minato.
Tempo fa mi è stato fatto notare un errore (di cui probabilmente si saranno accorti in molti, ma che solo un lettore si è degnato di segnalarmi): l'errore consiste nel fatto che quando si scopre una mina in effetti la partita non termina, ma può essere proseguita dal giocatore, benché il programma visualizzi la posizione delle altre mine e interrompa il timer. In realtà di errori ce ne sono anche altri: ad es. la partita non si interrompe neppure quando il giocatore vince, e per di più il timer continua a scorrere.
Per correggere questi bug occorre intervenire sull'evento Click dei pulsanti "mina", che controlla appunto se una mina sta per esplodere oppure no; questa è la routine originaria:

Private Sub Mina_Click(Index As Integer)
Dim x As Integer, y As Integer 'coordinate del pulsante premuto
Dim x1 As Integer, y1 As Integer 'coordinate dei pulsanti circostanti quello 'premuto

If Mina(Index).Tag > 0 Then 'nessuna mina esplosa, almeno una mina 'circostante
If Len(Mina(Index).Caption) = 0 Then
ContaMine = ContaMine + 1
End If
Mina(Index).Caption = Mina(Index).Tag
ElseIf Mina(Index).Tag = -1 Then 'mina esplosa
Mina(Index).Caption = "M"
For x = 0 To 2
Mina(PosMine(x)).Caption = "M"
Next x
Timer1.Enabled = False
Else 'mina(index).tag=0 (nessuna mina esplosa, nessuna mina circostante)
x = Int(Index / 4) 'riga in cui si trova il pulsante premuto
y = Index Mod 4 'colonna in cui si trova il pulsante premuto
For x1 = IIf(x = 0, 0, x - 1) To IIf(x = 3, 3, x + 1)
For y1 = IIf(y = 0, 0, y - 1) To IIf(y = 3, 3, y + 1)
If Len(Mina(4 * x1 + y1).Caption) = 0 Then
ContaMine = ContaMine + 1
End If
Mina(4 * x1 + y1).Caption = Mina(4 * x1 + y1).Tag
Next y1
Next x1
End If

If ContaMine = MaxContaMine Then
MsgBox "HAI VINTO!"
End If
End Sub

Per fare in modo che il giocatore non possa ulteriormente continuare a premere i pulsanti dopo aver vinto o perso, occorre in qualche modo "inibire" l'evento click, in modo che le precedenti istruzioni non siano eseguite: il modo più semplice per fare ciò è adottare un flag del tipo bEseguiClick che valga true se la partita è ancora in corso o false se la partita è terminata. In realtà una variabile del genere l'abbiamo già utilizzata, anche se non è un booleano: si tratta della variabile ContaMine, che quando raggiunge il massimo segnala la vittoria del giocatore.
Nessuno ci impedisce di assegnarle il medesimo valore anche quando il giocatore perde, e condizionare l'esecuzione della routine al fatto che tale variabile sia minore o uguale al valore massimo raggiungibile:

Private Sub Mina_Click(Index As Integer)
Dim x As Integer, y As Integer 'coordinate del pulsante premuto
Dim x1 As Integer, y1 As Integer 'coordinate dei pulsanti circostanti quello 'premuto

If ContaMine < MaxContaMine Then
If Mina(Index).Tag > 0 Then 'nessuna mina esplosa, almeno una mina 'circostante
If Len(Mina(Index).Caption) = 0 Then
ContaMine = ContaMine + 1
End If
Mina(Index).Caption = Mina(Index).Tag
ElseIf Mina(Index).Tag = -1 Then 'mina esplosa
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).tag=0 '(nessuna mina esplosa, nessuna mina circostante)
x = Int(Index / 4) 'riga in cui si trova il pulsante premuto
y = Index Mod 4 'colonna in cui si trova il pulsante premuto
For x1 = IIf(x = 0, 0, x - 1) To IIf(x = 3, 3, x + 1)
For y1 = IIf(y = 0, 0, y - 1) To IIf(y = 3, 3, y + 1)
If Len(Mina(4 * x1 + y1).Caption) = 0 Then
ContaMine = ContaMine + 1
End If
Mina(4 * x1 + y1).Caption = Mina(4 * x1 + y1).Tag
Next y1
Next x1
End If

If ContaMine = MaxContaMine Then
MsgBox "HAI VINTO!"
Timer1.Enabled = False
End If
End If

End Sub


Quando il giocatore scopre una mina, oltre ad arrestare il timer il programma provvede ad assegnare:
ContaMine = MaxContaMine
dopodiché termina forzatamente la routine per evitare la comparsa del messaggio "hai vinto!" (anche se il giocatore ha perso…); una volta che ContaMine ha il suo massimo valore concesso, qualunque ulteriore pressione delle mine non sortisce effetti, perché la condizione iniziale risulta falsa e la routine termina subito. In realtà il vero campo minato si comporta in modo leggermente diverso, impedendo la pressione stessa dei pulsanti: ciò può essere facilmente ottenuto disabilitando tutti i pulsanti "mina", ovvero impostando la proprietà Enabled su False per tutti gli elementi della matrice di CommandButton "Mina".
Se invece il contatore ContaMine ha un valore minore di MaxContaMine, significa che la partita è ancora in corso, pertanto la routine esegue tutti i controlli del caso ed eventualmente mostra il messaggio di vittoria; anche in tal caso il timer viene interrotto. Ciò non è ancora sufficiente, perché se una nuova partita viene cominciata subito dopo, l'etichetta lblTempo riprende da dove si era interrotta anziché ricominciare daccapo; il problema si risolve semplicemente inserendo l'istruzione:

lblTempo.Caption = ""

alla fine della routine mnuNew_Click, prima dell'abilitazione del timer, e quest'altra istruzione all'inizio dell'evento Timer1_Timer:

If ContaMine = 0 Then i = 0

Poiché la variabile ContaMine viene inizializzata a zero ad ogni nuova partita, il timer controllando il valore della variabile riesce a capire se deve continuare a contare da dove era arrivato (ricordiamo che la variabile "i" usata dal timer è di tipo static) o se deve ricominciare da zero. L'unico inconveniente è che finché il giocatore non comincia effettivamente a giocare (cioè non prova a premere qualche "mina") la variabile ContaMine resterà sempre a zero, e quindi il timer continuerà a inizializzare la variabile i e l'etichetta lblTempo continuerà a mostrare il valore "1" anche se la partita è cominciata da un bel pezzo. A questo punto si potrebbero escogitare vari trucchi, ma la soluzione più elegante è usare una variabile a livello di modulo anziché una variabile statica ma locale rispetto all'evento timer di Timer1:

Dim ContaSecondi As Long 'numero di secondi trascorsi dall'inizio della 'partita

E modificare di conseguenza la routine del timer:


Private Sub Timer1_Timer()
ContaSecondi = ContaSecondi + 1
lblTempo.Caption = CStr(ContaSecondi)
End Sub

Ovviamente la variabile ContaSecondi dovrà essere inizializzata a zero tutte le volte che comincia una nuova partita. Questo per quanto riguarda la correzione dei bug.
Già che ci siamo vediamo di migliorare il gioco implementando la visualizzazione di una bandierina sui pulsanti sui quali il giocatore clicca col tasto destro del mouse. Per fare ciò occorre innanzitutto impostare a 1 la proprietà Style dei pulsanti "mina": corrisponde allo stile grafico, che consente appunto di visualizzare icone sui pulsanti; la modalità di default è quella standard (Style=0), senza icone.
Purtroppo la proprietà è di sola lettura in fase di esecuzione, per cui è necessario modificare manualmente la proprietà di tutte le mine in ambiente di progettazione: si può fare in un colpo solo selezionando tutte le mine insieme (come si fa con un gruppo di file in gestione risorse) e cambiando il valore della proprietà.
A questo punto dobbiamo intercettare l'evento "click col tasto destro" per mostrare o nascondere l'icona della bandiera. L'evento "click", come sapete, è associato per default al tasto sinistro del mouse, e l'oggetto CommandButton non supporta un evento "RightClick" generato dalla pressione del tasto destro; esso supporta però l'evento "MouseDown", che viene generato ogniqualvolta l'utente preme un pulsante del mouse (non importa quale) sull'oggetto: è questo evento che dobbiamo intercettare. Questa è la dichiarazione dell'evento:

Private Sub Mina_MouseDown(Index As Integer, Button As Integer, Shift As Integer, X As Single, Y As Single)

End Sub

Il primo parametro, come per l'evento Click, indica su quale "mina" è stato premuto un tasto del mouse; il secondo invece specifica quale dei tasti è stato premuto: perciò è su questo parametro che dovremo fare gli opportuni controlli; il terzo parametro specifica quali dei tasti control, alt o shift sono premuti mentre viene generato l'evento: è molto utile per riconoscere particolari sequenze (ad es. ctrl+shift+tasto destro); gli ultimi due parametri indicano le coordinate del puntatore del mouse nel momento in cui viene generato l'evento, e per ora non ci interessano.
Come si è detto, il nostro compito è individuare la pressione del tasto destro del mouse per visualizzare la bandiera sulla mina corrispondente; la chiave è in una semplice If:

With Mina(Index)
If Button = vbRightButton Then 'click col tasto destro
If .Picture.Handle = 0 Then 'nessuna bandiera sul pulsante
.Picture = LoadPicture("BandieraCampoMinato.ico")
Else 'il pulsante ha già la bandiera
.Picture = LoadPicture()
End If
End If
End With

Se il tasto del mouse premuto è il destro, il parametro Button avrà il valore 2, che è il valore della costante vbRightButton (fa parte del gruppo MouseButtonConstants, insieme a vbLeftButton e vbMiddleButton): in tal caso, se il pulsante non ha alcuna bandiera essa viene visualizzata attraverso la proprietà Picture, altrimenti significa che il giocatore ha premuto il tasto destro su una mina che aveva già la bandiera: perciò essa sarà tolta.
L'impostazione della proprietà Picture avviene tramite la funzione LoadPicture: dei vari parametri che accetta, qui è stato usato solo il primo, ovvero il nome del file.
Come potete appurare controllando sul visualizzatore oggetti, la funzione restituisce un riferimento a un'istanza della classe IPictureDisp, che dispone di una proprietà Handle: tale proprietà assume valore zero se non è associata ad alcuna immagine, altrimenti assume il valore dell'handle dell'icona; l'handle è un numero a 32 bit che fa riferimento univocamente all'immagine caricata in memoria dalla funzione LoadPicture.
Per eliminare un'immagine associata al pulsante, basta usare ancora la funzione LoadPicture senza alcun parametro: infatti il nome del file immagine è facoltativo e, se assente, indica di rimuovere l'immagine correntemente associata al pulsante.
Intercettato il click col tasto destro, occorre poi fare in modo che facendo click col tasto sinistro non accada nulla se sulla mina premuta è posta una bandiera: si tratta di fare una piccola estensione alla condizione che abbiamo posto all'inizio dell'evento Mina_Click:

If (ContaMine < MaxContaMine) And (Mina(Index).Picture.Handle = 0) Then

L'istruzione If controlla non solo che il gioco sia ancora in corso confrontando il valore di ContaMine con MaxContaMine, ma anche che la mina che il giocatore ha premuto non sia una di quelle "bloccate" con la bandiera, altrimenti l'evento click viene sostanzialmente ignorato. A differenza del vero campo minato, tuttavia, i pulsanti appaiono effettivamente "premuti" quando si fa click, anche se non succede nulla: non è come se i pulsanti "Mina" fossero disabilitati.
In effetti si potrebbe impostare Enabled = False quando il giocatore fa click col tasto destro del mouse, approfittando anche del fatto che ai pulsanti disabilitati è possibile associare un'icona tramite la proprietà DisabledPicture: ogniqualvolta un pulsante è disabilitato Visual Basic provvede a mostrare l'immagine associata alla proprietà, senza bisogno di usare la funzione LoadPicture. Il problema è che poi una volta disabilitato il pulsante non è più possibile intercettare l'evento mousedown per eventualmente reimpostare Enabled = True; non è un problema insormontabile, ma sembra più conveniente la strada illustrata sopra.
È opportuno invece disabilitare le mine all'avvio dell'applicazione, abilitandole solo quando il giocatore sceglie di iniziare una nuova partita:

Private Sub Form_Load()
Dim lContatore As Long

MaxContaMine = 13
For lContatore = Mina.LBound To Mina.UBound
Mina(lContatore).Enabled = False
Next lContatore

End Sub

Mina.LBound e Mina.UBound equivalgono rispettivamente a Lbound(Mina) e Ubound(Mina), ovvero 0 e 15. Per lo stesso motivo, come si è detto più sopra, risulta conveniente disabilitare i pulsanti quando il giocatore termina (vincendo o perdendo) la partita, anziché utilizzare la variabile ContaMine per inibire il click sulle mine: dipende anche dai gusti del programmatore scegliere una delle tante strade disponibili.
Resta ancora una cosa da fare: eliminare tutte le bandiere eventualmente esistenti quando si comincia una nuova partita; l'ultima parte della routine mnuNew_Click diventerà quindi:

For i = 0 To 15
With Mina(i)
.Caption = ""
.Enabled = True
.Picture = LoadPicture()
End With
Next
i
ContaMine = 0
lblTempo.Caption = ""
ContaSecondi = 0
Timer1.Enabled = True

Infine, una caratteristica prevista ma non ancora implementata riguardava l'etichetta lblMine, che nel campo minato dovrebbe indicare quante mine restano ancora da scoprire: ora che abbiamo capito come intercettare il click col tasto destro possiamo anche aggiornare questa etichetta. Si tratta semplicemente di sottrarre, dal numero complessivo di mine (3 nel nostro esempio), il numero di pulsanti su cui sventola la bandiera; il luogo più adatto in cui effettuare questo calcolo è l'evento mousedown, dato che è lì che si decide se issare o ammainare la bandiera. Intanto dichiariamo un nuovo contatore per le bandiere a livello di modulo nel form:

Dim ContaBandiere As Long 'numero di bandiere issate

Poi incrementiamo o decrementiamo il contatore ogni volta che una bandiera viene aggiunta o tolta, e aggiorniamo l'etichetta:

Private Sub Mina_MouseDown(Index As Integer, Button As Integer, Shift As Integer, X As Single, Y As Single)
With Mina(Index)
If Button = vbRightButton Then 'click col tasto destro
If .Picture.Handle = 0 Then 'nessuna bandiera sul pulsante
.Picture = LoadPicture("c:\giorgio\word\corsovb\BandieraCampoMinato.ico")
ContaBandiere = ContaBandiere + 1
Else 'il pulsante ha già la bandiera
.Picture = LoadPicture()
ContaBandiere = ContaBandiere - 1
End If
End If
End With

lblMine.Caption = CStr(3 - ContaBandiere)

End Sub

Infine, inizializziamo il contatore ad ogni nuova partita:

Private Sub mnuNew_Click()
'...
ContaMine = 0
lblTempo.Caption = ""
lblMine.Caption = "3"
ContaSecondi = 0
ContaBandiere = 0
Timer1.Enabled = True
End Sub

In teoria dovremmo usare una costante per indicare il numero di mine, ma per ora non formalizziamoci troppo. Ci sarebbe anche un piccolo bug: se il giocatore mette più bandiere di quante sono le mine, l'etichetta mostra naturalmente un numero negativo; cosa che peraltro accade anche col vero campo minato. Se il programmatore lo ritiene opportuno, può imporre dei controlli per evitare che ciò accada, ma a me sembra tutto sommato più conveniente lasciarlo così: l'etichetta ha solo una funzione indicatrice, e se il giocatore ha voglia di mettere bandiere in eccesso, che lo faccia pure.
Ora il campo minato sembra funzionare a dovere: ma molte cose possono essere ancora migliorate, come vedremo. Buon divertimento.