Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: 'Złośliwiec'
Biblioteki C++

Kolory kontrolek

[lekcja]
Fakt, że większość kontrolek posiada ten sam kolor tekstu i tła jest w gruncie rzeczy zaletą systemu operacyjnego (wyobraźmy sobie program, w którym każdy przycisk jest w innym kolorze), ale to nie znaczy, że barwy same w sobie są złe. Czasem wyróżnienie czegoś innym kolorem znacznie poprawia czytelność naszego okna dialogowego czy co tam akurat projektujemy. Użytkownik z pewnością nie przeczyta kilkuwierszowego ostrzeżenia, jeśli użyjemy standardowej, czarnej czcionki. Ale czerwony tekst sprawi, że zapali mu się przysłowiowa lampka: "O, cholera, jeszcze sobie coś napsuję, może jednak lepiej to przeczytam!".

Nie istnieją w WinAPI żadne funkcje do zmiany koloru kontrolek. Nie ma też do tego specjalnych komunikatów wysyłanych do kontrolki (a jeśli są, to nie do wszystkiego). Nie mamy również zbytniego wpływu na kolor podczas tworzenia kontrolki. Zamiast tego system wysyła nam odpowiednie komunikaty (osobno dla różnych rodzajów kontrolek!), na które możemy odpowiedzieć. Jeśli nie zrobimy tego, kontrolka zostanie narysowana w sposób standardowy.

Kontrolka STATIC

Na pierwszy ogień – statyczny tekst (lub inny statyczny element). Komunikat ustawiający kolor nazywa się tu WM_CTLCOLORSTATIC. Umieszczamy więc obsługę tego komunikatu w naszej procedurze okna. Odpowiadamy na komunikat (który wysyłany jest przez system za każdym razem, gdy kontrolka chce się narysować), zwracając uchwyt do pędzla, który zostanie użyty by narysować tło kontrolki. Oczywiście pędzel ten musimy sobie najpierw utworzyć – najlepiej gdzieś w pobliżu miejsca, gdzie tworzymy samą kontrolkę:

C/C++
g_hBrush = CreateSolidBrush( RGB( 255, 255, 0 ) );

Musimy też pamiętać, by zwolnić utworzony w ten sposób pędzel, gdy przestaje już być potrzebny (tj. gdy wszystkie używające go kontrolki zostaną zniszczone). Możemy też użyć funkcji GetSysColorBrush, aby pobrać koloru systemowy – wtedy nie musimy się martwić o zwalnianie, ale do wyboru jest tylko kilka kolorów ustawianych w Panelu Sterowania.

Obsługa WM_CTLCOLORSTATIC w najprostszym wydaniu wygląda następująco:

C/C++
case WM_CTLCOLORSTATIC:
return( LRESULT ) g_hBrush;

Po kompilacji kodu z dodanymi powyższymi fragmentami widzimy, że nasz sposób okazał się dość ułomny. Mamy wprawdzie żółty kolorek zamiast szarego (czy białego), ale wokół tekstu widoczny jest i tak paskudny szary (biały) prostokąt. W dodatku kolor ustawia się dla wszystkich kontrolek, a nie jednej wybranej. No i jak ustawić kolor tekstu?

I tutaj na scenę wchodzą parametry wParam i lParam. Pierwszy z nich zawiera uchwyt do kontekstu urządzenia kontrolki (HDC), a drugi – uchwyt do samej kontrolki (HWND). Mając te dane jesteśmy w stanie zrobić nieco więcej. Najpierw dodamy warunek, dzięki któremu tylko jedna kontrolka otrzyma żółty kolorek. Załóżmy, że uchwyt do tej wybranej kontrolki mamy w zmiennej g_hStatic:

C/C++
case WM_CTLCOLORSTATIC:
{
    HWND hCtl =( HWND ) lParam;
    if( hCtl == g_hStatic )
         return( LRESULT ) g_hBrush;
   
}
break;

W takiej postaci nasz kod nie popsuje pozostałych kontrolek, jakie mamy w okienku, bo kolor ustawi się tylko dla tej, dla której ma się ustawić. Co jednak z pozostałymi mankamentami? Załatwimy je dzięki otrzymanemu HDC. Najpierw wyeliminujemy "otoczkę" z naszego tekstu:

C/C++
case WM_CTLCOLORSTATIC:
{
    HWND hCtl =( HWND ) lParam;
    HDC hDC =( HDC ) wParam;
   
    if( hCtl == g_hStatic )
    {
        SetBkMode( hDC, TRANSPARENT );
        return( LRESULT ) g_hBrush;
    }
}
break;

