Sviluppo

Come faccio? - parte V

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 2

Benvenuti a questa nuova lezione sulla programmazione Presentation Manager. In questi articoli saranno discussi semplici problemi di programmazione del PM. Questi articoli sono indirizzati a persone interessate alla programmazione PM o semplicemente curiose di sapere come funzionano i programmi PM; per comprendere questi articoli, è utile un po' di esperienza di programmazione (preferibilmente in C++).

Come promesso, questo mese termineremo la discussione sui menu. L'articolo del mese scorso e questo possono essere visti, sotto questo aspetto, come uno solo. Il mese scorso abbiamo discusso le cose più semplici relative ai menu, tutto ciò di cui abbiamo parlato poteva essere fatto in un editor di risorse o scrivendo un semplice file di risorse (RC). Questo mese ci concentreremo sugli aspetti più pesanti, per ottenere i quali è richiesta un po' di programmazione vera e propria. Il programma di esempio presentato questa volta è essenzialmente un'estensione del programma del mese scorso.

Ci concentreremo su:

  • Voci di menu con spunta (Checked)
  • Sottomenu condizionali
  • Disabilitare/abilitare le voci di menu
Ricordate, stiamo lavorando in un ambiente che è governato da eventi. Perciò la prima cosa che dobbiamo conoscere è a quale evento dobbiamo rispondere per ottenere l'effetto desiderato.

