Lezione 5 – Timer e Window Procedure



By admin on settembre 12, 2010


Come potremmo, ci chiedevamo alla fine del capitolo precedente, ottenere una specie di "auto-ripetizione" dell'effetto dei nostri pushbutton, se l'utente tiene premuto su di uno di essi il tasto del mouse?
Studiando la documentazione dell’SDK di Windows, e specificamente i messaggi di notifica spediti dai bottoni, può sembrare che ci sia una possibilità.
Sono, infatti, documentati (sia pure con avvertimenti di non usarli!) due messaggi, BN_PUSHED e BN_UNPUSHED, che sembrano fare proprio al caso nostro; se i nostri bottoni ci spedissero BN_PUSHED quando sono premuti, e BN_UNPUSHED quando sono rilasciati, sarebbe infatti possibile realizzare una "auto-ripetizione".
Supponiamo che questi messaggi arrivassero; come si realizzerebbe, dunque, l'auto-ripetizione dei click?
Per questo scopo, potremmo usare uno dei timer che Windows mette a disposizione. Un timer è un meccanismo che permette di chiamare a ripetizione, alla frequenza richiesta, una nostra funzione di "call-back", che possiamo scrivere secondo il prototipo:

VOID CALLBACK ProceduraTimer(HWND hwnd,    // finestra associata
    UINT uMsg,     // sempre il codice WM_TIMER
    UINT idEvent,  // identificatore del timer
    DWORD dwTime   // "istante attuale" ("tempo di sistema")
);

 
Un timer si attiva chiamando:

SetTimer(hwnd, idEvent, cMillisec, ProceduraTimer);

 

dove cMillisec è il periodo, in millisecondi, che dovrà passare fra due successive chiamate alla ProceduraTimer da parte del sistema; il timer si disattiva chiamando:

KillTimer(hwnd, idEvent);

 

SetTimer e KillTimer tornano entrambe zero in caso di errore; la GetLastError darà allora il codice d'errore).
Ad esempio, quindi, noi potremmo: nella gestione del comando BN_PUSHED, attivare un timer sulla finestra del dialogo, con id pari all'id del bottone, e un periodo, ad esempio, di 200 mSec; nella gestione di BN_UNPUSHED, disattivare questo timer; nella procedura di gestione del timer, simulare il clic sul bottone -- per il nostro programmino, basterà chiamare la funzione di gestione dei clic, con tutti i dati opportuni: HWND del dialogo, e id del bottone, saranno fra i parametri della procedura di timer; da questi, possiamo ottenere anche l'HWND del bottone, con GetDlgItem, e infine chiamare:

OnDlgCommand(hwndDialogo, idBottone, hwndBottone, BN_CLICKED) 

 
Non è difficile inserire questi elementi nel nostro programmino, e invitiamo appunto il lettore a provare a farlo.
Purtroppo, essi non "agiranno": infatti (come si può confermare, ad esempio, ponendo una chiamata a MessageBox nel punto del nostro codice che, se mai ne arrivasse una, gestirebbe la notifica di BN_PUSHED; oppure, usando il prezioso strumentino "Spy++" che la Microsoft fornisce come parte dell'SDK che già più volte abbiamo nominato) questi messaggi di notifica non vengono in realtà mai generati dai nostri bottoni.
Sembra dunque che siamo bloccati, come fare, se i bottoni standard fanno quasi al caso nostro, ma mancano di una feature che ci serve, come, qui, quella di spedire questi messaggi di notifica? Dovremmo, forse, scriverci da zero la nostra "classe" di controlli, riproducendo tutte le utili caratteristiche già presenti nei bottoni, e aggiungendo quelle supplementari che ci servono e ad essi mancano?
Sarebbe un grosso spreco di energia riscrivere tanto codice già esistente; non c'è modo di riutilizzarlo, limitandoci, cioè, a scrivere il poco codice supplementare che serve a implementare quelle poche, piccole feature mancanti?
Eccome se c'è; anzi, Windows offre più di una valida alternativa per questa esigenza. Ci limitiamo, per ora, a illustrare la più semplice e flessibile, il subclassing di istanza (altre, pure utili, che possono avere vantaggi in certe situazioni ma ricalcano sostanzialmente idee simili, sono il subclassing di classe, e il superclassing, che per ora non illustreremo).
Abbiamo già accennato all'esistenza di "classi di finestre", e come tutte le funzionalità che abbiamo usato sinora poggino su classi standard, predefinite per noi da Windows: precisamente, le classi "dialogo", "static", e "bottone". Una classe di finestre ha varie caratteristiche, ma, fra esse, la più rilevante è la window procedure associata alla classe, una funzione analoga alla "dialog procedure" che già abbiamo visto per i dialoghi, di prototipo:

LRESULT CALLBACK WindowProc(HWND hwnd, // la finestra
  UINT uMsg,      // il messaggio e i suoi parametri:
  WPARAM wParam, LPARAM lParam);

 
A differenza della dialog procedure, la window procedure non può semplicemente tornare FALSE per dire "non ho gestito questo messaggio, pensaci tu"; il risultato che essa torna deve infatti dipendere dal messaggio ricevuto; può, però, esplicitamente delegare i messaggi che non ha finito di gestire ad una procedura di default, con:

return DefWindowProc(hwnd, uMsg, wParam, lParam);

 

la DefWindowProc è un'API di Windows, studiata proprio a questo scopo, che esegue una "elaborazione minima possibile" su ogni tipo di messaggio.
Alternativamente, se la nostra window procedure ha a propria disposizione un puntatore a un'altra window procedure (cioè una variabile, diciamo wp, di tipo WINPROC), può delegare ad essa invece che alla DefWindowProc; questo tipo di delega da una window procedure ad un'altra si fa, però, non chiamando direttamente l’altra procedura, bensì usando un'API apposita:

return CallWindowProc(wp, hwnd, uMsg, wParam, lParam);

 

Dovrebbe già essere abbastanza chiaro come questo concetto ci permetta di "scrivere solo il codice supplementare" per caratteristiche da aggiungere o modificare: se ho il puntatore a una window procedure esistente, che faccia "quasi" già quel che desidero, basterà infatti che la mia window procedure intercetti i soli messaggi d'interesse, delegando all'altra, con CallWindowProc, per tutti quelli che ignora, o, comunque, che non gestisce interamente.
Resta da capire come ottenere questo "puntatore a window procedure esistente" e, naturalmente, come far sì che la mia window procedure specializzata vada in esecuzione, per una data finestra, al posto della sua window procedure originale.
L'API di Windows permette di accedere a vari dati relativi a una finestra attraverso una singola funzione:

LONG GetWindowLong(HWND hWnd,  // la finestra
        int nIndex  // codice del valore che interessa
);

 

I valori così accessibili sono parecchi, e, in particolare, usando come codice GWL_WNDPROC, si ottiene appunto il puntatore alla window procedure della finestra (bisogna, naturalmente, eseguire cast del LONG ritornato a WNDPROC, per usarlo).
Inoltre, si possono anche modificare questi dati per una data finestra, ottenendo come effetto collaterale il valore che il dato aveva prima della modifica:

LONG SetWindowLong(HWND hWnd,       // la finestra
        int nIndex,      // codice del dato che interessa
        LONG dwNewLong   // nuovo valore per il dato
);

 

Quando SetWindowLong è usata per impostare una window procedure che delega in parte a quella che era impostata precedentemente sulla finestra, si parla di subclassing per la finestra così modificata.
Ecco dunque come possiamo "arricchire" il comportamento dei pulsanti per ottenere da essi notifiche BN_PUSHED e BN_UNPUSHED come desideriamo, per semplicità, consideriamo di avere una variabile globale wpBase, di tipo WNDPROC, inizializzata a zero; scriviamo una nuova window procedure, che chiamiamo ad esempio WpSuper, che tiene conto delle pressioni e dei rilasci dei pulsanti ma delega poi comunque la gestione di ogni evento a wpBase (anche pressioni e rilasci non vengono gestiti interamente da WpSuper: la wpBase va comunque informata di questi eventi, poich´ provvede a ridisegnare il pulsante in stato premuto o non-premuto, e a generare la notifica di click); nella inizializzazione del nostro dialogo, ad esempio nelle prime righe della procedura OnDlgInitDialog, aggiungiamo la "istallazione" del subclassing:

wpBase = (WNDPROC)SetWindowLong(GetDlgItem(hwnd,IDC_BUTTON1),
         GWL_WNDPROC,(LONG)WpSuper);
SetWindowLong(GetDlgItem(hwnd,IDC_BUTTON2), GWL_WNDPROC,(LONG)WpSuper);
SetWindowLong(GetDlgItem(hwnd,IDC_BUTTON3), GWL_WNDPROC,(LONG)WpSuper);
SetWindowLong(GetDlgItem(hwnd,IDC_BUTTON4), GWL_WNDPROC,(LONG)WpSuper);
 

Qui registriamo il "valore precedente" solo all'atto del primo subclassing, perch´ sappiamo che le window procedure predefinite per gli altri bottoni debbono essere eguali a quella predefinita per uno qualsiasi di essi.

La WpSuper è facile da scrivere sapendo quali sono i messaggi che arrivano al bottone alla pressione ed al rilascio del mouse su di esso: sono rispettivamente WM_LBUTTONDOWN e WM_LBUTTONUP (per il pulsante principale, cioè "di sinistra", del mouse, che è quello che ci interessa usare):

LRESULT WpSuper(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
 UINT code = 0;
 switch(uMsg)
  {
   case WM_LBUTTONDOWN: code = BN_PUSHED; break;
   case WM_LBUTTONUP: code = BN_UNPUSHED; break;
  }
 if(code) SendMessage(GetParent(hwnd), WM_COMMAND,
            MAKELONG(GetDlgCtrlID(hwnd),code), (LPARAM)hwnd);
 if(wpBase) return CallWindowProc(wpBase,hwnd,uMsg,wParam,lParam);
 return 0;
}

 

La macro MAKELONG (sempre dagli header di Windows) compone una longword (ULONG) da due parole di 16 bit l'una, prendendo come primo argomento la "meno significativa", cioè quella da inserire nella LOWORD del risultato.
La precauzione di controllare che wpBase non sia nullo, prima di delegargli l'esecuzione, può non essere strettamente necessaria, ma è in generale un buon stile; in alternativa, si può usare un'asserzione assert(wpBase) all'ingresso in WpSuper, poichè essa non dovrebbe mai trovarsi ad eseguire senza che wpBase sia già stata impostata.
Con quest'ultima modifica, il nostro programma, finalmente, fa proprio il lavoro che desideravamo. C'è ancora un singolo, grave difetto, un difetto strutturale: è proprio questa variabile globale wpBase, che abbiamo appena inserito "per comodità"... le variabili globali sono una iattura, e Windows ci dà tutti gli strumenti necessari per farne quasi totalmente a meno. Nel prossimo paragrafo, riproporremo l'intero programma, nel "giusto ordine", e, in particolare, risolveremo questo problema.

 

Esempio rivisitato

// resource.h: definizioni degli identificatori
// delle risorse del nostro programma
#ifndef _RESOURCE_H
#define _RESOURCE_H

// dialoghi
#define IDD_DIALOG1     101

// controlli
#define IDC_BUTTON1    1000
#define IDC_BUTTON2    1001
#define IDC_BUTTON3    1002
#define IDC_BUTTON4    1003
#define IDC_STICON     1004
#define IDC_LIMITS     1005

#endif

File di risorse, esem1.rc:

// esem1.rc: risorse (dialogo e suoi controlli) per il programma di

#include 
#include "resource.h"

IDD_DIALOG1 DIALOG 100, 100, 180, 180
    STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
    CAPTION "Grafica a go-go!"
    FONT 8, "MS Sans Serif"
BEGIN
    AUTOCHECKBOX "Movimento limitato?",IDC_LIMITS,0,0,70,15,WS_CLIPSIBLINGS
    PUSHBUTTON "1",IDC_BUTTON1,140,20,20,20,BS_BITMAP|WS_CLIPSIBLINGS
    PUSHBUTTON "2",IDC_BUTTON2,160,20,20,20,BS_BITMAP|WS_CLIPSIBLINGS
    PUSHBUTTON "3",IDC_BUTTON3,150,0,20,20,BS_BITMAP|WS_CLIPSIBLINGS
    PUSHBUTTON "4",IDC_BUTTON4,150,40,20,20,BS_BITMAP|WS_CLIPSIBLINGS
    ICON       "",IDC_STICON,80,80,20,20,WS_CLIPSIBLINGS
END

 
Veniamo al codice C vero e proprio. Qui, l'approccio fondamentale deve chiaramente essere:

  • identificare le funzionalità riusabili, e impacchettarle separatamente.
  • tenere invece in file specifici quelle funzioni che sono inestricabilmente legate a una singola applicazione.

Il codice principale (WinMain) è abbastanza riusabile, almeno fra tutti i programmi Windows che non fanno altro che mostrare un dialogo e gestire l'interazione con esso dell'utente; per semplicità, supporremo qui che l'ID del dialogo sia sempre IDD_DIALOG1, ma potrebbe essere interessante anche "impacchettare" il dialogo (comprese le sue risorse) in una DLL, avere un WinMain che riceve il nome di quella DLL in linea comandi, la carica, eccetera. Comunque, per ora, ecco il file sorgente del nostro WinMain, esem1.c:

#define STRICT
#define WIN32_LEAN_AND_MEAN
#include 
#include "resource.h"

#include "esemdlg.h"

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine, int nCmdShow)
{
 DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0);
 return 0;
}

 
Il riuso sarà possibile, se non altro, con "copia e incolla"; abbiamo comunque isolato la dichiarazione della dialog procedure nel file di header esemdlg.h:

// generica dialog-procedure
BOOL CALLBACK DialogProc(HWND hwndDlg,UINT uMsg, WPARAM wParam, LPARAM lParam);

 

giusto per seguire il normale uso volto a garantire l'identità di prototipo fra dichiarazione e definizione di una funzione (includeremo, naturalmente, questo stesso file di header anche nel file sorgente che implementa questa dialog procedure).
Alcune delle funzioni che abbiamo già scritto sono chiaramente riusabili, e indipendenti fra di loro, e le impacchetteremo dunque ciascuna nel proprio file sorgente, con un file di header per ciascuna, che dichiara la funzione.

// dlgclose.h
// generica chiusura di dialogo modale
void OnDlgClose(HWND hWnd);
 
// dlgclose.c
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include 

#include "dlgclose.h"

// generica chiusura di dialogo modale
void OnDlgClose(HWND hWnd)
{
 EndDialog(hWnd,0);
}

 
Funzionalità banale, certo, ma, avendola messa nella nostra "libreria", non dovremo comunque più riscriverla! Forse un pochino meno banale è al seguente:

// windmove.h
// spostamento relativo, senza deformazioni, di una finestra; facoltativamente,
// la mantiene entro un certo rettangolo (se pRectLimite==0, ciò non si applica)
BOOL WindMove(HWND hwnd, int dx, int dy, LPRECT pRectLimite);

