i migliori amici dei programmatori GUI

Esempio: dlg_one

Difficilmente un programma per windows non contiene dialog boxes (Finestre di dialogo). Basta andare su File -> Apri in qualsiasi editor per rendersi conto che è così. Le Dialogs non sono limitati, possono assumere qualunque aspetto e fare qualsiasi cosa scegliate. Il punto di attrazione delle dialogs è che essi rappresentano una strada veloce per organizzare e creare una GUI (Graphic User Interface, Interfaccia Grafica Utente) e anche per creare un punto dove far eseguire operazioni comuni, tagliando di molto il numero di righe di codice che altrimenti avreste dovuto scrivere.

Una cosa da ricordare è che i dialogs sono semplicemente delle finestre. La differenza tra una dialog e una “normale” finestra è che il sistema esegue ulteriori operazioni di default su di essa, come creare e inizializzare controlli e processare l’ordine di tab. Normalmente tutte le API applicabili alle finestre “normali” funzioneranno nelle finestre di dialogo e vice versa!

Il primo passo è creare una risorsa di Dialog. Come per le altre risorse la creazione dipende dal compilatore/IDE. Di seguito vi viene mostrato il testo da della dialog all’interno del file .rc da incorporare nel vostro progetto.

IDD_ABOUT DIALOG DISCARDABLE  0, 0, 239, 66
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Informazioni su..."
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON   "&OK",IDOK,174,18,50,14
    PUSHBUTTON      "&Annulla",IDCANCEL,174,35,50,14
    GROUPBOX        "Informazioni su...",IDC_STATIC,7,7,225,52
    CTEXT           "Un esempio di Finestre di dialog\r\nby giusc",
                    IDC_STATIC,16,18,144,33
END

Nella prima riga, IDD_ABOUTDLG è l’id della risorsa, DIALOG è il tipo, e i quattro numeri sono le coordinate della posizione a Sinistra, in Alto, la Larghezza e l’Altezza. RICORDATE CHE NON SONO PIXELS, l’unita di misura e’ dbu (Dialog Box Units) che è basata sulla grandezza del font usato dal sistema (e scelto dall’utente). Se avete un font molto grande, la Dialog sarà grande e viceversa. Questo è importante per far si che tutti i controlli siano della grandezza esatta per visualizzare il testo con il font corrente. Potete convertire le Dialog Units in pixels mentre il programma è in esecuzione usando MapDialogRect(). DISCARDABLE dice al sistema che può “swappare” la memoria per le risorse sul disco quando non viene usata in modo da conservare le risorse di sistema (sostanzialmente inutile).

La seconda riga inizia con STYLE e prosegue con gli stili che verranno utilizzati per creare la finestra di dialogo. Questi stili dovrebbero essere spiegati all’interno della documentazione di CreateWindow() Per utilizzare le costanti predefinite dovrete aggiungere #include "windows.h" al file .rc, oppure nel caso di VC++, winres.h oppure afxres.h. Se usate un editor visuale quasi sicuramente questi files saranno aggiunti automaticamente se necessario. Il CAPTION non è altro che il titolo della dialog box.

La riga FONT definisce la grandezza ed il nome del font da usare per questa dialog box. Tenete conto che persone differenti possiedono font differenti quindi è consigliabile utilizzarne uno di sistema o che per lo meno è sicuramente presente sulla quasi totalità dei sistemi operativi dove il programma andrà a girare.

Ora passiamo alla lista dei controlli da creare all’interno della dialog

DEFPUSHBUTTON   "&OK",IDOK,174,18,50,14

Questa è la riga per il bottone OK. Il & in questo caso, come per i menu, sottolinea la lettera successiva “O”, in modo che premendo Alt+O l’utente può attivare questo controllo. (Questo fa parte del processo di default sopra menzionato). IDOK è l’identificatore del controllo.
IDOK è predefinito, quindi non andremo a definirlo ulteriormente. I quattro numeri successivi sono rispettivamente le coordinate Sinistra, Alto, Larghezza, Altezza. Tutto in dialog units.

Queste informazioni sono alquanto nozionistiche ed accademiche in quanto sicuramente quasi sempre utilizzerete un editor visuale per creare le dialog. Però è giusto conoscere anche come si fa a mano perché alcune volte si rivela necessario, specialmente se non avete un editor visuale.

Due dei controlli hanno un ID di IDC_STATIC (che corrisponde a -1), Questo viene usato per indicare che non avremo mai necessità di accedervi, quindi non hanno le necessità di un identificatore. Comunque non fa male dare anche a loro un ID e quasi sicuramente l’editor di risorse gliene assegnerà uno automaticamente.

Il "rn" nel testo del controllo statico è una coppia CR-LF, il modo in cui windows rappresenta una newline.

Ora! Dopo aver aggiunto il tutto al file .rc abbiamo bisogno di scrivere una Procedura per processare i messaggi della finestra di Dialogo. Non vi preoccupate, nulla di nuovo è la Dialog Procedure è praticamente la stessa cosa della Window Procedure (ma non esattamente identica).

BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT Message, WPARAM wParam,
                LPARAM lParam)
{
    switch(Message)
    {
        case WM_INITDIALOG:

        return TRUE;
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case IDOK:
                    EndDialog(hwnd, IDOK);
                break;
                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                break;
            }
        break;
        default:
            return FALSE;
    }
    return TRUE;
}

Ci sono poche ma importanti differenze tra una dialog procedure ed una window procedure. La prima è che nella dialog procedura non viene chiamata DefWindowProc() per i messaggi non processati. Con le dialog questo viene fatto automaticamente ed è veramente importante lasciarlo fare al sistema altrimenti si potrebbero avere conseguenze inaspettate.

Come seconda cosa, in generale si ritorna FALSE per i messaggi processati e TRUE per i messaggi processati A MENO CHE il messaggio non richieda di ritornare qualcos’altro. Notate che è esattamente quello che facciamo sopra: per default non facciamo nulla e ritorniamo FALSE tranne per i messaggi che processiamo dove ritorniamo TRUE.

Terzo, non chiamiamo DestroyWindow() per chiedere una dialog, chiamiamo EndDialog(). Il secondo parametro è il valore ritornato a qualsiasi cosa abbia chiamato DialogBox() Infine, invece di processare WM_CREATE, processiamo WM_INITDIALOG per fare qualsiasi tipo di inizializzazione dopodichè ritorniamo TRUE per avere il focus della tastiera sul controllo di default, FALSE se abbiamo cambiato il focus su un altro controllo. (Si può comunque processare WM_CREATE ma ricordate che esso viene chiamato prima che qualsiasi controllo venga creato, quindi è possibile accedere ai controlli, mentre in WM_INITDIALOG i controlli sono stati già creati).

Troppo bla-bla, creiamolo….

case WM_COMMAND:
    switch(LOWORD(wParam))
    {
        case ID_HELP_ABOUT:
        {
            int ret = DialogBox(GetModuleHandle(NULL),
                MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDlgProc);
            if(ret == IDOK){
                MessageBox(hwnd, "Dialog terminato con IDOK.", "Avviso",
                    MB_OK | MB_ICONINFORMATION);
            }
            else if(ret == IDCANCEL){
                MessageBox(hwnd, "Dialog terminato con IDCANCEL.", "Avviso",
                    MB_OK | MB_ICONINFORMATION);
            }
            else if(ret == -1){
                MessageBox(hwnd, "Dialog fallito!", "Errore",
                    MB_OK | MB_ICONINFORMATION);
            }
        }
        break;
        // altri comandi menu...
    }
    break;

Questo è il codice usato per creare l’about box, potete immaginare probabilmente che il codice sopra vada inserito all’interno dell’handler per WM_COMMAND. Se questo aspetto non vi è chiaro, vi consiglio di riguardare la parte relativa ai menu. ID_HELP_ABOUT è l’identificatore per Aiuto -> Informazioni su….

Siccome vogliamo che la Dialog venga creata quando si fa click sul menu della nostra finestra principale ovviamente andremo ad inserire questo codice all’interno della WndProc() della nostra finestra principale, non nella dialog proc.

Notate che viene visualizzato un messaggio sul valore di ritorno della chiamata a DialogBox(), questo, sia per farvi osservare gli effetti della pressione dei due bottoni, pressione di Esc, Invio etc… dall’interno della dialog, sia per illustrarvi come usare il valore di ritorno di una dialog box per controllarne il successo, il fallimento, la scelta dell’utente o qualsiasi informazione interessi recuperare dall’uso della dialog box.

DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_ABOUT), hwnd,
         AboutDlgProc);

Questa è l’unica parte importante, e potete scegliere di inserirla ovunque all’interno del codice ove desideriate che la dialog appaia.
IDD_ABOUT è l’id della risorsa dialog. hwnd è l’handle alla finestra padre della dialog AboutDlgProc() è ovviamente la Dialog Procedure usata per controllare la finestra di dialogo.

Questo è tutto!

Un lettore particolarmente astuto potrebbe eventualmente chiedersi, se DialogBox() non ritorna finchè la dialog viene chiusa, non possiamo processare i nostri messaggi quando sta aperta, quindi come funziona? Beh la cosa carina su DialogBox() è che essa ha il proprio loop di messaggi, quindi mentre la dialog viene visualizzata, il nostro message loop è fuori dall’immagine e il loop di default viene processato da Windows che oltretutto si prende anche cura di alcune cose simpatiche tipo muovere il focus della tastiera da controllo a controllo premendo Tab.

Un altro effetto dell’uso di DialogBox() è che la finestra principale è disabilitata finchè la dialog non viene chiusa. Alcune volte è quello che vogliamo, altre no, tipo quando vogliamo usare una dialog come toolbar mobile, questo è il caso in cui vogliamo essere in grado di interagire sia con la finestra principale che con la dialog; è di questo che parleremo nella prossima sezione.