Introduzione alle API Win32

Capitolo 45: scorrimento orizzontale

Abbiamo visto che, benchè fra gli stili del nostro controllo list-box avessimo specificato anche il bit WS_HSCROLL, la desiderata barra di scorrimento orizzontale non voleva saperne di apparire, anche quando inserivamo il lista delle stringhe molto lunghe. Perchè no...? E, come potremmo rimediare...?

La risposta alla prima delle due domande è abbastanza semplice. Il controllo listbox mantiene un suo valore di "ampiezza (massima) corrente", ma non lo aggiorna automaticamente; dobbiamo essere invece noi ad aggiornarlo, esplicitamente, inviando al controllo un messaggio LB_SETHORIZONTALEXTENT, con l'ampiezza in pixel in wParam.

Possiamo anche chiedere al controllo quale sia la sua ampiezza corrente, spedendogli un messaggio LB_GETHORIZONTALEXTENT, che ci restituirà l'ampiezza stessa come valore di ritorno. Se facciamo ad un listbox appunto questa richiesta, senza avere fatto aggiornamenti alla sua ampiezza con LB_SETHORIZONTALEXTENT, vedremo che, per default, la "ampiezza corrente" del controllo listbox è sempre 0; non c'è da stupirsi, dunque, se la barra di scorrimento orizzontale non viene visualizzata.

In linea di principio, anche la risposta alla seconda domanda non è poi tanto astrusa; in pseudo-codice, potremmo dire:

per aggiungere la stringa S al listbox L: scopri la larghezza in pixel, W, di S chiedi ad L la sua ampiezza, A se W > A: aggiorna a W l'ampiezza di L aggiungi S ad L ridisegna L L'ordine di esecuzione dei vari passi deve essere proprio questo: l'aggiornamento dell'ampiezza del controllo deve venire prima dell'aggiunta della stringa, e alla fine è necessario ridisegnare il controllo, per permettere alla barra di scorrimento orizzontale di apparire; su alcune piattaforme Windows possono funzionare anche altri ordinamenti di queste operazioni di base, ma questa è l'unica sequenza certa su tutte le diverse piattaforme Windows. Essa, d'altra parte, si "incastra" bene con la serie di operazioni che, lo abbiamo già visto al capitolo precedente, è la più pratica e semplice per praticare lo scorrimento verticale del controllo list-box in modo tale da garantire che la stringa appena aggiunta sia visibile in fondo al controllo stesso.

Questa sequenza potremo, naturalmente, incapsularla in una nostra funzione riusabile, o anche implementarla, come risposta ad un qualche opportuno messaggio, in una nostra versione subclassata (o "superclassata", una variante che esamineremo più avanti) del controllo standard list-box.

Resta un solo problemino sostanziale...: come realizzare la funzioncina che, in termini di pseudo-codice, abbiamo così semplicemente espresso come "scopri la larghezza in pixel della stringa"...?

La funzionalità necessaria per scoprire quanti pixel di larghezza richiederà una stringa è una funzione "grafica", che dipende da un device context (un "contesto di dispositivo", concetto che già abbiamo incontrato in precedenza nello sviluppo del nostro programmino per il grafico di una funzione) e dal font selezionato in quel contesto. Di conseguenza, per potere ottenere questa informazione dobbiamo prima ottenere un device context per il controllo list-box, e selezionarvi il giusto font (e, alla fine, dovremo rimettere a posto il font e liberare il device context).

Ecco, dunque, una funzione che incapsula appunto tutte queste operazioni...:

int extent(HWND hWnd, const char* lpString) { // ottieniamo il DC e il font da usare HDC hDC = GetDC(hWnd); HFONT hfNew = (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0); // piazziamo il nuovo font e ne troviamo la metrica HFONT hfOld; if(hfNew) hfOld = (HFONT)SelectObject(hDC, hfNew); TEXTMETRIC tm; GetTextMetrics(hDC, &tm); // otteniamo la dimensione in pixel della stringa SIZE size; GetTextExtentPoint32(hDC, lpString, strlen(lpString), &size); // ripristiniamo la situazione di font e DC if(hfNew) SelectObject(hDC, hfOld); ReleaseDC(hWnd, hDC); // restituiamo il risultato return size.cx + tm.tmAveCharWidth; }

L'API GetDC prende come parametro un'HWND, e ritorna l'HDC corrispondente alla finestra (esso va poi rilasciato con l'API ReleaseDC, come nella funzione qui elencata).

Il messaggio WM_GETFONT chiede a una finestra (qui, al listbox) quale sia il suo font; il valore di ritorno è (va sottoposto naturalmente a cast...) un HFONT, che NON dovremo distruggere (ci penserà la finestra, quando a sua volta verrà distrutta). Se la finestra "non ha nessun font particolare" (cioè, se ricorre al font di default di sistema), in risposta al messaggio tornerà 0 (in questo caso, evitiamo i passi descritti al prossimo paragrafo).

