Sviluppo

Come faccio? - parte VII

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 4

L'articolo del mese scorso ha prodotto un'interessante quantità di reazioni. Le idee mostrate in alcune di esse sono troppo buone per tenervele nascoste. Così ho deciso di analizzare più in profondità le "capacità non sfruttate" di OS/2 per salvare e ripristinare posizione, dimensioni e presentation parameters delle nostre finestre.

Vorrei discutere di tre argomenti che sono emersi dalla posta che ho ricevuto. La cosa bella è che molte lettere contenevano anche la soluzione per i problemi posti. Questo è un bene perché vuol dire che gli articoli si scrivono da soli.

1) Usare il file OS2.INI
2) Usare il flag FCF_SHELLPOSITION
3) Salvare la posizione delle finestre minimizzate

Usare il file OS2.INI

Salvare i PP, la posizione e le dimensione delle finestre nel file OS2.INI con un solo comando è molto attraente, ma ha alcuni risvolti negativi per alcuni utenti. OS2.INI è un database binario che può contenere ogni genere di informazioni usate dalle nostre applicazioni. Il mese scorso abbiamo visto che gli elementi dei file INI hanno una struttura a due livelli. Per ogni chiave del livello superiore (detta "applicazione") possiamo definire molte chiavi al livello inferiore con associato ogni tipo di dato. Questo modo di registrare informazioni è usato sia da OS2.INI che dai profili privati (ne parleremo tra poco).

OS2.INI ha la tendenza a crescere. Se le applicazioni vi aggiungono dati, ma non li rimuovono quando vengono disinstallate, si accumulerà un mucchio di roba inutile che rallenterà il sistema. Questa è la ragione principale per cui molti non vogliono che le applicazioni scrivano informazioni in OS2.INI.

Comunque, c'è anche un aspetto positivo! OS2.INI è più semplice da usare dei profili privati. È sempre aperto, per cui non è necessario uno specifico comando per aprirlo, e neppure per chiuderlo. A parte questo, possiamo usare le funzioni WinRestoreWindowPos e WinStoreWindowPos che funzionano specificamente per OS2.INI.

Poi preferisco usare OS2.INI anche per un'altra ragione: è veloce. È ben possibile che i dati che state cercando siano già in memoria. Poiché non dovete aprirlo, l'accesso al disco è minimo. Quindi, se avete bisogno di un'applicazione che si apra instantaneamente e che usi posizione, PP, e altre informazioni da un file INI, OS2.INI è il miglior candidato. Ovviamente, è anche possibile una soluzione ibrida. Salvate le informazioni time-critical in OS2.INI e il resto (ad esempio percorsi, ecc.) in un file INI privato.

Detto questo, è tempo di analizzare i profili oltre il livello del mese scorso, senza scendere troppo nei dettagli.

Per usare un file INI privato, dobbiamo aprirne o crearne uno. La cosa bella della funzione che gestisce questa azione è che il file verrà creato se non esiste. La chiamata da usare è PrfOpenProfile:

HAB   hab;         // Handle dell'anchor block
PSZ   pszFileName; // Nome del file profilo
HINI  hini;        // Handle di inizializzazione del file

hini = PrfOpenProfile(hab, pszFileName);
Il parametro hab non è molto interessante: è il valore restituito da WinInitialize, che abbiamo usato anche per creare la coda dei messaggi. Il parametro pszFileName, invece, è importante. Deve essere un nome di file con tutto il percorso (altrimenti viene usato il percorso corrente). È buona norma dare ai profili l'estensione INI, ma non è richiesto, quindi attenti a non sovrascrivere file esistenti. Ovviamente, il profilo non può chiamarsi OS2.INI o OS2SYS.INI.

Anche l'handle che viene ritornato, hini, è importante. È l'handle che ci servirà per ogni operazione sul profilo appena aperto (o creato). Si usa anche per chiudere il file. La chiamata di chiusura è molto semplice: PrfCloseProfile(hini). Salvare dati in un profilo privato è lo stesso che con OS2.INI.

