Controllo tasti nei cicli

venerdì 16 luglio 2004 - 15.04

hydra Profilo | Junior Member

Salve a tutti, ho una nuova domanda:

ho necessità di controllare la pressione dei tasti all'interno di un ciclo for, ad esempio premendo il tasto canc voglio uscire oppure premendo un altro tasto vogio settare dei parametri. Ho messo il codice dei tasti nell'evento della finestra, ma come immaginavo all'interno del ciclo non riesco a passare il controllo in modo da poter ugualmente premere i tasti. Qualcuno di voi ha idea di come si possa fare? Spero di essere stato abbastanza chiaro nelle spiegazioni, al max chiedete pure. Attendo le vostre illuminanti proposte.

Brainkiller Profilo | Guru

Non ho ben capito che cosa succede.
Forse hai fatto un ciclo For intensivo e che richiede molto tempo e in quel momento non riesci a premere i tasti perchè la finestra non risponde ?

Se sì, riesci a risolvere creando un thread apposito e inserendo lì il codice del ciclo for.

ciao
david

pecos81 Profilo | Junior Member

Ciao,
secondo me devi usare 2 thread: uno che gestisce il tuo ciclo for ed un altro che attende la pressione dei tasti. Qualcosa del genere:

Dim MyThread As New System.Threading.Thread(AddressOf longProcess)

Sub longProcess()
Dim i As Integer
For i = 0 To 30000
Label1.Text = i
Next
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Try
MyThread.Start()
Catch exc As Exception
Label2.Text = exc.Message
End Try
End Sub

ed in un altro processo gestisci la pressione dei tasti.
Spero di esserti stato d'aiuto. Ciao

alextyx Profilo | Expert

Stamani sono ancora poco sveglio ed ho già fatto la mia pessima figura mattutina su un altro thread, scambiando un Report x un Form. Spero che i santi numi che vegliano sui programmatori, volgano il loro benevolo sguardo su di me e mi evitino di bissare la figuraccia.
Il problema, SE (maiuscolo) ho capito, è un classico problema della serie: Se sono in un loop intensivo e clicco su un tasto o ne premo uno che vorei intercettare, come faccio a far 'sì che l'evento click, o KeyDown, vengano serviti?

Devi inserire nel tuo ciclo, ad ogni giro oppure ogni 'tot' giri, una riga di codice siffatta:

Application.DoEvents()

Questa determinerà l'esecuzione di tutti gli eventi occorsi e attualmente in attesa, compresi i click e le pressioni dei tasti, che altrimenti verranno serviti tutti alla fine del ciclo, quando ormai nn ti servono più a niente. Prova a metterla ad ogni giro del loop e controlla se ti funziona!

Sicuramente nn avrò capito bene il problema, perchè sento parlare di multithreading che nn dovrebbe essere scomodato x problemi così banali, quindi mi scuso in anticipo x eventuali travisamenti e conto di correggermi ....... appena avrò capito cosa mi sta sfuggendo :-)

hydra Profilo | Junior Member

Mi spiego bene, io ho necessità di controllare la pressione dei tasti oppure dei pulsanti a video quando all'interno di un ciclo sto facendo altro (per esempio premo il tasto canc per interrompere il ciclo). Grazie a tutti dei vostri consigli, e devo dire una cosa a alextyx: ci hai preso in pieno, la cosa che mi interessava era proprioquella, quindi penso che le tue preghiere siano state ascoltate. Con questo non voglio dire che gli altri hanno "cannato", anzi, probabilmente quelle soluzioni erano altrettanto valide, e forse io non sono stato molto chiaro, solo che volevo dare un po di fiducia a alextyx che mi sembrava un po demoralizzato. Rinnovo cmq i miei ringraziamenti a tutivoi per il grande supporto che date :)))

alextyx Profilo | Expert

Grazie Hydra, due insuccessi di fila mi avrebbero seccato, in effetti! Ciao e buon lavoro a tutti!

hydra Profilo | Junior Member