// windmove.c
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include 

#include "windmove.h"

BOOL WindMove(HWND hwnd, int dx, int dy, LPRECT pRectLimite)
{
 // scopre la posizione attuale
 WINDOWPLACEMENT wp;
 ZeroMemory(&wp, sizeof(wp));
 wp.length = sizeof(wp);
 if(!GetWindowPlacement(hwnd, &wp)) return FALSE;

 // applica lo spostamento relativo
 wp.rcNormalPosition.left   += dx;
 wp.rcNormalPosition.right  += dx;
 wp.rcNormalPosition.top    += dy;
 wp.rcNormalPosition.bottom += dy;

 if(pRectLimite)
   {
    // c'e' un rettangolo-limite, verifichiamolo:
    int dd = wp.rcNormalPosition.left - pRectLimite->left;
    if(dd<0)
      {
       // la finestra sporge a sinistra
       wp.rcNormalPosition.left -= dd;
        wp.rcNormalPosition.right -= dd;
       }
     else
       {
        dd = wp.rcNormalPosition.right - pRectLimite->right;
        if(dd>0)
          {
           // la finestra sporge a destra
           wp.rcNormalPosition.left -= dd;
           wp.rcNormalPosition.right -= dd;
          }
       }
     dd = wp.rcNormalPosition.top - pRectLimite->top;
     if(dd<0)
       {
        // la finestra sporge "dall'alto"
        wp.rcNormalPosition.top -= dd;
        wp.rcNormalPosition.bottom -= dd;
       }
     else
       {
        // la finestra sporge "dal basso"
        dd = wp.rcNormalPosition.bottom - pRectLimite->bottom;
        if(dd>0)
          {
           wp.rcNormalPosition.top -= dd;
           wp.rcNormalPosition.bottom -= dd;
          }
       }
    }
 // stabilisce la nuova posizione della finestra
 return SetWindowPlacement(hwnd, &wp);
} 

