Introduzione alle API Win32

Capitolo 34: il grafico: ultimi dettagli

Il "programma principale" e la dialog procedure del nostro esempietto di grafico di una funzione non presentano, naturalmente, molte novità; dopo i soliti define e include di sistema, le solite...:

// chiusura di un generico dialogo modale void OnDlgClose(HWND hWnd) { EndDialog(hWnd,0); } extern BOOL OnDlgInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam); extern void OnDlgCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify); extern void OnDlgDrawItem(HWND hwnd, const DRAWITEMSTRUCT *lpDrawItem); BOOL CALLBACK DialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch(uMsg) { HANDLE_MSG(hwndDlg,WM_CLOSE,OnDlgClose); HANDLE_MSG(hwndDlg,WM_INITDIALOG,OnDlgInitDialog); HANDLE_MSG(hwndDlg,WM_COMMAND,OnDlgCommand); HANDLE_MSG(hwndDlg,WM_DRAWITEM,OnDlgDrawItem); } return FALSE; } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_INDIES_DIALOG), 0, DialogProc, 0); return 0; }

Attenzione, però, agli utenti dell'ottimo e gratuito MinGw32: il cracker per WM_DRAWITEM non è stato riportato, chissa perchè!, negli header file per questo compilatore; in effetti, essi includono solo una piccola parte di windowsx.h. Gli utenti di questo compilatore, che vogliano usare i message cracker, dovranno dunque procurarsi anche il Platform SDK dal sito Microsoft (download gratuito e comunque consigliabile, poichè comprende vari strumenti e documentazioni interessanti!), e fare un poco di taglia-e-cuci da windowsx.h per ricavarne i cracker di proprio interesse.

 

L'inizializzazione del dialogo deve essere condotta con molta cura, poichè la procedura di validazione che già abbiamo visto dipende in modo cruciale dal fatto che non vi siano mai due controlli di edit che, allo stesso tempo, siano in stato "non valido" rispetto alla propria rispettiva sintassi. Ecco, ad esempio, un possibile approccio:

extern stru* nuova_stru(); BOOL SetDlgItemDouble(HWND h, int id, double d) { char buf[40]; sprintf(buf, "%g", d); return SetDlgItemText(h, id, buf); } BOOL OnDlgInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) { stru* ps = nuova_stru(); if(ps) { SetWindowLong(hwnd, DWL_USER, (LONG)ps); SetDlgItemInt(hwnd, IDC_PASSI, ps->nPunti, FALSE); SetDlgItemDouble(hwnd, IDC_MINX, ps->minx); SetDlgItemDouble(hwnd, IDC_MAXX, ps->maxx); SetDlgItemDouble(hwnd, IDC_MINY, ps->miny); SetDlgItemDouble(hwnd, IDC_MAXY, ps->maxy); SetDlgItemText(hwnd, IDC_STATUS, ps->status); SetDlgItemText(hwnd, IDC_FUNZIONE, ps->funzione); } return TRUE; }

Qui, tutto si appoggia sulla struttura stru, cui già abbiamo accennato, e su di una funzione nuova_stru in grado di "generare" in qualche modo un oggetto di questo tipo e tornarci un puntatore ad esso. In realtà, nell'implementarla, possiamo, almeno per il momento, "barare", poichè sappiamo che non ci sarà mai più di una stru alla volta ``in giro'' -- quindi, almeno come prototipo, si può scrivere qualcosa del tipo...:

static BOOL stru_prepara(stru* pS, const RECT pR); static stru ss; static char* svuot = ""; stru* nuova_stru() { if(!IndiEs_Init(0,0)) return 0; memset(&ss, 0, sizeof(ss)); ss.minx = ss.miny = 0.0; ss.maxx = ss.maxy = 1.0; ss.status = svuot; ss.funzione = svuot; ss.nPunti = 40; ss.sfondo = RGB(200,120,40); ss.prepara = prepara; return &ss; }

Abbiamo quasi tutti i frammenti... manca solo, a parte la "organizzazione e ricucitura" del codice, che lasciamo all'iniziativa del lettore, la gestione del clic sulla checkbox (automatica) IDC_AUTOY, che abbiamo più volte menzionato. Supponiamo, naturalmente, di delegare anche questa ad una funzione apposita, sempre per tenere il nostro codice diviso e modularizzato in modo ragionevole ed appropriato. Cosa farà questa funzione...?

