Sviluppo

Come faccio? - parte IX

Gianni Ceccarelli
 

stesura originale in inglese di Eric Slaats - liberamente tradotto da Gianni Ceccarelli
comparso per la prima volta su Os/2 e-Zine! volume 2 numero 7

Benvenuti alla nostra nuova puntata sui dialogs. Il mese scorso abbiamo introdotto brevemente i dialogs e abbiamo parlato di alcuni concetti fondamentali. I più importanti erano i concetti di finestra "parent" ("genitrice") e "owner" ("possessore") e di dialogo "modal" e "modeless". Abbiamo anche dato un'occhiata alla speciale API di OS/2 che ci permette di produrre rapidamente un semplice dialog in cui l'utente può ricevere un messaggio e dare una semplice risposta.

Questo mese ci addentriamo nelle modalità di creazione dei dialogs. Daremo uno sguardo alle risorse e ai loro rapporti con i dialogs. Discuterò come gestire dialogs modali e non modali e come ottenere alcune funzionalità di interfaccia dai dialogs senza programmare, con l'introduzione del concetto di "gruppo".

Ricordate alcuni articoli fa, quando ho esposto la mia filosofia su come dovrebbero essere fatti i programmi? Siate pigri (non fate ciò che pyò essere fatto al posto vostro) e fatelo semplice. In quell'articolo facemmo il nostro primo incontro con le risorse. Inoltre, creammo un primo script di risorse per un menu. Lo facemmo scrivendo un file di testo, ma era sicuramente più semplice che dover programmare ogni opzione di menu con una chiamata API o facendo scambiare messaggi tra il nostro programma e la finestra del menu. Demmo anche una semplice definizione di un file di risorse. Ecco un riassunto:

In un file di risorse (.RC) possiamo descrivere i controlli con una specie di linguaggio. Non è esattamente un linguaggio di programmazione, è più un linguaggio usato per descrivere come appaiono le cose. Alcuni esempi di risorse che possono essere definite sono:

  • Menu
  • Tabelle di acceleratori
  • Modelli di dialoghi e di finestre
  • Icone
  • Fonts
  • Immagini bitmap
  • Stringhe
Nella maggior parte dei casi, non dobbiamo preoccuparci di scrivere lo script di risorse a mano. Assieme a molti compilatori, nonché con il Toolkit di OS/2, vengono forniti editor di risorse dedicati. Questi strumenti offrono un ambiente grafico in cui potete disegnare le risorse che volete usare.

Non approfondiremo il linguaggio di script per i dialogs in questo articolo (anche se una conoscenza più dettagliata può essere utile di tanto in tanto). Assumerò che chiunque possa ottenere un editor di dialogs visuale. Se usate un compilatore public-domain, c'` sempre dlgedit.exe (GIF, 9.6k) dell'IBM che fa parte del developer's toolkit. Perosnalmente preferisco il resource workshop del BCC 2.0 per OS/2 (GIF, 13.5k). Ma ci sono anche altri eccellenti prodotti sul mercato. Prominar designer è uno di questi (in effetti è molto più di editor di dialogs).

Come funziona un editor di dialogs? Se avete mai lavorato con Delphi, Visual Cafe o altri strumenti visuali, non avrete nessun problema. Potete trascinare vari controlli tipo notebook, entry fields (campi di immissione testo), spin buttons (i pulsantini " freccia su" e "freccia giù" per incrementare e decrementare un valore), etc. sulla finestra del dialog. Qui potete cambiarne le dimensioni e gli attributi come il colore e il font. Ora, questa è la parte facile. Probabilmente chiunque può imparare a trascinare controlli in una finestra in pochi minuti.

Ora è un buon momento per avviare l'applicazione di esempio di questo mese (ZIP, 16.7k). Quando l'avviate, noterete che ha due opzioni di menu. Ci arriveremo tra un minuto. Per ora, sceglietene una e apparirà questo dialog (GIF, 4k). Ora provate quanto segue:

  • Selezionate il radio button 1 e usate i tasti cursore
  • Selezionate il radio button A e usate i tasti cursore
  • Selezionate uno degli entry field e usate i tasti cursore
  • Selezionate il pulsante X e usate i tasti cursore
L'effetto che dovreste notare è causato dal raggrupamento dei controlli. Usando i tasti cursore, potete selezionare uno dei controlli del dialog. Questo è molto comune con i radio buttons, ma non è quasi mai usato con gli entry fields. Specialmente con questi ultimi, trovo il raggrupamento molto conveniente. Potete scorrere i vari entry fields usando semplicemente i tasti cursorse, in modo naturale e intuitivo. È incredibile che questa caratteristica sia usata così raramente.

L'effetto di raggruppamento può essere ottenuto con molti dei controlli di OS/2. Ci sono, ovviamente, controlli che reagiscono per conto proprio ai tasti cursore; in questi casi potete sempre usare il tasto TAB per passare al controllo successivo. Il dialog dell'esempio di questo mese mostra l'effetto del raggruppamento per i pulsanti, i radio buttons e gli entry fields.

