Newsgroup: it.comp.lang.visual-basic
Titolo: ActiveX e form a visualizzazione Modale

Salve
ho un problema abbastanza importante, e sarei grato se qualcuno potesse
aiutarmi.
Allora, partiamo dalla situazione:
sto sviluppando un'applicazione che deve servire da supervisione ad un
impianto industriale, e di conseguenza deve leggere da 3 linee seriali
(RS232), da eventi esterni via ActiveX, monitorare una text box che
cambia mediante un SendMessage esterno e leggere dati via Ethernet.
Il tutto possibilmente perdendo meno colpi (quindi dati, poichè
vengono archiviati in un file
csv) possibile.
Il problema è nella visualizzazione dei vari form, che a volte mi
bloccano gli eventi del form
principale, perdendo quindi le letture di cui sopra.
Mi spiego meglio:
I form mostrati in modalità modale (vbModal) non bloccano gli eventi
del form chiamante (che siano timer, linea seriale in ricezione, eventi
da activeX esterni, ricezioni da SendMessage, tutte verificate di
persona).
Le msgbox bloccano tutti questi eventi. Ho risolto questo problema
creando una mia funzione MyMsgBox identica all'originale, ma, poichè
mostrata con vbModal, non mi blocca il resto. E così funziona.
Il problema che devo ancora risolvere è con le finestre a
visualizzazione modale inserite in
controlli ActiveX che ho sviluppato o che sto sviluppando ora (per una
maggiore modularità
dell'applicazione, poichè mi serviranno molte altre volte).
I form mostrati in vbModal all'interno di controlli activeX, quando
sono richiamati dall'applicazione principale (quindi mediante chiamata
a funzioni o sub del controllo che a loro volta richiamano la finestra
in vbModal) mi bloccano tutti gli eventi, esattamente come le msgbox.
oppure come le CommonDialogBox.
Ho provato a modificare tutte le chiamate di visualizzazione dei form
dei controlli activeX da vbModal a vbmodeless: così funziona, non mi
blocca nulla, ma trovo che la soluzione sia poco elegante, e inoltre mi
crea altri problemi.
In certi casi infatti mi è utile bloccare l'esecuzione di una funzione
all'apertura di un form
e lasciarla proseguire solo alla sua chiusura, ma se il form è aperto
in vbModeless, l'esecuzione della sub continua subito dopo la
visualizzazione del form, senza aspettare che questo sia chiuso.
Certo potrei giocare con gli enable, che permettono eventi, ma credo di
incasinare il tutto a scapito della stabilità dell'applicazione
principale.
Se qualcuno ha qualche dritta, è il benvenuto
Grazie
Marco F.

dell'applicazione, poichè mi serviranno molte altre volte).
Oltre a questo, considera che c'è un noto bug dell'IDE, mai risolto e oramai
credo imperituro, per cui se visualizzi il mymsgbox, poi lo chiudi, e poi
digiti qualcosa in una textbox sullo UserControl, VB6 si chiude e
buonanotte, senza avvisi di alcun tipo. Buona norma salvare spesso.
Tornando al tuo problema, mostrare le form in VbModeless non mi piace,
perchè comunque se ti servivano Modali un motivo c'era, e convertirle a
Modeless lo ritengo sbagliato concettualmente.
Ma, effettivamente, le Modal intercettano e non passano gli eventi allo
UserControl padre della form.
Noi sviluppiamo un buon 50% degli applicativi (no, forse di più) come
UserControl, e abbiamo scelto una strada diversa.
Richiede un po' di rigore nel distribuire il codice nel modulo, ma funziona
bene.
Anzichè Form, utilizziamo dei Frame.
Quando ci occorre visualizzare una finestrella di dialogo, in realtà
disabilitiamo il Frame Main, e rendiamo visibile e abilitato il Frame
'Dialog', che passa in primo piano. Certo, non è spostabile come una form,
ma non dà problemi ed è implicitamente 'modale'.
Resta il tuo problema di sospendere una sub durante la visualizzazione del
form/frame, ma qui non vedo altro modo che quello di uscire subito dalla
sub, e gestire il proseguimento sull'evento di chiusura del form/frame.
Visto che operi con raccolta dati e controlli industriali, ti faccio notare
che nessuno inserisce in uno stesso applicativo sia la parte di
comunicazione che quella di interfaccia. Cioè, la parte di comunicazione
(ascolto e invio su seriale o altro mezzo) vive in un applicativo in
background (minimizzato o, infinitamente meglio, un servizio NT), mentre la
parte di interfaccia è una normale consolle che non ha lo stretto problema
del real-time.
Oppure, puoi passare a sistemi in multithreading, come .Net, e sei a posto
(cioè, non è che con la seriale sia una bomba, ma per andare va).
Ciao
Alberto

 Se qualcuno ha qualche dritta, è il benvenuto
