Dialog wyboru koloru
Więc chodź, pomaluj mój świat na żółto i na niebieeeesko! No dobra, ale najpierw przydałoby się wybrać te kolory, bo inaczej pomalujemy świat na czarno, a przecież nie jesteśmy żadnymi wrednymi satanistami (bez względu na to, co sugeruje nazwa tej strony ;-)) i kolorki lubimy. Dialog, jaki sobie tym razem wyczarujemy, wygląda następująco:
Czarowanie rozpoczniemy, jak się pewnie domyślasz, od przedstawienia nowej struktury. Będzie to struktura typu
CHOOSECOLOR:
typedef struct
{ DWORD lStructSize;
HWND hwndOwner;
HWND hInstance;
COLORREF rgbResult;
COLORREF * lpCustColors;
DWORD Flags;
LPARAM lCustData;
LPCCHOOKPROC lpfnHook;
LPCTSTR lpTemplateName;
} CHOOSECOLOR;
Jak widzisz, w porównaniu z
OPENFILENAME nie ma się czego bać :-). Zresztą znów możemy bezczelnie pominąć kilka pól, które na razie nam się nie przydadzą. Dwa pierwsze pola już znasz, takie same były w strukturze
OPENFILENAME. Znaczenia czwartego i piątego się pewnie domyślasz - tak, tu będzie przechowywany rezultat dialogu.
Zwróć uwagę na pole
lpCustColors. Powinien on wskazywać na tablicę
16 kolorów. Musimy ją sobie sami stworzyć. Jak widać, na dialogu wyboru koloru mamy
48 predefiniowanych (standardowych) kolorów - tych zmieniać nie możemy, można je tylko wybrać, oraz
16 kolorów niestandardowych, definiowanych przez użytkownika. To właśnie na nie jest ta tablica. Jeśli jej nie utworzymy, dialog nie będzie miał gdzie przechowywać danych o tych kolorach.
Tablicę, wskazywaną przez
lpCustColors dobrze jest zainicjalizować, albo przynajmniej wyzerować funkcją
ZeroMemory. Jeśli tego nie zrobimy, to nic złego się nie stanie, po prostu dialog będzie wyświetlał
16 przypadkowych kolorów.
Nie pozostaje nic innego, jak tylko wypełnić strukturę i wywołać dialog na scenę:
CHOOSECOLOR ccl;
COLORREF TabKol[ 16 ];
BOOL bResult;
ZeroMemory( & ccl, sizeof( CHOOSECOLOR ) );
ccl.lStructSize = sizeof( CHOOSECOLOR );
ccl.hwndOwner = hwnd;
ccl.lpCustColors = TabKol;
ccl.Flags = CC_ANYCOLOR;
bResult = ChooseColor( & ccl );
Jeśli wszystko pójdzie zgodnie z planem, to funkcja
ChooseColor zwróci
TRUE. W przeciwnym wypadku będziemy musieli wywołać specjalną funkcję
CommDlgExtendedError w celu sprawdzenia, jaki konkretnie błąd wystąpił. Jeśli
ChooseColor zwróci
FALSE, ale
CommDlgExtendedError zwróci
0, będzie to oznaczało, że żadnego błędu nie było, tylko użytkownik wcisnął przycisk Anuluj:
DWORD dwErr;
bResult = ChooseColor( & ccl );
if( bResult )
MessageBox( hwnd, "Wybrano nowy kolor", "test", MB_ICONINFORMATION );
else
{
dwErr = CommDlgExtendedError();
if( !dwErr )
MessageBox( hwnd, "Anulowano wybór koloru", "test", MB_ICONEXCLAMATION );
else
MessageBox( hwnd, "Wystąpił jakowyś błąd", "test", MB_ICONSTOP );
}
Ten przykład ilustruje tylko samo wywołanie dialogu, nie robi nic z wybranym kolorem (który zwracany jest w polu
rgbResult, jak nietrudno się domyślić) ani nawet nie sprawdza, czy użytkownik wybrał w ogóle jakiś kolor. To wszystko pozostawiam tobie do samodzielnych eksperymentów.
Dialog wyboru czcionki
Z wyborem czcionek sprawa ma się podobnie. Tym razem struktura nazywa się
CHOOSEFONT, a funkcja otwierająca dialog -
ChooseFont. Mamy też i flagi - w większości oznaczają one rodzaje czcionek, jakie mają być dostępne do wyboru:
typedef struct { DWORD lStructSize;
HWND hwndOwner;
HDC hDC;
LPLOGFONT lpLogFont;
INT iPointSize;
DWORD Flags;
DWORD rgbColors;
LPARAM lCustData;
LPCFHOOKPROC lpfnHook;
LPCTSTR lpTemplateName;
HINSTANCE hInstance;
LPTSTR lpszStyle;
WORD nFontType;
WORD ___MISSING_ALIGNMENT__;
INT nSizeMin;
INT nSizeMax;
} CHOOSEFONT;
Trochę tego więcej niż ostatnio, ale nie marudzimy i zabieramy się do roboty:
CHOOSEFONT cfnt;
LOGFONT lf;
ZeroMemory( & cfnt, sizeof( CHOOSEFONT ) );
cfnt.lStructSize = sizeof( CHOOSEFONT );
cfnt.hwndOwner = hwnd;
cfnt.Flags = CF_EFFECTS | CF_FORCEFONTEXIST | CF_SCREENFONTS;
cfnt.lpLogFont = & lf;
Podobnie jak w przypadku dialogu z kolorkami, tutaj również potrzebujemy własnej zmiennej, tym razem typu
LOGFONT. Wskaźnik do niej musimy przekazać w polu
lpLogFont (ostatnia linijka przykładu). Jest to bardzo ważne, gdyż inaczej dialog nie będzie miał gdzie zapamiętać wybranej czcionki. Gdybyśmy dorzucili do flag
CF_INITTOLOGFONTSTRUCT, a zmienna
lf zawierałaby już prawidłowy opis czcionki, to dialog przyjąłby tę czcionkę za domyślną.
Użyliśmy trzech flag, jak widać. Pierwsza,
CF_EFFECTS, oznacza że dialog powinien umożliwiać wybór efektów dla czcionki. Te efekty to: podkreślenie, przekreślenie oraz kolor. Nie są to zbyt potrzebne rzeczy, ale domyślnie są ukryte, co paskudnie wygląda.
Flaga
CF_FORCEFONTEXIST oznacza, że użytkownik musi wybrać istniejącą czcionkę. Jeśli menda wpisze złośliwie coś dziwnego w pole z nazwą czcionki, a ta flaga jest ustawiona, to system ochrzani użytkownika za pisanie głupot ;-).
No i wreszcie
CF_SCREENFONTS mówi systemowi, że ma nam wyświetlić tylko czcionki ekranowe. Gdyby była taka potrzeba, moglibyśmy wyświetlić tylko czcionki drukarki (flaga
CF_PRINTERFONTS) albo i jedne, i drugie (flaga
CF_BOTH). Jeśli natomiast nie damy żadnej z wymienionych w tym akapicie trzech flag, to dostaniemy uroczy (i absurdalny) komunikat, że nie zainstalowano żadnych czcionek w systemie, a następnie funkcja
ChooseFont zwróci
FALSE, co oznacza błąd.
Jeśli jednak do takiej niewesołej sytuacji nie doszło, to
ChooseFont zwraca
TRUE i wówczas możemy skorzystać z kilku następnych pól naszej struktury
CHOOSEFONT, ażeby uzyskać informacje o wybranej przez użytkownika czcionce. Pole
iPointSize będzie wówczas zawierało wybrany rozmiar czcionki,
rgbColors - jej kolor,
nFontType - typ czcionki (zawiera informację o pogrubieniu / pochyleniu czcionki, o tym czy jest to czcionka ekranowa czy też czcionka drukarki itp.). No i najważniejsze pole -
lpLogFont będzie zawierało wskaźnik do struktury
LOGFONT, opisującej wybraną czcionkę.
Pozostaje pytanie, jak wykorzystać informacje zawarte w
LOGFONT... Wiemy bowiem, że aby wybrać czcionkę dla danego kontekstu, podajemu uchwyt do niej (typu
HFONT). Na szczęście uzyskanie uchwytu na podstawie struktury
LOGFONT nie jest to zbyt skomplikowane. Służy do tego funkcja
CreateFontIndirect:
HFONT hfnMyFont;
hfnMyFont = CreateFontIndirect( cfnt.lpLogFont );
if( hfnMyFont == NULL )
MessageBox( hwnd, "Wredna czciona nie chce się utworzyć...", NULL, MB_ICONEXCLAMATION );
Pora na krótki przykład zastosowania. Załóżmy, że mamy EDIT-a o globalnym uchwycie
g_hEdit, przycisk (o uchwycie
g_hButton) do otwierania dialogu wyboru czcionki oraz globalny uchwyt
g_hfnNowy typu
HFONT (domyślnie ustawiony na
NULL):
case WM_COMMAND:
{
if(( HWND ) lParam != g_hButton ) break;
CHOOSEFONT cfnt;
LOGFONT lf;
ZeroMemory( & cfnt, sizeof( CHOOSEFONT ) );
cfnt.lStructSize = sizeof( CHOOSEFONT );
cfnt.hwndOwner = hwnd;
cfnt.Flags = CF_EFFECTS | CF_FORCEFONTEXIST | CF_SCREENFONTS;
cfnt.lpLogFont = & lf;
if( !ChooseFont( & cfnt ) )
{
DWORD dwErr = CommDlgExtendedError();
if( dwErr ) MessageBox( hwnd, "Straszliwy błąd dialogu!", NULL, MB_ICONEXCLAMATION );
}
else
{
if( g_hfnNowy != NULL )
DeleteObject( g_hfnNowy );
g_hfnNowy = CreateFontIndirect( cfnt.lpLogFont );
if( hfnNowy != NULL )
SendMessage( g_hEdit, WM_SETFONT,( WPARAM ) hfnNowy, MAKELPARAM( TRUE, 0 ) );
}
}
break;
case WM_DESTROY:
{
if( g_hfnNowy != NULL ) DeleteObject( g_hfnNowy );
PostQuitMessage( 0 );
}
break;
Jak łatwo się przekonać (a dla niektórych wystarczy nawet spojrzeć ;-)), powyższy kod sprawia, że naciśnięcie przycisku otwiera dialog wyboru czcionki, a w przypadku gdyby użytkownik był na tyle miły i wybrał jakąś, to czcionka ta zostanie ustawiona dla pola tekstowego.
Do ustawienia czcionki dla okna (tutaj dla pola tekstowego) posłużyliśmy się komunikatem WM_SETFONT. Jego parametry to: uchwyt czcionki oraz flaga oznaczająca, czy po zmianie czcionki okno (kontrolka) ma być odświeżone (u nas jak najbardziej
TRUE).