Codice chiaramente riusabile è anche quello che abbiamo usato per associare bitmap ai pulsanti, e l’icona allo static. Altrettanto chiaramente, entrambi i tipi di immagini e di controlli ci possono servire, quindi ci ritroviamo con quattro combinazioni:

// ctlimmag.h
void Mostra_Icona_static(const char* ic_cod, HWND hDlg, int sta_cod);
void Mostra_Icona_bottone(const char* ic_cod, HWND hDlg, int sta_cod);
void Mostra_BM_static(int bm_cod, HWND hDlg, int but_cod);
void Mostra_BM_bottone(int bm_cod, HWND hDlg, int but_cod);

// ctlimmag.c
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include 

#include "ctlimmag.h"

void Mostra_Icona_static(const char* ic_cod, HWND hDlg, int sta_cod)
{
 HWND hSta = GetDlgItem(hDlg, sta_cod);
 HICON hi = LoadIcon(0,ic_cod);

 SendMessage(hSta,STM_SETICON,(WPARAM)hi,0);
}

void Mostra_Icona_bottone(const char* ic_cod, HWND hDlg, int but_cod)
{
 HWND hBut = GetDlgItem(hDlg, but_cod);
 HICON hi = LoadIcon(0,ic_cod);

 SendMessage(hBut,BM_SETIMAGE,IMAGE_ICON,(LPARAM)hi);
}

void Mostra_BM_static(int bm_cod, HWND hDlg, int sta_cod)
{
 HWND hSta = GetDlgItem(hDlg, sta_cod);
 HBITMAP hi = LoadBitmap(0,MAKEINTRESOURCE(bm_cod));

 SendMessage(hSta,STM_SETIMAGE,IMAGE_BITMAP,(LPARAM)hi);
}