Użyliśmy funkcji SetBkMode, by zmienić tryb rysowania tła dla HDC kontrolki. Teraz cały obszar zajmowany przez kontrolkę jest żółty i o to chodziło. Co zaś z kolorem tekstu? Nic prostszego, wystarczy użyć funkcji SetTextColor z naszym HDC:

C/C++
case WM_CTLCOLORSTATIC:
{
    HWND hCtl =( HWND ) lParam;
    HDC hDC =( HDC ) wParam;
   
    if( hCtl == g_hStatic )
    {
        SetBkMode( hDC, TRANSPARENT );
        SetTextColor( hDC, RGB( 255, 0, 0 ) );
        return( LRESULT ) g_hBrush;
    }
}
break;

No to mamy czerwony tekst na żółtym tle. Pora na pozostałe kontrolki...

Kontrolka EDIT

Tutaj sytuacja jest bardzo podobna. Zmienia się tylko typ komunikatu – tym razem jest to WM_CTLCOLOREDIT:

C/C++
case WM_CTLCOLOREDIT:
{
    HWND hCtl =( HWND ) lParam;
    HDC hDC =( HDC ) wParam;
   
    if( hCtl == g_hEdit )
    {
        SetBkMode( hDC, TRANSPARENT );
        SetTextColor( hDC, RGB( 255, 0, 0 ) );
        return( LRESULT ) g_hBrush;
    }
}
break;

Różnice zaczynają się w momencie, gdy chcemy kontrolować również wygląd pól tekstowych, które są tylko do odczytu (tj. mają ustawiony styl ES_READONLY) albo zostały wyłączone (np. funkcją EnableWindow). W takim przypadku dostajemy nie WM_CTLCOLOREDIT, tylko znany nam już WM_CTLCOLORSTATIC. Co zrobić, by nie pisać dwa razy tego samego kodu, jeśli chcemy np. taki sam kolor tła, ale różny kolor tekstu dla kontrolki wyłączonej? Można zrobić tak:

C/C++
case WM_CTLCOLOREDIT:
case WM_CTLCOLORSTATIC:
{
    HWND hCtl =( HWND ) lParam;
    HDC hDC =( HDC ) wParam;
   
    if( hCtl == g_hEdit )
    {
        SetBkMode( hDC, TRANSPARENT );
       
        if( IsWindowEnabled( hCtl ) )
             SetTextColor( hDC, RGB( 255, 0, 0 ) ); // czerwony
        else
             SetTextColor( hDC, RGB( 128, 128, 128 ) ); // szary
       
        return( LRESULT ) g_hBrush;
    }
}
break;

Obsłużyliśmy obydwa komunikaty naraz; nie jest to jednak problem, ponieważ i tak sprawdzamy uchwyt kontrolki. Nie ma więc ryzyka, że ustawimy kolor na innej kontrolce, niżbyśmy chcieli.

Warto jeszcze wspomnieć, że przedstawiony tutaj sposób zmiany koloru tła nie działa dla kontrolki RichEdit (która wprawdzie ma wiele wspólnego ze "zwykłym" EDIT-em, ale jak widać nie wszystko). W tym wypadku mamy prościej – kolor ustawiamy, wysyłając komunikat EM_SETBKGNDCOLOR, w jego parametrach podając nowy kolor.

Kontrolki LISTBOX i COMBOBOX

W przypadku kontrolki LISTBOX sytuacja jest prawie identyczna jak z EDIT. Zmienia się jedynie komunikat – na WM_CTLCOLORLISTBOX:

C/C++
case WM_CTLCOLORLISTBOX:
{
    HWND hCtl =( HWND ) lParam;
    HDC hDC =( HDC ) wParam;
   
    if( hCtl == g_hListBox )
    {
        SetBkMode( hDC, TRANSPARENT );
        SetTextColor( hDC, RGB( 255, 0, 0 ) );
        return( LRESULT ) g_hBrush;
    }
}
break;

ComboBox jest nieco bardziej skomplikowany. Tutaj dostajemy komunikat WM_CTLCOLOREDIT, ale – by życie nie było zbyt proste – jeśli klikniemy strzałkę, to pokaże się lista i tutaj już dostaniemy osobny komunikat. Nietrudno się domyślić, że będzie to znany nam już WM_CTLCOLORLISTBOX. Reszta bez zmian. Jeśli chcemy, by wszystkie elementy ComboBox-a miały te same kolory, stosujemy tę samą sztuczkę, co w przypadku wyłączonej kontrolki EDIT (czyli podwójny case).

Kontrolka SCROLLBAR