Oltre ai suggerimenti di Albe, ti dò qualche altra dritta:
1) Per sostituire i MsgBox che bloccano gli eventi, usa l'api MessageBox:
Private Declare Function MessageBox Lib "user32.dll" Alias "MessageBoxA" (ByVal
hwnd As Long, ByVal lpText As String, ByVal lpCaption As String, ByVal wType As
Long) As Long
Public Function APIMsgBox(ByVal Prompt As String, Optional ByVal Style As
VbMsgBoxStyle = vbOKOnly, Optional ByVal Title As String) As VbMsgBoxResult
' Mostra un MessageBox "non bloccante" il loop dei messaggi di Windows
Dim hwnd    As Long
    If Not Screen.ActiveForm Is Nothing Then
        hwnd = Screen.ActiveForm.hwnd
    End If
    APIMsgBox = MessageBox(hwnd, Prompt, Title, Style)
End Function
2) Per "simulare" l'apertura modale di un form, in modo sì da sospendere
l'esecuzione, ma senza bloccare il loop degli eventi di Windows, nel form da
mostrare aggiungi un metodo "ShowDialog", così fatto, da chiamare al posto di
"Show vbModal":
Private Declare Function WaitMessage Lib "user32.dll" () As Long
Public Function ShowDialog() As Boolean
' Ad es. ritorna True/False a seconda che l'utente abbia premuto OK o Annulla...
    Me.Show vbModeless
    Do While Visible
        WaitMessage ' <-- fondamentale per evitare che la CPU schizzi al 100%
        DoEvents
    Loop
    ShowDialog = <il valore di un flag che setti in cmdOK_Click, ad es...>
End Function
Naturalmente negli eventi Click degli eventuali pulsanti OK ed Annulla, chiami
Me.Hide.
Analogamente, nell'evento QueryUnload, se non innescato da codice (quindi ad es.
se chiudi usando il pulsante std. di chiusura, devi settare Cancel=True e
nascondere il form con Hide.
Però comunque così non è ancora realmente "modale" poiché il form di dialogo non
è vincolato a stare "davanti" al form "owner" e quest'ultimo non è disabilitato.
Il codice si può migliorare passando alla funzione ShowDialog un riferimento al
form Owner:
Public Function ShowDialog(OwnerForm As Form) As Boolean
    OwnerForm.Enabled = False
    Me.Show vbModeless, OwnerForm
    Do While Visible
        WaitMessage
        DoEvents
    Loop
    OwnerForm.Enabled = True
    ShowDialog = ...
End Function
My 2... pardon, 4 cents...
Ciao
Raf

Innanzi tutto grazie ad entrambi.
Rispondo con un unico post solo per semplicità (scrivo da casa con una
connessione UMTS, quindi costosa, e usando l'interfaccia di Google a
usenet, quindi non ho proprio le comodità di un Free Agent a portata
di mano + ADSL.
Le dritte di Alberto e di Rafunk sono state utilissime, se non
fondamentali.
Rispondo punto per punto.
1) Alberto circa lo stile di programmazione a frame piuttosto che a
form: ottimo suggerimento, ho riscritto un controllo e risolto un
problema
2) Alberto e la questione di raccolta dati > nessuno inserisce in uno
stesso applicativo sia la parte di comunicazione che quella di
interfaccia.
Hai ragione, Mi sono spiegato male: l'interfaccia operatore deve
gestire 4 sistemi di visione, un modulo GSM, un PLC Omron e un gruppo
di continuità. Noi infatti costriamo impianti di controllo basati su
sistemi di ispezione visiva, l'interfaccia operatore serve solo per
visualizzare le statistiche del PLC (non salvarle, poichè ci pensa
già lui), le immagini elaborate delle telecamere, con setup e
tolleranze annesse, e qualche altra funzione. L'esigenza di salvare
dati in un CSV è nata da poco, e in questo caso (l'applicazione che
stiamo sviluppando) deve essere una lettura via ethernet ogni 2
secondi, ma è ammesso (seppur raramente) perdere qualche dato.
Per applicazioni successive, se il reporting diventa fondamentale,
utilizzaremo la tua tecnica, o cmq sdoppieremo il programma di
interfaccia da quello di lettura e salvataggio dati.
PS parli di servizio NT e di realtime, ma su winXp, con una tempistica
non deterministica, come si può parlare di real time?
3) Rafunk: MSGBOX non bloccanti: Ottima dritta, in questo modo le
msgbox dell'applicazione principale non mi bloccano gli eventi, e
neanche quelle dei controlli ActiveX.
Le ho inserite infatti anche nei vari controlli. Ho solo notato che se
una msgbox mi veniva lanciata dal form main mentre era aperta una
finestra del mio controllo, questa non bloccava la finestra del
controllo, ma solo la form main.
Ho risolto in questo modo:
Public Function MsgBox(ByVal Prompt As String, Optional ByVal Style As
VbMsgBoxStyle = vbOKOnly, Optional ByVal Title As String = "") As
VbMsgBoxResult
' Mostra un MessageBox "non bloccante" il loop dei messaggi di Windows
Dim hWnd    As Long
    If GetForegroundWindow() <> 0 Then
        hWnd = GetForegroundWindow() 'API da user32.dll
    Else:
        hWnd = Screen.ActiveForm.hWnd
    End If
    MsgBox = MessageBox(hWnd, Prompt, Title, Style)