void Mostra_BM_bottone(int bm_cod, HWND hDlg, int but_cod)
{
 HWND hBut = GetDlgItem(hDlg, but_cod);
 HBITMAP hi = LoadBitmap(0,MAKEINTRESOURCE(bm_cod));

 SendMessage(hBut,BM_SETIMAGE,IMAGE_BITMAP,(LPARAM)hi);
}

 
Ci restano da vedere il codice vero e proprio del dialogo, e la piccola (ma, importante!) questione del subclassing senza uso di variabili globali.
Eccoci, dunque, al punto di prendere una decisione strutturale importante per il progetto: come incapsulare il "subclassing" che abbiamo effettuato? C'è modo di renderlo più usabile, più generale, riusabile in una qualche misura? Altra considerazione importante: possiamo farlo senza usare variabili globali, con tutti i problemi che si portano dietro? Vediamo anzitutto quest'ultimo aspetto. All'arrivo di un messaggio su di un controllo che abbiamo “subclassato”, andrà in esecuzione la nostra window procedure; questa ha bisogno di trovare dei dati dove, se non in variabili globali? Noi non controlliamo quali argomenti vengono passati alla nostra window procedure a fronte di ogni dato messaggio. Ma è vero anche che, in ciascun caso, uno di questi argomenti altro non è che l'HWND della finestra che abbiamo subclassato; se avessimo modo di, diciamo, "associare" tutti i dati per noi rilevanti a questa HWND, la soluzione sarebbe limpida: ogni finestra subclassata si porterebbe dietro l'"associazione" a tutti i dati che sono necessari alla window procedure di subclassing: problema risolto.
Abbiamo già visto che Windows ci offre le API SetWindowLong e GetWindowLong, con le quali si può accedere a tanti dati associati a ogni HWND, e uno dei valori definiti per l'argomento "indice" ha il promettente nome di GWL_USERDATA, "dati dell'utente", non potremmo forse usare quella longword, per metterci i dati che ci fanno comodo? In generale, questa potrebbe essere la soluzione migliore; come vedremo, infatti, quando creeremo le nostre classi di finestre, potremo addirittura riservare più spazio (sempre accedibile con SetWindowLong ecc), rispetto alla singola longword normalmente disponibile, proprio per facilitare questo uso. La soluzione, però, non sarebbe affidabile e generale per il caso specifico del subclassing. Infatti, per finestre subclassate, non possiamo "aumentare lo spazio" a disposizione e l'unica longword potrebbe essere già usata, per scopi suoi, dalla window procedure preesistente. Teniamo presente che la window procedure preesistente "non sa" che sarà subclassata; è responsabilità del "subclassatore", dunque, quella di mantenere per la wp preesistente l'"ambiente", la "normalità", sulla quale essa può fare conto, compreso il suo uso privato e personale del GWL_USERDATA. Non possiamo, quindi, chiaramente, usare impunemente noi stessi quella stessa longword.
L'API di Windows prevede un meccanismo apposito per questi casi; un meccanismo che esegue la associazione di dati arbitrari con una HWND senza "toccare" la struttura dell'HWND stessa: ideale, dunque, per il subclassing! Si tratta del meccanismo delle "Window Properties". Le API fondamentali di questo meccanismo sono:

BOOL SetProp( HWND hWnd, // la finestra
    LPCTSTR lpString,    // nome da dare alla proprietà
    HANDLE hData         // valore da dare alla proprietà
); 

che crea, o modifica, una proprietà con un certo nome da noi scelto, associandola alla generica "handle" che le passiamo (è specificamente documentato che al posto di una handle si possono usare 32 bit qualsiasi come hData, basta un opportuno cast);

HANDLE GetProp( HWND hWnd,   // la finestra
    LPCTSTR lpString         // nome della proprietà
);

 

che torna il valore della proprietà richiesta (0 se non esiste -- attenzione, dunque, che una proprietà settata a zero è difficile da distinguere da una non esistente);

HANDLE RemoveProp( HWND hWnd, // la finestra
    LPCTSTR lpString          // nome della proprietà
);
 

che opera come GetProp, ma, inoltre, rimuove la proprietà (per pulizia, un programma dovrebbe togliere tutte le proprietà dalle finestre cui ne ha assegnate, prima che esse siano distrutte).