L'API SelectObject accetta come primo parametro un HDC, e, come secondo, la handle di un oggetto grafico (una penna, un pennello, un font...); essa inserisce l'oggetto stesso come "oggetto corrente" di quel tipo in quel DC, e torna la handle del precedente "oggetto corrente" dello stesso tipo nello stesso DC. Qui, chiamiamo questa API (se la chiamiamo) passandole come secondo parametro la handle di un font, quindi essa imposta il font corrente del DC, e ritorna l'HFONT (anche qui, naturalmente, ci serve un cast!) del font che era in precedenza selezionato in quel DC; dobbiamo conservarci quest'ultima handle (qui, lo facciamo nella variabile locale hfOld), al fine di re-inserirla (sempre con SelectObject) nel DC, per ripristinarlo allo stato di riposo, prima di rilasciarlo.

Tutte queste API, molto generali, potranno servirci, in altri casi, anche a scopi piuttosto diversi. Le altre due API usate in questa funzione sono invece specializzate ai problemi di misurazone delle stringhe e dei font. La GetTextMetrics prende come primo parametro un HDC, e come secondo parametro l'indirizzo di una nostra struttura di tipo TEXTMETRIC, che riempie con abbondanza di dati relativi alle dimensioni del font attualmente selezionato nel DC stesso. Qui, ci serve, fra tutti i suoi campi, il solo campo tmAveCharWidth, che ci dà l'ampiezza media, in pixel, di un carattere; è questo proprio il numero di pixel che dobbiamo sommare alla misurazione ritornata dall'API GetTextExtentPoint32, per avere un decente "margine" di spazio bianco per la visualizzazione. Quest'ultima API, dato un DC e una stringa (indirizzo e lunghezza), riempie una struttura SIZE, ponendo nei suoi due campi cx e cy rispettivamente l'ampiezza e l'altezza in pixel della stringa così come verrà disegnata nel font attualmente selezionato in quel DC.

Notiamo, naturalmente, che questa funzione extent, che abbiamo qui sviluppato, è perfettamente generale, e si applica al calcolo dell'ampiezza in pixel di una stringa in una qualsiasi finestra; per avere una generalità ancora un poco maggiore, si potrebbe al massimo prevedere di accettare anche un'HFONT come parametro, per coprire il caso in cui vorremo poi disegnare la stringa in un font diverso da quello della finestra (lasciamo al lettore questa ulteriore generalizzazione, limitandoci a ricordargli che l'arte del progetto di buon codice riusabile sta anche nel decidere quali generalizzazioni non inserire...:-).

Ecco, comunque, la funzione completa di "aggiungi questa stringa in coda a questo listbox", che tiene conto delle problematiche di scorrimento, sia verticale, sia orizzontale:

void addString(HWND hList, const char* lpString) { // sospendiamo il ridisegno automatico SendMessage(hList, WM_SETREDRAW, 0, 0); // garantiamo una corretta gestione della barra // di scorrimento orizzontale int wid = extent(hList, lpString); int awi = SendMessage(hList, LB_GETHORIZONTALEXTENT, 0, 0); if(wid > awi) SendMessage(hList, LB_SETHORIZONTALEXTENT, wid, 0); // aggiungiamo la stringa in coda al listbox int idx = SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)lpString); // garantiamo una corretta gestione dello scorrimento // verticale, che mostri la nuova stringa in fondo int shown; do { RECT rr; shown = SendMessage(hList, LB_GETITEMRECT, idx, (LPARAM)&rr); if(!shown) { int itop = SendMessage(hList, LB_GETTOPINDEX, 0, 0); SendMessage(hList, LB_SETTOPINDEX, itop+1, 0); } } while(!shown); // ripristiniamo il ridisegno automatico SendMessage(hList, WM_SETREDRAW, 1, 0); // forziamo il listbox a ridisegnarsi InvalidateRect(hList, 0, 0); }

Notiamo che occorrono anche precauzioni "analoghe ma inverse" per la corretta gestione della barra di scorrimento orizzontale a fronte della rimozione di stringhe dal listbox; se viene rimossa la stringa più lunga, si deve aggiornare l'"extent orizzontale" del listbox. Questa funzionalità non ci serve per il nostro programmino di "output di debug" (poichè in esso non cancelliamo mai stringhe dal listbox), e quindi la lasciamo alla buona volontà del lettore; per realizzarla in modo ragionevolmente ottimizzato, sarà necessario fare uso d'ingegno, e di strutture dati non banali.

Oltre a questi svariati usi per mostrare elenchi di stringhe, i controlli list-box, come già abbiamo accennato, possono tornare utili per mostrare elenchi di grafica del tutto arbitrari, grazie alle funzionalità di "owner-drawn". Esemplificheremo uno di questi usi, e coglieremo l'occasione di esaminare altre API, nel prossimo capitolo.


Capitolo 44: messaggi ai LISTBOX (2)
Capitolo 46: un elenco di bitmap
Elenco dei capitoli