Non è molto comodo cambiare i menu ogni volta che un'azione ne richiede una modifica. Ad esempio, per copiare un testo bisogna attivare un'opzione di menu. Questo vorrebbe dire che ogni volta che viene effettuata una selezione la voce "Copia" (all'interno di un sottomenu non visibile) verrà attivata. Ogni volta che il testo viene deselezionato, la voce di menu deve essere disabilitata. Questo non è molto efficiente perché noi vogliamo che tutto accada solo quando effettivamente serve. In altre parole, quando il menu è visibile.

Se ci pensate, tutte le azioni suddette si applicano solo a voci di sottomenu, che non sono sempre visibili. Se vogliamo modificare un sottomenu perché rifletta lo stato corrente, il momento ideale dovrebe essere subito prima che il menu venga visualizzato. In questo modo non abbiamo bisogno di reagire a ogni evento che ordina un cambiamento: cambiamo il menu solo prima che appaia.

E, ovviamente, OS/2 ha un messaggio perfetto per questo: il messaggio WM_INITMENU. Questo messaggio viene inviato alla vostra finestra principale ogni volta che un suo menu sta per diventare attivo. Una aspetto interessante di questo messaggio è che funziona anche per i menu pop-up, questo significa che possiamo gestire le voci pop-up come normali menu, sotto questo aspetto.

Il messaggio WM_INITMENU ha la seguente struttura:

param1   SHORT  smenuid     //  Identificatore del controllo "menu"
param2   HWND   hwnd        //  Handle della finestra del menu
returns  ULONG  ulReserved  //  Riservato, lasciare a 0
I parametri di questo messaggio sono proprio quello che ci serve: l'ID del sottomenu che sta diventando attivo e il suo handle, così possiamo interagire con esso. Con queste informazioni possiamo preparare il codice per gestire il messaggio WM_INITMENU. È molto simile alla gestione del messaggio WM_COMMAND; è una grande istruzione case nella quale viene selezionato il menu che si sta attivando. Questo porta alla seguente struttura:
WM_INITMENU
        {
        switch (SHORT1FROMMP(mpParm1))
                {
                case IDM_MENU-1:
                {
                // Azioni
                        }
                break;
                .
                .
                .
                case IDM_MENU-N:
                {
                // Azioni
                        }
                break;
                }
     }
break;
Dove è scritto "// Azioni" possiamo fare la nostra magia per cambiare il menu che sarà mostrato. Ad esempio, possiamo fare tutto ciò che è stato elencato all'inizio dell'articolo. Ricordate che ci si può spingere anche molto oltre: si possono anche aggiungere e rimuovere voci da un menu.

Ora che sappiamo dove mettere la nostra magia, gestiamo per prima cosa le voci con spunta(CHECKED). Per che cosa le usiamo? In molti casi voci con spunta sono usate per opzioni che alternano tra due stati(al posto di questo ci sta meglio:"che cambiano stato). Un esempio potrebbe essere attivare o disattivare il "ritorno a capo automatico" ("word wrap"). Quando è attivo, la corrispondente voce di menu ha una spunta, quando è disattivo no. Questo uso si vede spesso in menu in cui molte impostazioni possono essere attivate e disattivate. Ad esempio, date un occhiata al menu "View" del word processor di Works (nel BonusPak).

C'è anche un'altro uso per le voci con spunta. Essenzialmente è come mostrare lo stato di una voce, ma in questo caso ci sono più voci coinvolte. Si può mostrare quale voce in un elenco è attiva. Un esempio si può trovare nello stesso word processor (controllate il menu "Character"). In questo menu, voci come grassetto, sottolineato, ecc. possono essere scelte. Queste sono mutuamente esclusive (solo una alla volta può essere attiva) e la voce attiva ha una spunta accanto; se si sceglie un'altra voce, la spunta sarà accanto a quest'ultima la prossima volta che si apre il menu.

Costruiremo un esempio di quest'ultima situazione. Se riusciamo a gestirla, il caso più semplice dovrebbe essere immediato.

Facciamo le cose con ordine: prima di poter inserire o eliminare la spunta a fianco di una voce di menu, tale voce deve essere definita CHECKED. Questo significa che dobbiamo aggiungere un attributo (Menu Item Attribute, MIA) alle voci cui vogliamo dare la possibilità di apparire con la spunta. Questo può essere fatto con la parola MIA_CHECKED. Se guardate nel file SAMPLE5.RC (nel file di esempio di questo mese) noterete che è stato aggiunto questo codice:

SUBMENU "~Checked", IDM_MENU
BEGIN
     MENUITEM "Checked ~1", IDM_CHECKED1, MIA_CHECKED
     MENUITEM "Checked ~2", IDM_CHECKED2, MIA_CHECKED
     MENUITEM "Checked ~3", IDM_CHECKED3, MIA_CHECKED
     MENUITEM "Checked ~4", IDM_CHECKED4, MIA_CHECKED
     MENUITEM "Checked ~5", IDM_CHECKED5, MIA_CHECKED
END
Sarebbe bello se questo codice da solo gestisse tutto ma sfortunatamente non è così. Un menu definito in questo modo appare come se non ci fosse l'attributo MIA_CHECKED. Per far apparire una spunta, bisogna attivarla. Questo si può fare in due modi: per la via più difficile, usando un messaggio MENU, o per la via più semplice, usando una macro predefinita. Ovviamente noi useremo la via più semplice (semplice e pigri, ricordate...). D'altra parte, è sempre utile sapere cosa si sta facendo, perciò daremo anche un'occhiata a come la macro viene espansa.

La macro che useremo è WinCheckMenuItem. Questa macro richiede tre parametri:

  • L'handle del menu che si sta attivando
  • L'ID della voce da impostare
  • Lo stato della spunta (vero/falso)
Abbiamo già tutto ciò. L'handle è mp2 da WM_INITMENU, l'ID lo conosciamo già. Quando viene usata, la macro si espande così:
((BOOL)WinSendMsg(hwndMenu,
                  MM_SETITEMATTR,
                  MPFROM2SHORT(usId, TRUE),
                  MPFROM2SHORT(MIA_CHECKED, (BOOL)(fCheck) ? MIA_CHECKED : 0)))
(QUESTO PARAGRAFO PER ME E' UN PO INCASINATO VEDI SE CAMBIARLO OPPURE NO)Ora arriviamo all'ultima parte della creazione della nostra voce con spunta. Volevamo creare un menu con una sola voce marcata. Questo vuol dire che devono essere mutuamente esclusive. Per gestire questa situazione creiamo uno SHORT globale che contiene il numero della voce che deve essere attivata. In questo modo sappiamo quale voce marcare e quali no. (Controllate il codice aggiunto a WM_COMMAND. Credo (spero) che non richieda altre spiegazioni).

Ora possiamo inserire il codice per gestire il menu IDM_CHECKEDMENU in WM_INITMENU. Questo porta al codice seguente:

case IDM_CHECKEDMENU:
        {
        WinCheckMenuItem((HWND)mp2, IDM_CHECKED1, usChecked==1);
        WinCheckMenuItem((HWND)mp2, IDM_CHECKED2, usChecked==2);
        WinCheckMenuItem((HWND)mp2, IDM_CHECKED3, usChecked==3);
        WinCheckMenuItem((HWND)mp2, IDM_CHECKED4, usChecked==4);
        WinCheckMenuItem((HWND)mp2, IDM_CHECKED5, usChecked==5);
        }
break;
Come ho detto, l'evento WM_INITMENU può essere anche usato per attivare/disattivare voci di menu. A che cosa ci serve? Quando una voce è inattiva, non può essere selezionata. Così se vogliamo impedire all'utente di selezionare una certa voce di menu, basta disattivarla. E (di nuovo) possiamo farlo nel modo semplice o nel modo difficile. Proprio come per impostare la spunta, possiamo usare una macro per gestire il lavoro sporco. Questa macro è WinEnableMenuItem. I parametri che servono sono identici a quelli di WinCheckMenuItem:
  • L'handle del menu che si sta attivando
  • L'ID della voce da impostare
  • Se la voce è attiva o no (vero/falso)
Di nuovo, come per WinCheckMenuItem, daremo un'occhiata a come questa macro si espande.
((BOOL)WinSendMsg(hwndMenu,
                   MM_SETITEMATTR,
                   MPFROM2SHORT(usId, TRUE),
                   MPFROM2SHORT(MIA_DISABLED, (BOOL)(fEnable) ? 0 : MIA_DISABLED)))
Ora facciamo un esempio. Definiamo, giusto per questo esempio, il seguente problema: se una voce di menu ha la spunta, questa voce deve essere disattivata. Useremo la macro WinEnableMenuItem per ottenere questo. Il seguente codice esegue il tutto:
WinEnableMenuItem((HWND)mp2, IDM_CHECKED1, usChecked==1);
WinEnableMenuItem((HWND)mp2, IDM_CHECKED2, usChecked==2);
WinEnableMenuItem((HWND)mp2, IDM_CHECKED3, usChecked==3);
WinEnableMenuItem((HWND)mp2, IDM_CHECKED4, usChecked==4);
WinEnableMenuItem((HWND)mp2, IDM_CHECKED5, usChecked==0);
Ci resta ancora una cosa da discutere. I sottomenu condizionali. Un sottomenu condizionale è un sottomenu dove la freccia è un piccolo pulsante. Se viene selezionato questo pulsante, il sottomenu diviene visibile. Se si seleziona le voce di menu, la prima voce del sottomenu invisibile (quella di default) viene selezionata. La voce di default può comunque essere cambiata.

Per fare ciò, si usa il messaggio MM_SETDEFAULTITEMID inoltre Con questo messaggio si crea una spunta accanto alla voce di default (controllate il sottomenu "Aprire" del menu della Scrivania per un esempio).

Normalmente si fa così. Ad ogni modo, posso pensare a situazioni in cui preferisco fare le cose in maniera diversa. Personalmente preferisco usare i sottomenu condizionali per scelte mutuamente esclusive. La voce di default (la prima) nel sottomenu viene usata per cambiare tra i possibili valori mentre la spunta indica la scelta attiva. Questo significa che non possiamo impostare un default perché questo creerebbe una spunta rendendo impossibile marcare solo la scelta corrente. Per un esempio di ciò, guardate come Smalled gestisce le impostazioni Wrap e Indent.

Quale delle due scelte usare dipende dalle situazioni e dai vostri gusti. Mostrerò soltanto come creare un sottomenu condizionale; la gestione del valore di default la lascio alla vostra fervida immaginazione.

Per creare un sottomenu condizionale bisogna applicare lo stile MS_CONDITIONALCASCADE al sottomenu da rendere condizionale. Sfortunatamente questo non si può fare direttamente nel file RC, dobbiamo cambiare lo stile manualmente. Per cambiare lo stile di una finestra (qualsiasi finestra) dobbiamo prima di tutto sapere come questo stile viene mantenuto all'interno della finestra.

Le informazioni su una finestra sono tenute nelle cosiddette "window words" (ne parlerò in qualche futuro articolo). Le window words sono una serie di bytes riservati per ogni finestra aperta; in questa zona di memoria sono immagazzinate molte informazioni utili, tra cui lo stile, l'ID, il puntatore alla window procedure, i flags, ecc.

Per adesso, ci interessa cambiare lo stile di una finestra. Il LONG in cui è contenuto lo stile può essere cambiato con diverse chiamate. Vogliamo solo cambiare alcuni bit in questa variabile e per farlo possiamo prima leggerla, cambiarla e infine riscriverla. Suona bene, ma c'è una via ancora più semplice. OS/2 ha una funzione che cambia uno specificato numero di bit in una specificata posizione nelle window words. Questa è la funzione WinSetWindowBits. Questa funzione prende i seguenti parametri:

HWND     hwnd;    //  Window handle.
LONG     index;   //  Indice (a partire da 0) della word da modificare.
ULONG    flData;  //  Bit da scrivere nella window word.
ULONG    flMask;  //  Indicatore dei bit da scrivere.
Nel nostro caso vuol dire che possiamo fare così:
WinSetWindowBits(hwndSubMenu, QWL_STYLE, MS_CONDITIONALCASCADE, MS_CONDITIONALCASCADE); 
Il parametro hwndSubMenu parla da sé. Però abbiamo un piccolo problema. Come otteniamo questo handle? Dobbiamo fare una piccola deviazione, c'è una funzione con la quale possiamo ottenere l'handle di un controllo sapendone l'ID e l'handle della parent window. Nel caso del menu principale ci serve l'handle della frame window. Per fortuna, abbiamo questo handle che è disponibile appena creata la frame window. Ma dove troviamo l'ID del menu principale? OS/2 assegna identificatori di default ai controlli standard della frame window (anche di questo parleremo in futuro). Per il menu principale è FID_MENU. Ciò significa che la linea seguente ritornerà l'handle del menu principale:
WinWindowFromID(hwndFrame, FID_MENU)
Possiamo inserirla dove ci serve l'handle del menu.

Il parametro successivo in WinSetWindowBits, QWL_STYLE, è una costante che indica la posizione dei bit di stile nelle window words. I due valori seguenti indicano cosa vogliamo scrivere nel LONG dello stile. Quindi sarebbe sufficiente chiamare questa funzione una sola volta per impostare il sottomenu condizionale. C'è però un trabocchetto in tutto ciò: non conosciamo l'handle del sottomenu.

Cosa facciamo adesso? Anche qui prendiamo la via semplice, chiediamo tutte le informazioni sul sottomenu. Per questo useremo il messaggio MM_QUERYITEM e lo invieremo alla voce collegata al sottomenu. Questa chiamata riempirà una struttura MENUITEM. Questa struttura contiene un handle al sottomenu et voilá, abbiamo tutto ciò che ci serve. Questo vuol dire che ci basta chiamare il codice seguente al momento dell'inizializzazione per impostare il sottomenu condizionale per tutta la durata della sessione.

MENUITEM mi;
WinSendMsg(WinWindowFromID(hwndFrame, FID_MENU),
           MM_QUERYITEM,
           MPFROM2SHORT(IDM_CASCMENU,TRUE),
           &mi);
WinSetWindowBits(mi.hwndSubMenu,
                 QWL_STYLE,
                 MS_CONDITIONALCASCADE,
                 MS_CONDITIONALCASCADE);
Ok, ora sappiamo come impostare il sottomenu condizionale, ma dove lo impostiamo? Il momento più semplice è subito dopo la creazione della frame window, prima che inizi il message loop (guardate l'esempio). Nel file di esempio di questo mese (ZIP, 17.8k) uno dei sottomenu è reso condizionale con questo codice.

Questo articolo è iniziato dicendo che serviva un po' di esperienza di programmazione. Se guardate l'esempio, vi accorgerete che non è poi troppa. Tutto il codice nuovo è marcato con // NEW.

Il prossimo mese inizieremo un argomento completamente nuovo. Arrivederci!

[Pagina precedente] [Sommario] [Pagina successiva]