End Function
In questo modo ogni messagebox è sopra la prima finestra visualizzata.
Certo se apro per un momento Notepad e la form main del mio applicativo
mi genera una msgbox, quest'ultima avrà come genitore il mio notepad,
ma è una evenienza che non deve capitare mai: l'utente può eseguire
solo il mio programma e basta.
4) Rafunk: ottimo consiglio sulla simulazione modale con un loop in
attesa di risposta! Tra l'altro non sapevo neanche della WaitMessage, e
mi sono attivato per inserirla dopo il DoEvents in tutti gli altri
(anche se pochi) loop che avevo inserito nel programma.
Ho ampliato il trucco in questa maniera:
fUnloadFORM_DA_APRIRE = False   'flag semaforica di chiusura finestra
If HandleFrmMain <> 0 Then EnableWindow HandleFrmMain, 0
FORM_DA_APRIRE.Show , Me
Do
DoEvents
WaitMessage
Loop Until fUnloadFORM_DA_APRIRE = True
If HandleFrmMain <> 0 Then EnableWindow HandleFrmMain, 1
La HandleFrmMain la ricavo dal registro (GetSetting), e viene impostata
(SaveSetting) direttamente dalla mia finestra dell'applicativo
principale dove risuede il mio controllo ActiveX. In questo modo la
finestra non modale aperta dal controllo mi blocca anche l'applicativo
principale, ma consenti gli eventi.
5) MSGBOX personalizzate.
Nel vecchio post avevo scritto che usando una mia MyMsgBox avevo
risolto il problema del blocco degli eventi. Non è vero. Non sono un
programmatore eccelso, ma mi ritengo un buon tester (saranno i 5 anni
di attività in icsv e ahccc???), e ho scoperto parecchi problemi, che
tutt'ora non sono riuscito a risolvere.
Infatti se arrivano due chiamate alla mymsgbox, che apre una finestra
vbmodal, la seconda chiamata mi ritorna un errore se la prima finestra
è visualizzata.
Tuttavia se gestisco il tutto con flag di semaforo e loop intermedi mi
incasino con le risposte. Ma questa è un'altra questione, e non voglio
tediarvi troppo, ammesso che mi stiate ancora leggendo. Peccato, la mia
MyMsgBox aveva pulsanti grandi il doppio, e in un'applicativo che deve
girare in un touch-screen era molto comodo.
Ad ogni modo, grazie davvero per l'aiuto.
Marco F.

 girare in un touch-screen era molto comodo.
Forse va pensato tutto in modo diverso. Se serve che l'utente prenda atto di una
situazione ma è previsto che vi siano più "allarmi" in successione, IMHO un
semplice messagebox non è forse l'interfaccia più adatta.
Bye
Raf

 non deterministica, come si può parlare di real time?
Beh, ovviamente mi riferivo al 'real-time' come può essere in ambiente 
windows, ossia 'gestirò quasi subito'.
E' chiaro che con un plc ad interrupt hai una gestione in tempo 
veramente reale, ma è un altro discorso.
Ciao
Alberto
-- 
A questo punto hanno trovato un muro,
un muro duro, molto molto duro,
che siamo noi.