Salve, ho un nuovo problema relativo al caso. Per farla breve vi faccio un esempio che non è la mia realtà (che credetemi, è complicata da spiegare) ma ci assomiglia. Allora, ho un form con due label e un evento timer. Nel timer vado ad assegnare a ciascuno dei label il valore di due variabili. Ora, in una classe ho una funzione con due cicli do..loop, nel primo incremento l'una e nel secondo l'altra variabile. L'effetto che voglio io è quello di vedere il valore delle variabili che si incrementa, ma tale valore viene visualizzato solo alla fine di ogni ciclo. Allora ho pensato di mettere in ciascuno dei due cicli un Application.DoEvents, ma il risultato è questo: quando entro nel cicloeseguo la prima istruzione e quando incontro il DoEvents, passo il controllo alla finestra che però non mi torna indietro nella funzione. In definitiva, io voglio che nel ciclo mi vengano incrementate ste variabili e nel form mi venga visualizzato il valore, tenendo conto che la parte di visualizzazione la devo mettere nel timer della finestra. Qualcuno di voi ha idea di come fare? Se avete bisogno di maggiori dettagli sono qua, ma spero che il problema sia chiaro, anche perchè a spiegarlo in dettaglio è un po complicato. RiGrazie a tutti. :)

alextyx Profilo | Expert

Caro Hydra, in effetti stavolta sono ancora meno sicuro di aver capito. Se il problema è solo quello che le Label vengono aggiornate alla fine del loop e non ad ogni ciclo, credo che tu debba inserire, ad ogni giro, o ogni 'tot' di giri (dove ho già sentito queste parole?) un qualcosa tipo

LblConteggio.Refresh()

Se nn ho capito, resto in attesa di una tirata d'orecchi e di nuovi input! :-)

hydra Profilo | Junior Member

Hai capito bene. Il fatto è questo, se io richiamo questa funzione dal form tutto ok. Io però ho necessità di richiamare questa funzione anche all'interno di altre funzioni, e in questo caso il mio form non sarà attivo.Quindi se metto il controllo che mi hai detto mi causerà un'eccezione, a meno che non vada a controllare se la finesra è aperta, in quel caso aggiorna...... ma diventa lungo per la semplicità della funzione che ho fatto.
Volevo sapere se c'è una soluzione semplice, e in particolare voglio capire questo: ma la funzione DoEvents esattamente cosa fa?

alextyx Profilo | Expert

Ti giro la dotta, anche se vagamente criptica, spiegazione della guida:

Elabora tutti i messaggi di Windows attualmente presenti nella coda di messaggi.

[Visual Basic]
Public Shared Sub DoEvents()

[C#]
public static void DoEvents();

[C++]
public: static void DoEvents();

[JScript]
public static function DoEvents();

Osservazioni
Quando un Windows Form viene eseguito, crea il nuovo form, che resterà in attesa di eventi da gestire. Ogni volta che il form gestisce un evento, elabora tutto il codice associato a quell'evento. Tutti gli altri eventi attendono in coda. Mentre il codice gestisce l'evento, l'applicazione non risponde. La finestra non viene ad esempio ridisegnata se viene trascinata in primo piano un'altra finestra.

Se all'interno del codice viene chiamato il metodo DoEvents, l'applicazione sarà in grado di gestire gli altri eventi. Nel caso ad esempio di un form che aggiunge dati a una ListBox, se al codice si aggiunge il metodo DoEvents, il form si ridisegnerà quando su di esso verrà trascinata un'altra finestra. Se si rimuove il metodo DoEvents dal codice, il form non si ridisegnerà finché il gestore di eventi Click del pulsante non avrà ultimato l'esecuzione.

In genere questo metodo è utilizzato all'interno di un ciclo per elaborare i messaggi.

Attenzione La chiamata a questo metodo può comportare una nuova immissione del codice qualora un messaggio generi un evento.


Tuutavia il tuo problema mi sembra riconducibile ad una chiamata con parametro della tua funzione. Se la richiami quando ci sono le condizioni per eseguire certe operazioni, gli passi, ad esempio, un certo parametro, magari anche opzionale, uguale a True. Altrimenti richiamerai la funzione 'secca' che al suo interno testerà il parametro e trovandolo False (di defult potrai settarlo come false), nn eseguirà le istruzioni proibite! E così abbiamo lavorato anche in 'odore' di overload! :-)
Ma hai un problema quando la funzione si rivolge alle textbox (o label, nn ricordo), se ho ben capito?
Se posti la funzione e chiarisci l'errore, la mettiamo a posto al volo! Ciao