Esiste inoltre una ulteriore API, EnumPropsEx, che permette di esaminare tutte le proprietà associate a una data finestra, ma qui essa non ci servirà.

Il meccanismo delle Window Properties è un po’ più oneroso, in termini di spazio e tempo di esecuzione, rispetto a quello di SetWindowLong, ma è anche molto più flessibile, e normalmente le sue prestazioni sono comunque del tutto accettabili.

Useremo dunque questo schema per associare alle nostre finestre subclassate i dati che servono, come, ad esempio, l'indirizzo della window-procedure preesistente, cui delegare i messaggi non di specifico interesse.
Quello che abbiamo già scritto è codice con un compito abbastanza preciso: far sì che un bottone mandi certe "notifiche" (WM_COMMAND, con i codici BN_PUSHED, ecc.) alla finestra di dialogo che lo contiene, a fronte di certi eventi (up e down del bottone sinistro del mouse, nel nostro caso).
Ecco come potremmo impostare l'implementazione del nostro subclassing:

 
// aggnot.h

// aggiunge a un controllo la capacità di inviare notifiche, via WM_COMMAND,
// alla finestra di dialogo che lo contiene, sulla base di eventi Windows
// WM_xxx ricevuti dal controllo, la codifica della corrispondenza
// evento→notifica è nell'array pCodici, che agli indici pari ha codici WM_xxx,
// a quelli dispari subito dopo il codice di notifica da usare, terminato 
// logicamente da uno 0 a indice pari; l'array può venire modificato nel corso
// dell'esecuzione, per cambiare il comportamento del controllo.

void Aggiungi_Notifiche(HWND hwndDialogo, int idControllo, long* pCodici);

// aggnot.c
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include 

#include "aggnot.h"

// la window procedure di superclassing
static LRESULT CALLBACK
WpSuper(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
 // recupera i codici di corrispondenza messaggio->notifica
 long* pCodici = (long*)GetProp(hwnd,"AN_DT");
 if(pCodici)
   {
    // scandisce l'array sino a una corrispondenza, o
    // sino allo 0 di terminazione
    int i=0;
     while(pCodici[i])
      {
       if(pCodici[i] == (long)uMsg)
         {
          // trovata la corrispondenza!
          SendMessage(GetParent(hwnd), WM_COMMAND, MAKELONG(GetDlgCtrlID(hwnd),
                      pCodici[i+1]), (LPARAM)hwnd);
          break;
         }
       i += 2;
      }
    }
  // recupera la window procedure che ha subclassato
  WNDPROC wp = (WNDPROC)GetProp(hwnd,"AN_WP");
  if(uMsg == WM_DESTROY)
    {
     // alla distruzione, deve fare pulizia
     RemoveProp(hwnd, "AN_WP");
     RemoveProp(hwnd, "AN_DT");
    }
  if(wp) return CallWindowProc(wp, hwnd, uMsg, wParam, lParam);
  else return DefWindowProc(hwnd, uMsg, wParam, lParam); 
}

// istalla la procedura di subclassing e le due proprietà AN_WP
// (precedente window procedure) e AN_DT (puntatore ai codici)

void Aggiungi_Notifiche(HWND hwndDialogo, int idControllo, long* pCodici)
{
 HWND hwnd = GetDlgItem(hwndDialogo,idControllo);
 WNDPROC wp = (WNDPROC)SetWindowLong(hwnd,GWL_WNDPROC,(LONG)WpSuper);
 SetProp(hwnd,"AN_WP",(HANDLE)wp);
 SetProp(hwnd,"AN_DT",(HANDLE)pCodici);
}

 
Facciamo notare, che, come già detto, questo codice è del tutto insoddisfacente dallo specifico punto di vista del controllo degli errori. Il problema generale, e importantissimo, della gestione degli errori, ci potrebbe però qui portare un po' troppo lontani dallo scopo primario di imparare a usare le API di Windows.





Lascia un Commento