La cosa migliore è che per ottenere questo effetto non serve alcuna programmazione. Infatti, non scriveremo una sola linea di codice del dialog questo mese.

Diamo un'occhiata al file .RC dell'esempio. Ho disposto i gruppi di controlli in questo file con una linea di separazione. È tutto qui. Il compilatore di risorse disporrà i controlli nel dialog nello stesso ordine che hanno nel file .RC. Questo indicherà anche l'ordine in cui i controlli vengono selezionati quando si usa il tasto TAB. Perciò la prima cosa che dovete fare dopo che avete messo tutti i controlli nel dialog è accertarvi che i controlli siano in una sequenza logica. Ogni editor di risorse che ho visto ha un modo per ordinare i controlli. Questo è un'immagine del resource editor del BCC 2.0 (GIF, 13.8k) quando è in modo ordinamento.

Semplicemente ordinando i controlli, non possiamo ottenere l'effetto di raggruppamento. Normalmente, ogni controllo sarà gestito come un gruppo a sé. Questo significa che non otterremmo il comportamento mostrato nell'esempio.

Guardiamo di nuovo il file .RC. Potete vedere che alcune linee contengono l'attributo WS_GROUP. Se una linea contiene questo attributo, segna l'inizio di un gruppo. Vengono aggiunti a questo gruppo tutti i controlli che seguono che non hanno l'attributo WS_GROUP. Potete esaminare il file RC dell'esempio per vedere quest'effetto. Per chiarire un po' più le cose, facciamo un altro semplice esempio.

Diciamo che volete costruire un dialog con tre pulsanti e quattro entry fields. I pulsanti devono formare un gruppo a sé, e così anche gli entry fields. Quindi il file RC deve avere questa struttura:

DLGTEMPLATE DIALOG
BEGIN
    CONTROL "", ENTRYFIELD1 ....................  | WS_GROUP
    CONTROL "", ENTRYFIELD2 ....................
    CONTROL "", ENTRYFIELD3 ....................
    CONTROL "", ENTRYFIELD4 ....................

    PUSHBUTTON "A",.............................  | WS_GROUP
    PUSHBUTTON "B",.............................
    PUSHBUTTON "C",.............................
END
Se non volete raggrupparli, date ad ogni controlli l'attributo WS_GROUP (molti editor di risorse lo fanno automaticamente).

Ora che abbiamo creato le risorse, esaminiamo le API da usare per attivare un dialog definito in un file di risorse. Come potete aver capito, ci sono due chiamate: una per un modal dialog e una per un modeless dialog.

Con WinDlgBox possiamo aprire un dialog modale. La sintassi di questa API è la seguente:

HWND   hwndParent;     //  Handle della finestra parent.
HWND    hwndOwner;      //  Handle della finestra owner.
PFNWP   pfnDlgProc;     //  Dialog procedure.
HMODULE hmod;           //  Dove si trova la risorse.
ULONG   idDlg;          //  Identificatore del dialog nel file di risorse.
PVOID   pCreateParams;  //  Puntatore a un'area dati specifica dell'applicazione.
ULONG   ulResult;       //  Valore di ritorno.

ulResult = WinDlgBox(hwndParent,
                     hwndOwner,
                     pfnDlgProc,
                     hmod,
                     idDlg,
                     pCreateParams);
Come funziona questa chiamata? Esaminiamo i parametri uno alla volta. Per primo, il valore di ritorno. Questo non è molto interessante. È un valore che dice se il sistema è stato in grado di creare il dialog o meno. Conterrà il valore DID_ERROR se qualcosa è andato storto. I due parametri successivi si spiegano da soli, sono gli handle alle finestre parent e owner.

Il prossimo: pfnDlgProc. Questo richiede un po' di spiegazione. I dialog sono gestiti da una speciale window procedure, come la finestra principale di un'applicazione. In questo parametro dovremo passare l'indirizzo della window procedure che gestisce il dialog (ne riparleremo in un futuro articolo). Per ora useremo la window procedure di default per gestire i messaggi inviati al dialog.

Se un dialog ha la sua window procedure, significa che può anche usare la window procedure di default, WinDefWindowProc? NO! Questo è uno dei più comuni errori fatti dai programmatori e può portare a comportamenti scorretti e incontrollati. I dialog hanno la loro speciale window procedure, chiamata WinDefDlgProc. Se passiamo questa funzione per pfmDlgProc, il dialog apparirà e risponderà alle nostre azioni. D'altra parte, non avrà comportamenti particolari. Per ottenerli dovremmo programmare.