hydra Profilo | Junior Member

Ok, ti posto il codice del pulsante e della funzione:

Codice del pulsante:
Private Sub Button16_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button16.Click
If Not IForm1.FC.MoveToSvPoint(1) Then MsgBox("Fallito")
End Sub

Codice della funzione:
Public Function MoveToSvPoint(ByVal Point as Integer) As Boolean
Dim ExitC As Boolean = False

' Questa parte è di prova, poi il dataset sarà caricato dall'esterno
IForm1.DstLav = New DataSet
IForm1.DstLav.ReadXml(IForm1.PgmPath + IForm1.DataPath + "mod2.xml")

' Movimento
IForm1.GF.Move(IForm1.SM.Z, Point)
Do
' Controllo
If IForm1.FS.CheckEmergency <> 0 And IForm1.FA.CheckFC <> 0 Then
Return (False)

End If
' Se em o err allora ExitC = true
' Exit Do
'Application.DoEvents()
Loop Until IForm1.GF.GetMove(IForm1.SM.Z) = 0

' Movimento
IForm1.GF.Move(IForm1.SM.X, Point)
Do
'Application.DoEvents()
Loop Until IForm1.GF.GetMove(IForm1.SM.X) = 0

' Movimento
IForm1.GF.Move(IForm1.SM.Y, Point)
Do
'Application.DoEvents()
Loop Until IForm1.GF.GetMove(IForm1.SM.Y) = 0

' Movimento
IForm1.GF.Move(IForm1.SM.ZT, Point)
' Movimento
IForm1.GF.Move(IForm1.SM.R, Point)
Do
'Application.DoEvents()
Loop Until IForm1.GF.GetMove(IForm1.SM.T) = 0 And IForm1.GF.GetMove(IForm1.SM.R) = 0
Return (True)

End Function


Le funzioni Move() e GetMove() sono altre due funzioni indipendenti provenienti da una Dll (e queste funzionano perchè già utilizzate con successo in altre parti di programma).

Se io lascio il DoEvent mi succede che, dopo averlo incontrato la prima volta, la mia funzione termina e il controllo passa alla finestra chiamante. Se poi io in questa finestra premo qualsiasi pulsante, io vedo che si preme il mio Button16 (che è un button di prova). Se io invece tolgo il DoEvent, la visualizzazione sulla finestra avviene quando la mia funzione è terminata. Cosa sta succedendo? Dove sto sbagliando? Grazie a tutti per il vostro intervento. :)

alextyx Profilo | Expert

Allora, per quanto riguarda il problema che mi avevi prospettato, ovvero l'evitare di mettere controlli sull'apertura della tua finestra, come già ti avevo anticipato, la soluzione piuttosto semplice può essere questa (tieni presente che scrivo qui e quindi potrebbero esserci errori di sintassi, quello che voglio passare è il concetto).
Nota che ho aggiunto un parametro che potresti anche mettere opzionale.
Quando la usi, nn ho capito bene come, ma comunque svincolata dal Form, la chiami passandoglielo False, altrimenti True

Public Function MoveToSvPoint(ByVal Point as Integer, ByVal EsguiDoEvente As Boolean) As Boolean
Dim ExitC As Boolean = False

' Questa parte è di prova, poi il dataset sarà caricato dall'esterno
IForm1.DstLav = New DataSet
IForm1.DstLav.ReadXml(IForm1.PgmPath + IForm1.DataPath + "mod2.xml")