Anzitutto, dovrà controllare se il check su IDC_AUTOY è stato messo, o tolto. Nel primo caso, l'utente chiede di calcolare automaticamente il minimo e il massimo per Y; bisognerà dunque rendere read-only i relativi controlli di edit, e causare un ridisegno del grafico -- la funzione di preparazione, chiamata per il ridisegno, porterà un bit di stato "calcolo automatico di minx/maxx"; in questo caso, una tantum, potrà essere appropriato ottenere subito il ri-calcolo con una chiamata di UpdateWindow, per potere aggiornare il valore dei campi in questione -- alternativamente, il compito di questo aggiornamento potrà essere delegato (sotto verifica del bit di stato in questione) a punti più appropriati del codice, come il gestore di WM_DRAWITEM, visto che questi campi andranno normalmente riscritti, se soggetti ad aggiornamento automatico, sostanzialmente tutte e sole le volte che viene ridisegnato il grafico (cambiamento di questo bit di stato, della funzione, del minimo o massimo di X, e relativo clic dell'utente sul bottone di "Disegna").

Il caso in cui la "spuntatura" ("check") è stata tolta ad IDC_AUTOY è ancora più semplice: basterà, in questo caso, rimuovere lo stato di read-only dagli edit di min-y e max-y -- non occorre cambiare il testo che contengono, e neppure fare nessun ri-disegno.

L'impostazione di un edit control a read-only, e il reset di esso a read-write, si ottengono spedendogli il messaggio EM_SETREADONLY, con lParam a 0 e wParam al valore BOOL che si vuole dare all'attributo di read-only; in alternativa, la solita macro di windowsx.h, qui:

Edit_SetReadOnly(hwndCtl, fReadOnly) permette di svolgere l'identico compito in modo forse più chiaro e leggibile.

Lo stato di "checked" di un bottone (checkbox o radio), ricordiamo, si può richiedere in vari modi, fra cui il più semplice è forse l'API IsDlgButtonChecked (che ha come suoi parametri l'handle del dialogo e l'ID del bottone, e torna come risultato lo stato del check su quel bottone).

 

Ecco, dunque, tutti gli elementi occorrenti -- il lettore è certamente in grado di metterli insieme a formare il programma desiderato (e questo esercizio di "ricucitura" può essere un ottimo modo di verificare la propria effettiva comprensione dei vari punti da noi spiegati, che rischiano altrimenti di restare troppo "teorici" per il lettore; di conseguenza, raccomandiamo calorosamente di svolgere davvero l'esercizio stesso).

Resta, naturalmente, un problema sostanziale con il programma di "grafico di una funzione" che così si otterrà: la povertà del nostro "linguaggino" per la definizione delle funzioni il cui grafico siamo in grado di disegnare! I dettagli di questo linguaggio sono incapsulati dall'interfaccia IndiEs, e la sua sintassi e semantica potrebbero certo essere arricchite senza eccessiva fatica (esercizio per il lettore interessato: migliorare l'analizzatore lessicale perchè possa riconoscere generici "identificatori"; e aggiungere fra le funzioni unarie implementate dal programma quelle indicate dagli identificatori SIN, COS, TAN, ASIN, ACOS, ATAN).

Ma è mai possibile, piuttosto, che non ci sia modo di riutilizzare, al posto di una versione di IndiEs tutta scritta da noi, un qualche linguaggio interpretativo già esistente...?!

Certamente! Disponendo, ad esempio, dei sorgenti di un interprete, e in rete se ne trovano tanti, non è certo impossibile "cablare" quell'interprete nel nostro stesso programma; con una progettazione accurata delle interfacce, può essere possibile anche un riutilizzo "binario", ad esempio di un interprete che sia fornito come DLL (posso, ad esempio, raccomandare nel modo più incondizionato il linguaggio Python, davvero splendido, per questo tipo di usi -- e anche per quasi qualsiasi altro uso, in realtà!-).

Tuttavia, c'è anche un approccio specifico di Windows al riutilizzo di componenti di ogni tipo, che si applica, fra i mille esempi, anche, in particolare, ai linguaggi interpretati (o "di scripting", come usa spesso dire); ed è un approccio che merita sicuramente di essere preso in considerazione.

Il seguito, dunque, alla prossima puntata...!


Capitolo 33: i parametri del grafico
Capitolo 35: il COM, visto da C (1)
Elenco dei capitoli