Poi: hmod. Questo è un handle al file di risorse. È possibile immagazzinare le risorse in una DLL. Questo può essere molto conveniente quando volete rilasciare un programma multilingue. Scrivete vari file .RC, uno per lingua, e compilateli in varie DLL. Scegliete la DLL giusta al momento dell'installazione e potete impostare l'applicazione per ogni lingua vogliate. Per ora non ci addentreremo in questa interessante possibilità. Inseriremo le risorse nel file .exe (normalmente si fa così). Se le risorse sono nel file .exe, hmod deve valere NULLHANDLE.

Ancora: dDlg. Questo è l'ID del dialog che vogliamo mostrare. È solo il numero che abbiamo assegnato al dialog quando lo abbiamo creato. Normalmente si usa una costante definita in un file header. Nel nostro case è la costante DIALOG1.

Infine abbiamo pCreateParams. È possibile passare un puntatore al dialog per fornirgli informazioni aggiuntive. Nel nostro caso non è necessario, quindi mettiamo uno 0. Quando riparleremo della window procedure dei dialog, torneremo su questo parametro.

Tenendo conto di tutto questo, possiamo scrivere la chiamata per mostrare una versione modale del nostro DIALOG1.

WinDlgBox(HWND_DESKTOP,            // Parent
          hwnd,             // Owner
          WinDefDlgProc,    // Dialog window procedure
          NULLHANDLE,       // Dove è la risorse?
          DIALOG1,      // Dialog ID
          0);           // Parametri di creazione
La chiamata per creare dialog non modali è molto simile. Anche i parametri sono gli stessi. C'è però una differenza importante: il valore di ritorno è l'handle della finestra del dialog. Vedremo tra poco perché questo sia utile. Questa API si chiama WinLoadDlg.

Il mese scorso ho detto che la possibilità per un applicazione di creare istanze multiple di uno stesso dialog non modale può essere vista come un errore quando questo effetto non è richiesto esplicitamente. Mostrerò un semplice trucco per evitare questo comportamento. Quando ho cercato di gestire questa situazione per Smalled, mi sono accorto che alcune cose non erano così semplici come avevo pensato che fossero. Definiamo prima il problema che vogliamo affrontare. Si divide in due parti:

  1. Un dialog non modale può esistere al più una volta. Questo significa che non dobbiamo creare un nuovo dialog ogni volta che viene selezionata la voce di menu relativa.

  2. Se il dialog esiste già, dovrebbe diventare attivo quando viene selezionata la voce di menu.
Per gestire la prima parte di questo piccolo problema, quando la voce di menu viene attivata, deve controllare per prima cosa se il dialog esiste già. Per questo, ci serve almeno l'handle della finestra. Inoltre avremo bisogno di ricordarlo per la prossima invocazione di questa parte della window procedure. Usiamo quindi una variabile statica. Questa variabile si comporta quasi come una variabile globale, ma è visibile solo localmente.

Poi dobbiamo decidere se abbiamo bisogno di creare il dialog. Possiamo farlo controllando se la finestra è visibile. Mi ci è voluto un bel po' per capirlo. Sarebbe stato più logico usare la funzione WinIsWindow, ma strano a dirsi, questa funzione ritorna TRUE anche dopo che il dialog è stato chiuso! Perciò ho cominciato a cercare una funzione da usare al suo posto. Ho deciso per WinIsWindowShowing. Questa funzione ritorna TRUE quando una qualche parte della finestra è visibile.

Quindi la prima cosa da fare è accertarsi che il dialog sia visibile. Per far ciò chiamamo WinSetActiveWindow. Questo renderà visibile il dialog se esiste. Poi, possiamo chiamare WinIsWindowShowing. Se il dialog esiste, ritornerà TRUE. Questo ci porta al codice seguente:

static HWND hwndDlg = 0;

WinSetActiveWindow(HWND_DESKTOP,hwndDlg);
if (!WinIsWindowShowing(hwndDlg))
    {
    // Crea il dialog
    }
Questo semplice codice risolverà correttamente la prima parte del nostro problema. La cosa bella è che anche la seconda parte è risolta! Ci pensa WinSetActiveWindow. Quindi il codice completo è:
case IDM_DIALOG2:
    {
    static HWND hwndDlg = 0;

    WinSetActiveWindow(HWND_DESKTOP,hwndDlg);
    if (!WinIsWindowShowing(hwndDlg))
        {
        hwndDlg = WinLoadDlg(HWND_DESKTOP,  // Parent
                         hwnd,              // Owner
                         WinDefDlgProc,     // Dialog window procedure
                         NULLHANDLE,        // Dov'è la risorse?
                         DIALOG1,           // Dialog ID
                         0);                // Parametri di creazione
        }
    }
break;
Perché funziona? Siamo sicuri che ogni finestra nel sistema avrà un HWND unico in ogni momento. Perciò sappiamo che stiamo sempre gestendo la finestra giusta.

Bene, per questo mese è tutto. Spero vi sia piaciuto. Il mese prossimo ci addentreremo tra i tesori dei dialog: vedremo come deve essere fatta la window procedure per i dialog.


[Pagina precedente] [Sommario] [Pagina successiva]