' Movimento
IForm1.GF.Move(IForm1.SM.Z, Point)
Do
' Controllo
If IForm1.FS.CheckEmergency <> 0 And IForm1.FA.CheckFC <> 0 Then
Return (False)

End If
' Se em o err allora ExitC = true
' Exit Do

If eseguidoevent then Application.DoEvents()

Loop Until IForm1.GF.GetMove(IForm1.SM.Z) = 0

' Movimento
IForm1.GF.Move(IForm1.SM.X, Point)
Do

If eseguidoevent then Application.DoEvents()

Loop Until IForm1.GF.GetMove(IForm1.SM.X) = 0

' Movimento
IForm1.GF.Move(IForm1.SM.Y, Point)
Do

If eseguidoevent then Application.DoEvents()

Loop Until IForm1.GF.GetMove(IForm1.SM.Y) = 0

' Movimento
IForm1.GF.Move(IForm1.SM.ZT, Point)
' Movimento
IForm1.GF.Move(IForm1.SM.R, Point)
Do

If eseguidoevent then Application.DoEvents()

Loop Until IForm1.GF.GetMove(IForm1.SM.T) = 0 And IForm1.GF.GetMove(IForm1.SM.R) = 0
Return (True)

End Function

Poi....di questo utlimo suggerimento sono meno sicuro. Bisognerebbe poterlo provare. Comunque farei una cosetta per impedire che il codice possa essere ricorsivo e annodarsi dentro se stesso

Private Sub Button16_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button16.Click

if not sender.enable then exit sub 'Forse questo controllo è ridondante e comunque va provato. Serve a far 'sì che se si arriva qui col pulsante disabilitato (da codice si può arrivarci), si esca comunque!

sender.enable=false 'Disabilita il pulsante a ricevere interazioni dall'utente prima dell'inizio delle operazioni

If Not IForm1.FC.MoveToSvPoint(1) Then MsgBox("Fallito")

sender.enable=true 'Riabilita il pulsante a ricevere interazioni dall'utente

End Sub

Sperem!!! Ovviamente con tutti i se e i ma di codice nn provato. Magari ci sono castronerie pazzesche!

hydra Profilo | Junior Member

Ho provato a fare come consigli tu, ossia con il boolean di controllo. Poi col debug mi sono accorto di questo: quando arrivo al DoEvents torno alla mia form e rimango in attesa, cioè la mia funzione non continua. Cioè è come se la mia funzione rimanesse in attesa di chissà cosa. E non riesco a capire perchè. Voglio dire, prima di iniziare a programmare in VBNet usavo il PowerBuilder, che è nettamente di generazione precedente. Ma c'era una funzione che mi permetteva di "interrompere" momentaneamente quello che stavo facendo nel ciclo per consentire al programma di processare quello cheio facevo nel mentre, tipo premere Canc per fermare il ciclo. Il DoEvents dovrebbe fare la stessa identica funzione, ma a questo punto o non è proprio così, o sono io che sbagio chissà dove. Cosa dici, è meglio che mi metta a coltivare patate?
Ci sono sicuramente soluzioni alternative, tipo posso mettere il codice che nella finestra mi fa il rinfresco delle quote e, con il boolean di controllo, richiamare questa funzione quando è a true. Solo che se lo fa il Fk in auomatico è meglio. Vabbè, in attesa di illuminanti proposte vado a mangiare. Grazie a tutti comunque. :))

alextyx Profilo | Expert

Calma. calma, per i tuberi c'è tempo! :-)
Anche se nn è facile un debug a distanza, almeno cerchiamo di mantenerci allineati. Dunque, mi stai dicendo che hai messo il boolean, ma lo usi? Voglio dire, ammesso ch'io nn abbia sbagliato l'istruzione il Do.Events nn dovrebbe essere usato col boolean = false (controlla ch'io nn abbia invertito la logica, è possibile...sai...l'età! ;-) ) , nell'ipotesi che essa nn venga eseguita (se così nn fosse, correggi il mio test, o è invertito come logica o manca qualcosa, ma DEVE essere in grado di impedirlo), allora nn può neppure fare danni. O sbaglio?
Casomai prova con qualcosa di simile:

If EseguiDoevent=true then
application.Doevents()
End if

e controlla che l'istruzione venga saltata!

Partiamo dal verificare questo punto. nn è detto che si riesca a risolvere il problema, nn a distanza, ma di certo avremo dei vantaggi a procedere con delle certezze, anche piccole!
Almeno disinnescare l'Application.Doevents quando nn serve, dovrà pur riuscirci, nn ti pare?
Mi resta il dubbio di aver capito male o nn completamente le tue esigenze, ma speriamo che la cosa si chiarisca nel prosieguo! Ciao.

hydra Profilo | Junior Member

Allora, indipendentemente dalla logica riesco a saltare il DoEvents quando voglio saltarlo. Forse questo dettaglio aggiungerà carne al fuoco: quando io abilito il DoEvents mi succede come ho detto, il ciclo si blocca non si sa perchè. Inoltre non riesco a chiudere la finestra in nessun modo (e questo mi è sempre successo con il doevents). Dico che la finestra principale della mia applicazione è una MDI, ma la finestra incriminata non è una MDIChild, cioè (non voglio sbagliare terminologia...), quando istanzio la finestra non gli ho messo .MdiParent = me e la chiamo con una showdialog. otrebbe dipendere da questo?

alextyx Profilo | Expert

Aspetta....mi sembra che fossimo, alcuni posts fa, giunti al punto in cui, per risolvere il problema c'era l'alternativa di far eseguire un certo controllo, ritenuto oneroso e complicato, oppure di veder sorgere 'il mostro'. :-) La chiamata col boolean, aveva lo scopo di evitare il fastidioso controllo, in quanto si supponeva di sapere, a seconda di dove si trovava la chiamata alla funzione, se si era nel caso in cui tutto filava liscio, oppure no. Forse mi sono perso qualcosa?

Vediamo se riusciamo a capirci. Tu scrivevi:

Hai capito bene. Il fatto è questo, se io richiamo questa funzione dal form tutto ok. Io però ho necessità di richiamare questa funzione anche all'interno di altre funzioni, e in questo caso il mio form non sarà attivo.Quindi se metto il controllo che mi hai detto mi causerà un'eccezione, a meno che non vada a controllare se la finesra è aperta, in quel caso aggiorna...... ma diventa lungo per la semplicità della funzione che ho fatto.
Volevo sapere se c'è una soluzione semplice, e in particolare voglio capire questo: ma la funzione DoEvents esattamente cosa fa?


Il dubbio è se ti riferivi al Refresh oppure al Doevents?

Comunque sia, mi pare di capire che la funzione richiamata dall'interno del form possa funzionare, o no? Se sì, nn basta saltare, col boolean, le parti inutili e dannose quando non la si chiama da lì (siano esse il doevents o il refresh, o entrambi)? O stavi parlando di cose diverse???

Altra cosa...dove è 'sta funzione (quella postata)? In un modulo, o in un form?
Se è in modulo, penso nn sia quella col problema del refresh, perchè nn ti ho visto passare le label come parametri, quindi nn potresti aggiornarle se nn chiamando il form in maniera rigida. Comunque tale codice dovrebbe essere nascosto in qualcuna delle procedure che richiami, perchè nn mi pare di vederlo.

Tanto per farti venire il mal di testa (e poi magari nn caveremo un ragno dal buco!) mi spieghi meglio come girano le form, dove è il pulsante (button16 mi pare) che chiama la funzione e dove si trova la funzione ( ma questo te l'ho già chiesto!)

In ultimis, per adesso in maniera più o meno a caso, dissemina il tuo codice di breakpoint e attivali un attimo prima di chianmare la funzione. Vediamo se si riesce a capire che giro fa, magari leggando lo stack delle chiamate.

Buon lavoro! :-)))

hydra Profilo | Junior Member

Iniziamo:

La mia form è una form di gestione. La chiamo con lo showdialog perchè quando è aperta deve rimanere sempre attiva (data la delicatezza delle operazioni che porta). Io chiamo questa funzione con un button perchè voglio avere la possibilita di controllare quello che succede. Questa funzione la devo utilizzare, diciamo così, in automatico, nel senso che ho delle operazioni a sè stanti che mi vanno a richiamare questa funzione, dal cui esito dipende il proseguo. La funzione incriminata è implementata in una classe. dunque io poss fare così:

La parte di codice che mi rinfresca i label (evento timer della form) la metto in una funzione, referenziando i label direttamente;
Nell'evento timer chiamo questa funzione di rinfresco e lo chiamo anche nella funzione incriminata, solo che in quest'ultima la chiamata è condizionata dal fatto che io setti il boolean vero o falso (verrà settato vero se la form è aperta). In questo caso sono sicuro al 99% che funzioni.

Io vi ho rotto le scatole con sta storia perchè ho pensato questo: se io sono in un ciclo sono la che aspetto, quindi sicuramente non aggiorno niente. Se io metto il DoEvents dovrei riuscire ad essere un po' più elastico e di conseguenza riuscire a rinfrescare i label, avendo così "l' effetto movimento".
Ora il problema è che con il DoEvents si verificano una serie di errori che non riesco a gestire semplicemente perchè non capisco il motivo dell'errore (il debug non si ferma, non ricevo messaggi ne niente, solamente dopo il primo ciclo sono andato a campi.....)

Ora, senza che vi sbattiate a fare il lavoro che dovrei fare io, potri optare per la soluzione di cui sopra. Mi rimane solo il problema di capire perchè si verifica sto fatto (che potrei in ogni caso archiviare come uno dei tanti "misteri Microsoft").

Quindi, concludendo, ringrazio tutti quelli che sono intervenuti, specialmente alextyx che mi è stato particolarmente di supporto. Rimango comunque in attesa di un'eventuale chiarimento sull'argomento (se c'è, io sono fiducioso, magari la soluzione la trovo proprio io.... :))

alextyx Profilo | Expert

Parliamo di Timer. Per quel che ne so un timer piazzato sulla form1 (tanto x essere originali!), funziona anche se c'è una form2 visualizzata modale, ovvero se l'esecuzione del codice della form1 è ferma sulla riga dello Form2.showdialog. Il Tiner, tuttavia può nn scattare se la macchina è in un ciclo intensivo. In quel caso cosa ci vuole? Ma il doevents, ovvio !!! Questo tanto per farti dormire preoccupato! :-))) Il Doevents sarà la tua nemesi!
Comunque, mi rendo conto che nn riesco a visualizzare la tua applicazione. Adesso è venuto fuori un evento timer di cui nn avevo ancora preso coscenza e ancora nn so se la tua funzione è dichiarata public in un modulo, oppure in un form e quale esso sia. In effetti probabilmente ti sarebbe più oneroso descrivermi con la necessaria pignoleria la tua situazione che nn cercarti il bug da solo! Quello che posso consigliarti di fare è di seguire il codice a passi e comunque di disseminare il codice di breakpoint. Tieni presente che il doevents, può lanciare un evento che si sutorilancia e così via. Potresti essere finito in uno di questi loop. L' uso di variabili di tipo static potrebbe, se così fosse, levarti le castagne dal fuoco!
Mi raccomando, se scopri il problema, facci sapere dove era. I bug risolti sono un'ottima scuola x tutti. Buon lavoro. :-)

hydra Profilo | Junior Member

Ok, se riesco a risolvere il problema posto un messaggio con la spiegazione. Grazie intanto per la pazienza :)
Partecipa anche tu! Registrati!
Hai bisogno di aiuto ?
Perchè non ti registri subito?

Dopo esserti registrato potrai chiedere
aiuto sul nostro Forum oppure aiutare gli altri

Consulta le Stanze disponibili.

Registrati ora !
Copyright © dotNetHell.it 2002-2023
Running on Windows Server 2008 R2 Standard, SQL Server 2012 & ASP.NET 3.5