Introduzione alle API Win32

Capitolo 41: dialoghi non modali

I programmi che abbiamo sviluppato sino a questo punto del corso presentano sempre una sola "finestra principale", e più precisamente un dialogo, generato (a partire da un "template di dialogo" contenuto nelle risorse del nostro eseguibile, e ivi inserito dal linker, a partire da un file sorgente dato in pasto al "resource compiler", RC), da una chiamata all'API DialogBoxParam.

Questo tipo di finestre sono detti dialoghi modali; loro caratteristica precipua è quella di prendere pieno controllo della gestione degli eventi nel corso della loro esistenza (cioè nel tempo che intercorre fra la creazione attraverso l'API DialogBoxParam o simili, e la terminazione attraverso l'API EndDialog).

Questa "modalità" non è naturalmente un particolare problema per un dialogo che costituisce l'unica finestra della nostra applicazione, come sin qui è stato. In casi più generali, un dialogo modale costituisce invece una finestra necessariamente "transitoria", che viene attivata per un compito specifico (tipicamente, un compito di "dialogo" con l'utente, da cui il nome), e sparisce quando ha portato a termine quel compito (visto che, sinchè non sparisce, le altre finestre dell'applicazione sono disabilitate).

In quest'ottica, ad esempio, il sistema mette a nostra disposizione un certo numero di "dialoghi comuni", già pronti e "impacchettati", che svolgono comuni compiti di dialogo con l'utente, come quello di fargli scegliere un file da aprire, e così via. Sull'argomento "dialoghi comuni" ritorneremo più avanti nel corso di questo tutorial.

Ma, chiaramente, si può avere anche una esigenza piuttosto diversa: quella di aprire, in addizione ad altre finestre dell'applicazione, anche un dialogo che non monopolizzi l'interazione con l'utente, bensì "se ne stia buono buono nel suo cantone", mostrando all'utente stesso puro e semplice output (come nel caso del nostro "output di debug"), ovvero permettendogli interazioni per modificare parametri che possono essere cambiati in qualsiasi momento. A questo ruolo adempiono egregiamente i dialoghi non modali.

(In generale, per permettere l'interazione completa e normale con un dialogo non modale, è necessario inserire nel nostro "ciclo di gestione messaggi" una chiamata all'API IsDialogMessage, che traduce certi tasti (come tab, e frecce), spediti al dialogo non modale, in operazioni di "navigazione" sul dialogo stesso (perfettamente analoghe a quelle che, automaticamente, sono messe a nostra disposizione dai dialoghi modali).

Purtroppo, il ciclo di gestione dei messaggi non è sotto il nostro controllo quando è aperto un nostro dialogo modale; prendere il controllo di questo ciclo può anzi essere fra le motivazioni principali del passaggio da un'applicazione basata su dialogo modale, ad un'altra forma, anche se, per applicazioni molto semplici, non doversene occupare costituisce in effetti un vero e proprio beneficio.

Comunque, per ora non soffriremo della mancanza delle operazioni di "navigazione da tastiera" sui dialoghi non modali, e quindi potremo rimandare questa problematica.)

La creazione di un dialogo non modale si effettua con l'API, del tutto analoga a DialogBoxParam:

HWND CreateDialogParam( HINSTANCE hInstance, // istanza dell'applicazione LPCTSTR lpTemplateName, // il template del dialogo HWND hWndParent, // finestra proprietaria DLGPROC lpDialogFunc, // la "dialog procedure" LPARAM dwInitParam // usato in WM_INITDIALOG ); I parametri sono appunto identici alla API DialogBoxParam, che è usata per la creazione di dialoghi modali.

A differenza di un dialogo modale, uno non-modale non viene automaticamente mostrato alla creazione, a meno che non abbia fra i suoi bit di stile WS_VISIBLE. Più in generale, il programma lo mostrerà (e, se occorre, lo nasconderà di nuovo) con l'API ShowWindow.

Alla creazione, il dialogo non modale viene reso "finestra attiva" di questo processo; il programma, naturalmente, può ignorare questo fatto, usando l'API SetActiveWindow per porre attiva quella che preferisce fra le proprie finestre (l'analoga API SetForegroundWindow, più "drammatica" nei propri effetti, va invece chiamata solo in situazioni di emergenza, tali da esigere immediata attenzione da parte dell'utente; tipicamente, essa si giustifica solamente per errori critici che vanno rimediati con la massima urgenza -- tenetela presente se, e solo se, vi troverete a scrivere la GUI del software di controllo di una centrale nucleare:-).

La cosa fondamentale, naturalmente, è che il dialogo non-modale non "monopolizza" l'interazione; l'unico rapporto con la finestra "genitrice", o "proprietaria" che dir si voglia, è che il dialogo modale si mantiene sempre al di sopra di essa (quindi, può in parte coprirla) anche se non è attivo (inoltre, naturalmente, se viene distrutta una finestra-proprietaria, ovvero genitrice, il sistema distrugge anche, e in effetti subito prima, le finestre-figlie, ovverossia finestre-possedute).

Se non affidata a simili automatismi, la distruzione di una finestra di dialogo non modale si ottiene con l'API:

BOOL DestroyWindow( HWND hWnd // handle della finestra da distruggere ); L'importante è ricordarsi di non chiamare mai EndDialog per un dialogo non modale, bensì escluusivamente per quelli modali.

Possiamo dunque fare all'esempietto del capitolo precedente le modifiche necessarie per ottenere l'output delle stringhe su di un dialogo separato. Il file RC diverrà qualcosa del genere:

IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 117, 47 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Modale" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,60,5,50,14 PUSHBUTTON "Cancel",IDCANCEL,5,5,50,14 EDITTEXT IDC_EDIT1,5,25,105,20,ES_AUTOHSCROLL END IDD_DIALOG2 DIALOG DISCARDABLE 230, 0, 122, 68 STYLE WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Non-modale" FONT 8, "MS Sans Serif" BEGIN LISTBOX IDC_LIST1,10,5,105,60,NOT LBS_NOTIFY | LBS_NOSEL | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP END Negli stili del dialog template da cui costruiremo la finestra non modale, abbiamo omesso la "modal frame" (che non sarebbe appropriata) e lo stile "popup" (idem, visto che ne vogliamo fare una finestra indipendente); abbiamo invece inserito WS_VISIBLE affinchè la finestra venga visualizzata alla creazione.

La dialog procedure per il dialogo non-modale ha qui l'unico scopo di restituire, all'inizializzazione, la handle del listbox control, e a questo scopo passeremo come lParam l'indirizzo di una nostra variabile di tipo HWND in cui questo valore verrà depositato; il frammento rilevanta sarà dunque:

case WM_INITDIALOG: { HWND* pHw = (HWND*)lParam; *pHw = GetDlgItem(hwndDlg, IDC_LIST1); return 1; }

Questa HWND la passeremo al dialogo modale come suo lParam, quindi nella relativa dialog procedure avremo:

case WM_INITDIALOG: SetWindowLong(hwndDlg, DWL_USER, lParam); return 1; scegliendo di depositare questo valore come long-word di indice DWL_USER della finestra del dialogo (l'indice, appunto, che è riservato per i dati "nostri"); la risposta al clic sul bottone di OK diventerà dunque qualcosa di simile a: case IDOK: { char buf[256]; GetDlgItemText(hwndDlg, IDC_EDIT1, buf, 256); LONG hlist = GetWindowLong(hwndDlg, DWL_USER); SendMessage((HWND)hlist, LB_ADDSTRING, 0, (LPARAM)buf); Non molto diverso da quanto visto al capitolo precedente, dunque, salvo il piccolo dettaglio relativo a come recuperare l'HWND del list control per spedirgli il messaggio di aggiunta-elemento.

Così risolto il cruciale aspetto strutturale, passeremo, al prossimo capitolo, a studiare più in dettaglio i controlli list-box, anche per consentirci poi di rimediare ai due difettucci secondari che avevamo già identificato alla fine del capitolo precedente.


Capitolo 40: output per il debug
Capitolo 42: controlli: i LISTBOX
Elenco dei capitoli