Eccoci ora al punto cruciale di questa discussione: possiamo covnertire la funzione che salva le informazioni della finestra in OS2.INI in una funzione che usa un profilo privato? Questa idea è venuta a Mark Kimes (l'autore delle utility di FM/2), ed è semplice ma molto efficace. Siccome WinStoreWindowPos e WinStoreWindowPos funzionano solo con OS2.INI, basta usare questo file come un buffer temporaneo. Servono le seguenti azioni:

  • In risposta a WM_SAVEAPPLICATION si usa WinStoreWindowPos
    - si copiano le informazioni da OS2.INI in un profilo privato
    - si cancellano le informazioni da OS2.INI
  • Al riavvio dell'applicazione:
    - si copiano le informazioni dal profilo provato a OS2.INI
    - si usa WinRestoreWindowPos
    - si cancellano le informazioni da OS2.INI (non è in effetti necessario, visto che saranno cancellate all'uscita)
Semplice ed efficace. Nell'esempio di questo mese, le seguenti linee sono state aggiunte all'inizio del programma:
HINI    hini;   // Handle del profilo privato
ULONG   ulSize; // Dimensione dei dati da copiare
PVOID   pBuffer;// Buffer

hini = PrfOpenProfile(hab, "Sample7.INI"); // Apri il profilo privato
if (hini)
        {
        //----------------------------------------------------------------------
        // recupera le informazioni dal profilo in memoria
        //----------------------------------------------------------------------
        PrfQueryProfileSize(hini, APPNAME, WINPOS, &ulSize);
        DosAllocMem(&pBuffer, ulSize, PAG_READ| PAG_WRITE |PAG_COMMIT);
        PrfQueryProfileData(hini, APPNAME, WINPOS, pBuffer, &ulSize);
        PrfCloseProfile(hini);  // Close private profile
        //----------------------------------------------------------------------
        // scrivi le informazioni in OS2.INI
        //----------------------------------------------------------------------
        PrfWriteProfileData(HINI_USERPROFILE, APPNAME, WINPOS, pBuffer, ulSize);
        DosFreeMem(pBuffer);
     }
È tutto molto lineare. Per prima cosa, si apre il profilo. Se non è possibile, non c'è bisogno di proseguire. Se è stato aperto, chiediamo la dimensione dei dati. Ci serve per allocare il buffer in memoria e per scrivere su OS2.INI. I dati vengono quindi copiati dal profilo in memoria chiamando PrfQueryProfileData. Dopo ciò, il profilo viene chiuso e possiamo copiare i dati su OS2.INI (ricordate, quest'ultimo non deve essere chiuso. Mi occuperò dei dettagli della gestione della memoria qualche altra volta).

Il codice per il messaggio WM_SAVEAPPLICATION è un po' più complesso, ma in effetti non è altro che l'inverso di quanto visto sopra. Ho aggiunto le linee seguenti:

case WM_SAVEAPPLICATION:
        {
        HINI    hini;   // Handle del profilo privato
        ULONG   ulSize; // Dimensione dei dati da copiare
        PVOID   pBuffer;// Buffer
        //------------------------------------------------------------------
        // Salva informazioni in OS2.INI
        //------------------------------------------------------------------
        WinStoreWindowPos(APPNAME, WINPOS, WinQueryWindow(hwnd, QW_PARENT));
        //------------------------------------------------------------------
        // Copia le informazioni da OS2.INI in memoria
        // e le cancella dal file
        //------------------------------------------------------------------
        PrfQueryProfileSize(HINI_USERPROFILE, APPNAME, WINPOS, &ulSize);
        DosAllocMem(&pBuffer, ulSize, PAG_READ| PAG_WRITE |PAG_COMMIT);
        PrfQueryProfileData(HINI_USERPROFILE, APPNAME, WINPOS, pBuffer, &ulSize);
        PrfWriteProfileData(HINI_USERPROFILE, APPNAME, WINPOS, NULL, 0);
        //------------------------------------------------------------------
        // Copia le informazioni dal buffer al profilo privato
        // Chiude il profilo e libera la memoria
        //------------------------------------------------------------------
        hini = PrfOpenProfile(hab, "Sample7.INI"); // Open private profile
        PrfWriteProfileData(hini, APPNAME, WINPOS, pBuffer, ulSize);
        PrfCloseProfile(hini);  // Close private profile
        DosFreeMem(pBuffer);
        }
L'unica linea che richiede un po' di spiegazione è la chiamata a PrfWriteProfileData con un NULL. Con questa chiamata i dati relativi a APPNAME e WINPOS (in questo caso) vengono cancellati dal profilo. In questo modo teniamo pulito OS2.INI.

Arriviamo ora alla seconda miglioria: usare FCF_SHELLPOSTION per impostare posizione e dimensioni della finestra nel caso non ci siano informazioni nel profilo. Anche questa soluzione viene da Mark.

Nell'esempio del mese scorso, avevo proposto di inserire dimensioni e posizione di default per gestire il caso in cui non ci fossero dati nel profilo. Il PM è in grado di posizionare e dimensionare una finestra: basta usare il flag FCF_SHELLPOSITION durante la creazione della finestra principale. Il vantaggio di questo metodo è che il PM imposterà la finestra a seconda della risoluzione dello schermo, creando perciò un'alternativa migliore. Ad ogni modo, avevamo abbandonato questa possibilità il mese scorso per evitare una sfarfallio all'avvio. La finestra sarebbe apparsa nella posizione scelta dal PM per poi scomparire e riapparire secondo i dati salvati nel profilo.

La soluzione, usando FCF_SHELLPOSITION, è molto semplice. Create una variabile che contenga i vari FCF tranne FCF_SHELLPOSITION. Controllate nel file OS2.INI che le informazioni per la finestra esistano. Se non ci sono, aggiungete FCF_SHELLPOSITION. Il modo più semplice per controllare che i dati ci siano è di chiederne la dimensione. Se è zero, i dati evidentemente non ci sono. L'API di OS/2 ha una funzioni apposita per questo: PrfQueryProfileSize. Questa funzione ritorna la dimensione in byte del valore di una specifica chiave. Inoltre ritorna zero se le informazioni richieste non esistono. In quest'ultimo caso possiamo usare FCF_SHELLPOSITION. Il codice seguente esegue tutto ciò:

ULONG flFrameFlags = FCF_TITLEBAR |FCF_SYSMENU |FCF_MENU |
                     FCF_SIZEBORDER |FCF_MINMAX  |FCF_TASKLIST;
ULONG ulIniSize = 0;

if(!PrfQueryProfileSize(HINI_USERPROFILE, APPNAME, WINPOS, &ulIniSize) || !ulIniSize)
             flFrameFlags |= FCF_SHELLPOSITION;
Funziona, ma dobbiamo anche controllare che FCF_SHELLPOSITION non sia usato quando richiamiamo WinRestoreWindowPos. Per questo possiamo usare la variabile ulIniSize. Se è zero, è stato usato FCF_SHELLPOSITION. Quindi la chiamata a WinRestoreWindowPos può essere questa:
if (ulIniSize)
        WinRestoreWindowPos(APPNAME, WINPOS, hwndFrame);
Promesse, promesse. Be', eccoci all'ultimo punto: cosa facciamo con le finestre ridotte a icona? È molto fastidioso quando vengono salvate le informazioni di una finestra minimizzata. Questo può succedere se l'utente chiude l'applicazione dall'elenco finestre con la finestra ridotta. Il fatto fastidioso è che la finestra non sarà visibile all'avvio successivo. Può essere visualizzata usando l'elenco finestre, coi comandi "affianca" o "sovrapponi". Comunque, un utente con meno esperienza potrebbe pensare che l'applicazione non funzioni. È un ottima ragione per evitarlo.

Come possiamo gestire ciò? È abbastanza semplice: dobbiamo solo controllare se la finestra è ridotta quando riceviamo il messaggio WM_SAVEAPPLICATION. Se lo è, possiamo fare due cose: ripristinare la finestra e salvare le informazioni, o non salvare niente. Penso che chiunque possa cambiare il codice in modo da non salvare niente, quindi illustrerò l'altro caso.

Ma andiamo con ordine: come facciamo a sapere se una finestra è ridotta? Per questo dobbiamo tonare indietro a qualcosa che è stato trattato negli scorsi articoli: le window-words. Le window-words sono uno spazio allocato per ogni finestra, che contiene informazioni specifiche di quella finestra. Tra le altre cose, vi sono immagazzinati gli window styles. Probabilmente li conoscete come le costanti WS_* che usate per creare una finestra. Ad esempio, WS_ANIMATE, WS_CLIPSIBLINGS e, ovviamente, WS_MINIMIZED e WS_MAXIMIZED. Queste informazioni possono essere recuperate chiamando WinQueryWindowULong con QWL_STYLE come parametro indice. Questo ritornerà le informazioni sullo stile. Confrontando questo valore con WS_MINIMIZED potremo scrivere questo costrutto:

if (WinQueryWindowULong(hwndFrame, QWL_STYLE) & WS_MINIMIZED)
Serve un'ultima cosa: ripristinare la finestra. Per questo possiamo usare un'altra funzione giàl vista: WinSetWindowPos. Chiamandola con SWP_RESTORE come parametro, la funzione azzererà il flag WS_MINIMIZED nelle window-words. Potremmo farlo anche da soli con qualche trucco booleano, ma perché fare qualcosa che può esser fatto dal sistema? Ricordate la prima regola: non fate ciò che può esser fatto al posto vostro.

Tutto ciò ci porta al codice seguente:

if (WinQueryWindowULong(hwndFrame, QWL_STYLE) & WS_MINIMIZED)
                                WinSetWindowPos(hwndFrame, HWND_TOP, 0,0,0,0, SWP_RESTORE);
Bene, per questo mese basta così. Mi sono preso la libertà di utilizzare alcune proposte contenute nelle vostre lettere riarrangiandole in questo articolo. Per un esempio delle tecniche mostrate, guardate il programma d'esempio (ZIP, 23k) di questo mese. Grazie di tutte le risposte. Il mese prossimo ci occuperemo di qualcosa di nuovo; analizzeremo controlli diversi dalla frame window.


[Pagina precedente] [Sommario] [Pagina successiva]