No i wreszcie ostatnia z podstawowych kontrolek – pasek przewijania. Również i ona ma swój osobny komunikat, jest to WM_CTLCOLORSCROLLBAR. Można zmienić jego kolor tła, dokładnie tak samo, jak dla wcześniej omówionych kontrolek:

C/C++
case WM_CTLCOLORSCROLLBAR:
{
    HWND hCtl =( HWND ) lParam;
   
    if( hCtl == g_hScroll )
    {
        return( LRESULT ) g_hBrush;
    }
}
break;

Sposób ten działa jednak tylko z paskami, które sami utworzyliśmy. Jeśli paski przewijania zostały automatycznie dodane do naszego okna (poprzez style WS_SCROLL i WS_VSCROLL), to dla nich nie dostaniemy komunikatu WM_CTLCOLORSCROLLBAR. Istnieje jednak jeszcze coś takiego, jak płaskie scrollbary. Jest to zupełnie odrębny rodzaj tych kontrolek, należący do grupy ''Common Controls''. Tak więc jeśli chcesz ich używać musisz najpierw dolinkować Comctl32.lib i dołączyć nagłówek Commctrl.h, a także zainicjować tę bibliotekę (jeśli nie wiesz jak, zajrzyj [[StatusBar|tutaj]]).

Następnie trzeba zainicjować płaskie paseczki, podając uchwyt do okna, w którym je włączamy:

C/C++
InitializeFlatSB( hwnd );

Teraz dopiero możemy ustawić kolor. Tym razem Microsoft stanął na wysokości zadania i dostarczył nam zgrabną funkcyjkę do tego celu:

C/C++
FlatSB_SetScrollProp( hwnd, WSB_PROP_HBKGCOLOR,( INT_PTR ) RGB( 0, 255, 0 ), TRUE );

Powyższy kod ustawia tło scrollbara na zieleń, ale tylko dla poziomego paska. Jeśli chcesz pomalować również pasek pionowy, użyj tego samego wywołania z parametrem WSB_PROP_HBKGCOLOR.

Dialogi i kontrolki w dialogach

Procedury dialogów, w przeciwieństwie do procedur okna, nie zwracają wartości LRESULT, tylko BOOL, która to wartość wskazuje, czy obsłużyliśmy dany komunikat, czy też nie. Zwykle omijamy ten problem, korzystając z funkcji SetWindowLongPtr z indeksem DWLP_MSGRESULT, by podać naszą odpowiedź na komunikat. WinAPI nie byłoby jednak WinAPI, gdyby wszystko było w nim proste, logiczne i konsekwentne. Otóż w tym akurat przypadku wartość ustawiana przez DWLP_MSGRESULT jest ignorowana, zaś kolor kontrolki z dialogu można ustawić przez... konwersję uchwytu do pędzla na typ INT_PTR, czyli typ zwracany przez procedurę dialogu. Można się też spotkać ze "starą" sygnaturą procedury dialogowej, która ma typ BOOL zamiast INT_PTR. Możemy użyć i konwersji na BOOL, w końcu to tylko synonim typu int i bez problemu zmieści się w nim uchwyt. Nie jest to jednak zalecane, jeśli zależy nam na kompatybilności z platformami 64-bitowymi:

C/C++
case WM_CTLCOLORSCROLLBAR:
{
    HWND hCtl =( HWND ) lParam;
   
    if( hCtl == g_hScroll )
         return( INT_PTR ) g_hBrush;
   
}
return FALSE;

Jeśli nie chcemy zmieniać koloru kontrolki, zwracamy FALSE (czyli 0) jak zwykle i wszystko gra.

Mówi się, że szewc bez butów chodzi i tak samo stało się z naszym dialogiem – kontrolki mają swoje kolory, a co z samym dialogiem? Proste, dostaje on komunikat WM_CTLCOLORDLG. I tutaj sytuacja jest podobna, jak z kontrolkami zawartymi w dialogu – konwertujemy uchwyt pędzla na INT_PTR i zwracamy, wszystko pozostałe robimy tak samo, jak w przypadku kontrolek.

To już wszystko na temat kolorów. Oczywiście temat jest jeszcze daleki od wyczerpania – omówiliśmy tylko podstawowe kontrolki. Tymczasem w grupie ''Common Controls'' jest, jak wiemy, sporo dodatkowych i generalnie każda z nich ustawia sobie kolor trochę inaczej, co widzieliśmy już na przykładzie ''flat scrollbars''. Niektóre są już omówione w artykułach poświęconych tym konkretnym kontrolkom, inne – mam nadzieję – pojawią się wkrótce :-).
Poprzedni dokument Następny dokument
Własne kontrolki, cz. 2 ProgressBar