Esempio: font_one
Caricare i Fonts
Le GDI di Win32 offrono notevoli funzionalità per gestire caratteri tipografici, stili, lingue e set di caratteri molto diversi tra loro. Il rovescio della medaglia è che lavorare con i fonts può sembrare piuttosto intimidatorio a chi è alle prime armi. CreateFont(), la API principale quando si parla di fonts, ha 14 parametri per specificare altezza, stile, spessore, famiglia e vari altri attributi.
Fortunatamente non è difficile come potrebbe sembrare, e buona parte del lavoro viene svolta da valori di default ragionevoli. Tutti i parametri di CreateFont() tranne 2 possono essere impostati a 0 o NULL, e il sistema userà semplicemente un valore di default, restituendo un font normalissimo.
CreateFont() crea un HFONT, un handle a un Font Logico in memoria. I dati a cui fa riferimento questo handle possono essere copiati in una struttura LOGFONT usando GetObject(), esattamente come si può riempire una struttura BITMAP a partire da un HBITMAP.
I membri di LOGFONT corrispondono ai parametri di CreateFont() e per comodità è possibile creare un font direttamente da una struttura LOGFONT esistente usando CreateFontIndirect(). Questo è molto utile, perché rende semplice creare un nuovo font a partire dall’handle di un font esistente quando se ne vogliono modificare solo alcuni aspetti. Usate GetObject() per riempire un LOGFONT, modificate i membri che desiderate e create un nuovo font con CreateFontIndirect().
HFONT hf;
HDC hdc;
long lfHeight;
hdc = GetDC(NULL);
lfHeight = -MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72);
ReleaseDC(NULL, hdc);
hf = CreateFont(lfHeight, 0, 0, 0, 0, TRUE, 0, 0, 0, 0, 0, 0, 0, "Times New Roman");
if(hf)
{
DeleteObject(g_hfFont);
g_hfFont = hf;
}
else
{
MessageBox(hwnd, "Font creation failed!", "Error", MB_OK | MB_ICONEXCLAMATION);
}Questo è il codice usato per creare il font dell’immagine di esempio: si tratta del Times New Roman a 12 punti con lo stile corsivo attivo. Il flag del corsivo è il sesto parametro di CreateFont(), che come potete vedere abbiamo impostato a TRUE. Il nome del font che vogliamo usare è l’ultimo parametro.
L’unico trucco in questo codice riguarda il valore usato per la dimensione del font, il parametro lfHeight di CreateFont(). Di solito, quando si ha a che fare con i fonts, si è abituati a lavorare con dimensioni in punti: corpo 10, corpo 12, ecc… CreateFont() però non accetta dimensioni in punti, vuole delle Unità Logiche, che sullo schermo sono diverse da quelle della stampante, e perfino tra stampanti e schermi diversi.
Questa situazione esiste perché la risoluzione dei vari dispositivi è molto diversa… Le stampanti possono tranquillamente arrivare da 600 a 1200 pixel per pollice, mentre uno schermo è fortunato se arriva a 200… se usaste per la stampante un font della stessa dimensione usata sullo schermo, probabilmente non riuscireste nemmeno a distinguere le singole lettere.
Tutto quello che dobbiamo fare è convertire la dimensione in punti che vogliamo nella corrispondente dimensione logica per il dispositivo. In questo caso il dispositivo è lo schermo, quindi otteniamo l’HDC dello schermo, ricaviamo il numero di pixel logici per pollice con GetDeviceCaps() e lo inseriamo nella formula gentilmente fornita da MSDN, che usa MulDiv() per convertire la nostra dimensione di 12 punti nella dimensione logica corretta che CreateFont() si aspetta. Memorizziamo il risultato in lfHeight e lo passiamo come primo parametro a CreateFont().
Fonts di Default
La prima volta che chiamate GetDC() per ottenere l’HDC della vostra finestra, il font selezionato di default è System, che a dire il vero non è poi così bello. Il modo più semplice per ottenere un font dall’aspetto accettabile (senza dover passare per la seccatura di CreateFont()) è chiamare GetStockObject() richiedendo il DEFAULT_GUI_FONT.
Questo è un oggetto di sistema e potete ottenerlo tutte le volte che volete senza perdite di memoria; potete anche chiamare DeleteObject() su di esso senza che succeda nulla, il che è un bene, perché così non dovete tenere traccia del fatto che il vostro font provenga da CreateFont() o da GetStockObject() prima di provare a liberarlo.
Disegnare il Testo
Ora che abbiamo un bel font a disposizione, come facciamo a scrivere del testo sullo schermo? Questo assumendo che non vogliamo semplicemente usare un controllo Edit o Static.
Le opzioni di base sono TextOut() e DrawText(). TextOut() è più semplice, ma ha meno opzioni e non gestisce per voi l’andata a capo automatica o l’allineamento.
char szSize[100];
char szTitle[] = "These are the dimensions of your client area:";
HFONT hfOld = SelectObject(hdc, hf);
SetBkColor(hdc, g_rgbBackground);
SetTextColor(hdc, g_rgbText);
if(g_bOpaque)
{
SetBkMode(hdc, OPAQUE);
}
else
{
SetBkMode(hdc, TRANSPARENT);
}
DrawText(hdc, szTitle, -1, prc, DT_WORDBREAK);
wsprintf(szSize, "{ %d, %d, %d, %d }", prc->left, prc->top, prc->right, prc->bottom);
DrawText(hdc, szSize, -1, prc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
SelectObject(hdc, hfOld);La prima cosa che facciamo è usare SelectObject() per inserire il font che vogliamo usare nel nostro HDC, pronto per disegnare. Tutte le successive operazioni sul testo useranno questo font finché non ne verrà selezionato un altro.
Poi impostiamo i colori del testo e dello sfondo. Impostare il colore di sfondo non colora tutto lo sfondo di quel colore, ma influisce solo su alcune operazioni (tra cui il testo) che usano il colore di sfondo per disegnare. Il risultato dipende anche dalla Modalità di Sfondo corrente. Se è impostata a OPAQUE (il default), dietro al testo disegnato viene riempito lo spazio con il colore di sfondo. Se è impostata a TRANSPARENT, il testo viene disegnato senza sfondo, lasciando vedere qualunque cosa ci sia dietro; in questo caso il colore di sfondo non ha alcun effetto.
Ora disegniamo effettivamente il testo con DrawText(), passandole l’HDC da usare e la stringa da disegnare. Il terzo parametro è la lunghezza della stringa, ma abbiamo passato -1 perché DrawText() è abbastanza furba da calcolare da sola la lunghezza del testo. Nel quarto parametro passiamo prc, il puntatore al RECT della client area. DrawText() disegnerà all’interno di questo rettangolo in base agli altri flag che le passate.
Nella prima chiamata specifichiamo DT_WORDBREAK, che di default allinea in alto a sinistra e manda automaticamente a capo il testo al bordo del rettangolo… molto utile.
Nella seconda chiamata stampiamo invece una sola riga senza andare a capo, e vogliamo che sia centrata sia orizzontalmente che verticalmente (cosa che DrawText() fa solo quando disegna una singola riga).
Ridisegnare la Client Area
Solo una nota sul programma di esempio… quando viene registrata la WNDCLASS ho impostato gli stili di classe CS_VREDRAW e CS_HREDRAW. Questo fa sì che l’intera client area venga ridisegnata quando la finestra viene ridimensionata, mentre il comportamento di default è ridisegnare solo le parti che sono cambiate. Il risultato sarebbe davvero brutto, perché il testo centrato si sposta durante il ridimensionamento e non verrebbe aggiornato come ci si aspetta.
Scegliere i Fonts
In generale, qualsiasi programma che ha a che fare con i fonts vorrà permettere all’utente di scegliere il proprio font, insieme al colore e agli attributi di stile da usare per visualizzarlo.
Come per le common dialogs che servono a ottenere i nomi dei file da aprire e salvare, esiste una common dialog per scegliere un font. Si chiama, guarda caso, ChooseFont() e lavora con la struttura CHOOSEFONT, che serve sia a impostare i valori iniziali sia a restituire il risultato finale della selezione dell’utente.
HFONT g_hfFont = GetStockObject(DEFAULT_GUI_FONT);
COLORREF g_rgbText = RGB(0, 0, 0);void DoSelectFont(HWND hwnd)
{
CHOOSEFONT cf = {sizeof(CHOOSEFONT)};
LOGFONT lf;
GetObject(g_hfFont, sizeof(LOGFONT), &lf);
cf.Flags = CF_EFFECTS | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
cf.hwndOwner = hwnd;
cf.lpLogFont = &lf;
cf.rgbColors = g_rgbText;
if(ChooseFont(&cf))
{
HFONT hf = CreateFontIndirect(&lf);
if(hf)
{
g_hfFont = hf;
}
else
{
MessageBox(hwnd, "Font creation failed!", "Error", MB_OK | MB_ICONEXCLAMATION);
}
g_rgbText = cf.rgbColors;
}
}L’hwnd in questa chiamata è semplicemente la finestra che volete usare come genitore della dialog dei fonts.
Il modo più semplice di usare questa dialog è insieme a una struttura LOGFONT esistente, che molto probabilmente deriva dall’HFONT che state usando in quel momento. Impostiamo il membro lpLogFont della struttura in modo che punti al LOGFONT che abbiamo appena riempito con le informazioni correnti, e aggiungiamo anche il flag CF_INITTOLOGFONTSTRUCT affinché ChooseFont() sappia di dover usare questo membro. Il flag CF_EFFECTS dice a ChooseFont() di permettere all’utente di selezionare un colore, oltre agli attributi Sottolineato e Barrato.
Stranamente, gli stili Grassetto e Corsivo non contano come effetti: sono considerati parte del font stesso, e infatti alcuni fonts esistono solo in grassetto o in corsivo. Se volete controllare o impedire che l’utente selezioni un font grassetto o corsivo, potete controllare rispettivamente i membri lfWeight e lfItalic del LOGFONT dopo che l’utente ha fatto la sua scelta. A quel punto potete chiedere all’utente di fare un’altra scelta, oppure modificare i membri prima di chiamare CreateFontIndirect().
Il colore di un font non è associato a un HFONT e quindi va memorizzato separatamente; il membro rgbColors della struttura CHOOSEFONT viene usato sia per passare il colore iniziale sia per ottenere il nuovo colore al termine.
CF_SCREENFONTS indica che vogliamo fonts pensati per funzionare sullo schermo, a differenza dei fonts pensati per le stampanti. Alcuni supportano entrambi, altri solo l’uno o l’altro. A seconda dell’uso che farete del font, su MSDN trovate questo e molti altri flag per limitare esattamente quali fonts l’utente potrà selezionare.
Scegliere i Colori
Per permettere all’utente di cambiare solo il colore del font, o di scegliere un nuovo colore per qualsiasi altra cosa, esiste la common dialog ChooseColor(). Questo è il codice usato nel programma di esempio per permettere all’utente di scegliere il colore di sfondo.
COLORREF g_rgbBackground = RGB(255, 255, 255);
COLORREF g_rgbCustom[16] = {0};void DoSelectColour(HWND hwnd)
{
CHOOSECOLOR cc = {sizeof(CHOOSECOLOR)};
cc.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ANYCOLOR;
cc.hwndOwner = hwnd;
cc.rgbResult = g_rgbBackground;
cc.lpCustColors = g_rgbCustom;
if(ChooseColor(&cc))
{
g_rgbBackground = cc.rgbResult;
}
}Il codice è piuttosto semplice; anche qui usiamo il parametro hwnd come genitore della dialog. Il flag CC_RGBINIT indica di partire dal colore che passiamo nel membro rgbResult, che è anche il membro da cui leggiamo il colore scelto dall’utente quando la dialog viene chiusa.
L’array g_rgbCustom di 16 COLORREF è necessario per memorizzare i valori che l’utente decide di inserire nella tabella dei colori personalizzati della dialog. Potreste salvare questi valori da qualche parte, ad esempio nel registro, altrimenti andranno semplicemente persi alla chiusura del programma. Questo parametro non è opzionale.
Fonts dei Controlli
Un’altra cosa che a un certo punto potreste voler fare è cambiare il font dei controlli della vostra dialog o finestra. Di solito capita quando si usa CreateWindow() per creare i controlli, come abbiamo fatto negli esempi precedenti. I controlli, come le finestre, usano di default il font System, per questo abbiamo usato WM_SETFONT per impostare un nuovo handle di font (ottenuto da GetStockObject()) da far usare al controllo. Potete usare questo metodo anche con i fonts creati con CreateFont(). Basta passare l’handle del font come wParam e impostare lParam a TRUE per far ridisegnare il controllo. L’ho già fatto negli esempi precedenti, ma ha senso parlarne qui perché è pertinente e molto breve:
SendDlgItemMessage(hwnd, IDC_OF_YOUR_CONTROL, WM_SETFONT, (WPARAM)hfFont, TRUE);Dove hfFont è ovviamente l’HFONT che volete usare, e IDC_OF_YOUR_CONTROL è l’ID del controllo di cui volete cambiare il font.
