Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Artur 'Mrowqa' Jamro
WinAPI i Windows

[WinAPI, C++] Obsługa paska zadań (taskbar)

[artykuł] Artykuł opisuje obsługę paska zadań pod kątem systemu operacyjnego Microsoft Windows 7.

Wstęp

W niniejszym dokumencie pokażę Wam jak oprogramować pasek zadań na platformie Windows. Jaki jest tego sens – zapytałbyś zapewne. W aplikacjach użytkowych jest to bardzo pomocna rzecz. Dzięki niej możemy powiadomić użytkownika o aktualnym stanie aplikacji i umożliwić sterowanie aplikacją bez konieczności wyciągania okna na wierzch. Dokument zawiera opis jak oprogramować:
  • „miganie” ikonki* aplikacji na pasku zadań (taskbar);
  • dodawanie i odejmowanie ikon na pasku zadań (taskbar);
  • pasek postępu (progressbar) na pasku zadań (dostępne w Windows 7),
  • przyciski na miniaturce okna (dostępne w Windows 7),
  • dymek (tooltip) i obszar podglądu okna na miniaturce (dostępne w Windows 7).
Wyjaśnienie:
* chodzi o kartę; składa się ona z ikonki i tekstowej etykiety. Użyłem wyrazu ikona, ponieważ lepiej brzmi :)

Miganie aplikacji na pasku zadań

Zapewne nieraz widziałeś jak po ściągnięciu jakiegoś pliku z Internetu ikona przeglądarki się podświetla, a potem wraca do poprzedniego stanu i tak w kółko. Nazwałem to sobie „miganiem”. Wygląda to następująco:
Miganie aplikacji na pasku zadań
Miganie aplikacji na pasku zadań
Zatem, jak to osiągnąć? Służą do tego dwie funkcje FlashWindow oraz FlashWindowEx. Ta pierwsza jest prostsza, jednak my zajmiemy się tą drugą ;). Jej deklaracja wygląda następująco:
C/C++
BOOL WINAPI FlashWindowEx(
/*__in*/ PFLASHWINFO pfwi
);
Jak zapewne zauważyłeś – przyjmuje ona tylko jeden argument. Jest to wskaźnik na strukturę FLASHWINFO. Przyjrzyjmy się niej.
C/C++
typedef struct
{
    UINT cbSize;
    HWND hwnd;
    DWORD dwFlags;
    UINT uCount;
    DWORD dwTimeout;
} FLASHWINFO, * PFLASHWINFO;
Jak widać nie jest duża :). Oto zestawienie składników i ich znaczenia:
Pole strukturyZnaczenie
cbSizeRozmiar struktury w bajtach. Należy tu wpisać
sizeof( FLASHWINFO )
.
hwndUchwyt do okna, które ma migać. Musi być otwarte bądź zminimalizowane.
dwFlagsOkreśla status migania.
uCountOkreśla ile razy okno ma mignąć.
dwTimeoutCzas określający ile trwa jedno mignięcie w milisekundach. Jeśli jest równe zero zostanie użyta wartość domyślna.
Możliwe flagi określające status migania:
  • FLASHW_CAPTION – powoduje miganie okna;
  • FLASHW_TRAY – powoduje miganie ikonki na pasku zadań;
  • FLASHW_ALL – miganie okna i ikony na pasku zadań. Jest to połączenie flag FLASHW_CAPTION oraz FLASHW_TRAY;
  • FLASHW_TIMERNOFG – powoduje miganie okna, dopóki nie wyjdzie ono na wierzch;
  • FLASHW_TIMER – powoduje ciągłe miganie do czasu ustawienia flagi FLASHW_STOP;
  • FLASHW_STOP – zatrzymuje miganie okna; system przywraca okno do jego oryginalnego stanu.
Część teoretyczną mamy za sobą. „Nareszcie!” – odrzekłbyś ze szczęściem :). W praktyce jest to dużo łatwiejsze, sam się przekonasz :P. Zbierzmy więc całą dotychczasową wiedzę i napiszmy krótki podsumowujący kod:
C/C++
FLASHWINFO obj;
ZeroMemory( & obj, sizeof( FLASHWINFO ) ); // czyścimy strukturę
obj.cbSize = sizeof( FLASHWINFO );
obj.dwFlags = FLASHW_TRAY; // miganie paska
obj.hwnd = g_hwnd; // ustawiamy główne okno
obj.uCount = 3; // mignie nam 3 razy
obj.dwTimeout = 300; // a jedno mignięcie będzie trwać 300ms (czyli 0,3 sekundy)
FlashWindowEx( & obj ); // i wywołujemy funkcję :)
Ten kod spowoduje nam trzykrotne mignięcie okna (każde trwające 300ms), a następnie nasza ikonka pozostanie w stanie podświetlonym. Krótki kod, wspaniały (:P) efekt… To nie takie trudne, nieprawdaż? :)

Uwagi

Nie wiem czemu, ale ta funkcja nie działa na oknach dialogowych. (W każdym razie mi :P.)
Link do dokumentacji MSDN:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms679347(v=vs.85).aspx

Dodawanie i odejmowanie ikon na pasku zadań

Spotkałeś się zapewne z aplikacjami, które mimo, że „są włączone tylko jeden raz” (mają aktualnie jedną instancję) to na pasku zadań mają wiele ikon bądź nie mają żadnej. Jest to ciekawa i użyteczna funkcjonalność, którą nieraz byłoby warto „wpleść” w naszą aplikację. „Jak to zrobić?” – niecierpliwiła by się część czytelników. Jest to łatwe do zrobienia, lecz jest jedno „ale”. Rzekoma funkcjonalność jest dostępna dopiero od Windows 2000. Ponadto musimy zarejestrować w systemie komunikat. „Wiedziałem, że to zbyt piękne by było prawdziwe” – pomyślałeś zapewne. Nie przejmuj się, damy radę :)

Sprawdzenie wersji systemu

Wiem, że wiesz jakiego masz Windowsa, ale Twój program nie wie :P. Co z niego za idiota, nie? W końcu to nie ja go stworzyłem :D. Musimy więc napisać kod, który sprawdzi wersję używanego systemu. Skorzystamy ze struktury OSVERSIONINFO. Ważniejsze jej pola to:
Pole strukturyZnaczenie
dwOSVersionInfoSizeRozmiar struktury w bajtach. Należy tu wpisać
sizeof( OSVERSIONINFO )
.
dwMajorVersionGłówny numer wersji.
dwMinorVersionPomniejszy numer wersji.
szCSDVersionŁańcuch znaków zawierający pełny numer wersji.
My pobierzemy wersję i porównamy ją z Windowsem 7, z tego powodu, iż będzie to nam potrzebne do dalszych części artykułu :). Co ciekawe, Windows 7 nie jest w wersji 7 tylko 6.1! :P. Będziemy więc zmuszeni do sprawdzenia wersji głównej i pomniejszej.

Dane pobierzemy funkcją GetVersionEx:
C/C++
OSVERSIONINFO osv;
ZeroMemory( & osv, sizeof( OSVERSIONINFO ) );
osv.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
GetVersionEx( & osv );
bool Win7lubNowszy =(( osv.dwMajorVersion == 6 ) &&( osv.dwMinorVersion >= 1 ) ) ||( osv.dwMajorVersion >= 7 );

Interfejs ITaskbarList

To jest sedno całego artykułu – ITaskbarList. Co ciekawsze, jest to prawdziwa klasa! Taka z metodami! Co jest rzadkością w WinAPI :P. (WinAPI było napisane w C, ten interfejs najwyraźniej w C++ :).) Spowodowanie aby ten interfejs działał jest nieco męczące...

Wskaźnik do interfejsu uzyskujemy w procedurze komunikatu, który wcześniej sami musimy zarejestrować :/. Oto przykład, który najlepiej sobie skopiować w całości:
C/C++
#include <Shobjidl.h> // dla ITaskbarList3
ITaskbarList3 * ptl;
//...
LRESULT CALLBACK WindowProcedure( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    static DWORD g_wmTBC =( DWORD ) - 1; // zmienna, przechowująca kod komunikatu
    if( msg == g_wmTBC ) // procedura obsługi naszego komunikatu :P
    {
        if( CoCreateInstance( CLSID_TaskbarList, NULL, CLSCTX_ALL, IID_ITaskbarList3,( LPVOID * ) & ptl ) != S_OK )
             Win7lubNowszy = false; // nie udało się uzyskać dostępu do interfejsu
       
    }
    else
    switch( msg )
    {
    case WM_CREATE:
        g_wmTBC = RegisterWindowMessage( "TaskbarButtonCreated" ); // rejestrujemy nasz komunikat
        break;
        // obsługa pozostałych komunikatów
    }
}
Jestem winny „troszeczkę” wyjaśnień :P. Użyliśmy ITaskbarList3, jest to potomek (klasa pochodna) klasy ITaskbarList. W wersji 3 doszedł pasek postępu i przyciski na miniaturce, dlatego od razu sobie go bierzemy :). Niestety jest on dostępny dopiero w Windows 7, więc jeśli chcesz możesz użyć ITaskbarList, bo już pierwsza wersja umożliwia nam zabawę z kartami na pasku zadań :). Jest już dostępna czwarta wersja interfejsu, ale nic ciekawego dla nas nie wnosi, więc po co z niej korzystać :)?

Zmienna przechowująca wartość komunikatu jest zmienną statyczną, ponieważ nie będziemy z niej korzystać po za tą funkcją.

„No dobra, ale czemu użyłeś if-a, a nie skorzystałeś ze switch-a do obsługi komunikatu?!”. Odpowiedź jest prosta – etykiety we wnętrzu switcha muszą być wartościami stałymi, a zmienna (w której przechowujemy kod komunikatu), jak sama nazwa wskazuje – zmienia się :P.

Dzięki CoCreateInstance uzyskujemy wskaźnik do interfejsu ITaskbarList. Gdy nam się uda – funkcja zwraca S_OK (ale nie taki do picia :D szkoda, co :P?). Gdyby jednak nam się nie udało – zmienną z wartością o systemie ustawiamy na false. Ta zmienna oznacza czy wolno nam użyć tego interfejsu... gdyby się okazało, że nie udało się pozyskać interfejsu ITaskbarList, np. przez starą wersję systemu to próba użycia tego interfejsu może skooczyd się tragicznie… :P. Wiem, że nazwa tej zmiennej nie jest trafna – była dla wcześniejszego przykładu :), zmień ją sobie jak chcesz, pozwalam Ci :P

No to to by było tyle na wstępie :P. W reszcie możemy się zająć za to co jest tematem tego artykułu.

Dodawanie i usuwanie kart

Doceniam Cię, że chciało Ci się czytać aż tyle. W końcu cierpliwość to cnota, nie :P? Teraz już bez dalszych przedłużeń poznajmy nasze funkcje (a konkretnie metody):
C/C++
HRESULT AddTab( HWND hwnd ); // dodaje kartę
HRESULT DeleteTab( HWND hwnd ); // usuwa kartę
HRESULT ActivateTab( HWND hwnd ); // aktywuje kartę
Widzisz jakie proste? To nie przywidzenia, opłacało się czekać :D.
Funkcja zwraca S_OK jeśli się powiedzie. Zapewne nie za bardzo wiesz co znaczy to „aktywuje kartę” – nie przejmuj się – ja też :D. Na MSDN jest napisane tylko tyle, że to wybrane okno (tylko jedno może być w danym czasie!) jest wyświetlane jako aktywne. Najprawdopodobniej chodzi o podgląd okna na żywo na miniaturce.
Użyjmy więc czym prędzej tego cudeńka na jakimś oknie dialogowym :)
C/C++
if( OSVersion ) // jak chcesz żeby Twoja aplikacja „pożyła” to nie zapominaj o tym!
{
    if( ptl->AddTab( hwnd ) == S_OK ) // należy pamiętać, że ptl to wskaźnik
    {
        ptl->ActivateTab( hwnd );
        ptl->DeleteTab( g_hwnd );
    }
}
Trzeba pamiętać, aby za każdym razem sprawdzać czy korzystamy z odpowiedniego systemu, by przypadkiem nie spowodować jakiegoś błędu, który by zakończył naszą aplikację. Zobaczmy efekt naszej pracy:
Niestety – nie podoba nam się ikonka okna (domyślna windowsowa), a skoro jesteśmy rządnymi wiedzy i jakości, więc postanawiamy to poprawić. Sprawa jest prosta – wystarczy wysład do okna odpowiedni komunikat.
C/C++
SendMessage( hwnd, WM_SETICON, ICON_SMALL,( LPARAM ) LoadIcon( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDI_DIALOG_ICON ) ) );
SendMessage( hwnd, WM_SETICON, ICON_BIG,( LPARAM ) LoadIcon( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDI_APP_ICON ) ) );
Argumentów się zapewne domyślisz :). Pierwszy komunikat ustawia ikonkę okna, drugi – ikonę aplikacji, czyli tą na pasku zadań. Gdybyśmy wysłali tylko pierwszy komunikat, a drugi już nie, to na pasku zadań ujrzelibyśmy ikonkę okna. Nasze okno po poprawie wygląda następująco:

Link do dokumentacji:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb774652(v=vs.85).aspx

Pasek postępu (progress bar)

Tu sprawa jest stosunkowo prosta :). Ten pasek wygląda mniej więcej tak:
Pasek postępu (progress bar) - Windows 7
Pasek postępu (progress bar) - Windows 7
Tym razem mamy dwie, przyjemne metody klasy ITaskbarList3. Obie zwracają S_OK w razie powodzenia. Pierwsza z nich to:
C/C++
HRESULT SetProgressState( HWND hOkno, TBPFLAG stan );
hOkno to uchwyt okna, którego karta znajduje się na pasku zadao.
Oto możliwe stany paska:
FlagaZnaczenieWygląd
TBPF_NOPROGRESSBrak paska
TBPF_INDETERMINATEStan nieokreślony
TBPF_NORMALStan normalny
TBPF_PAUSEDPauza
TBPF_ERRORBłąd
Kolejna funkcja to:
C/C++
HRESULT SetProgressValue( HWND hOkno, ULONGLONG aktualny_postep, ULONGLONG max_postep );
Jak widać kolejna funkcja z jasnymi argumentami :). Przykładowe wywołania:
C/C++
if( OSVersion )
{
    ptl->SetProgressState( hwnd, TBPF_NORMAL );
    ptl->SetProgressValue( hwnd, 4, 7 );
}
Spowoduje ustawienie paska na kolor zielony (normalny) i 4/7 długości.

Uwagi

Jeśli pasek jest w stanie TBPF_INDETERMINATE i wywołamy funkcję SetProgressValue to pasek przejdzie do stanu TBPF_NORMAL. Więcej uwag na MSDN.

Link do dokumentacji:
http://msdn.microsoft.com/en-us/library/windows/desktop/dd391692(v=vs.85).aspx

Przyciski na miniaturce okna

W Winindows 7 gdy najedziemy na ikonkę danej aplikacji znajdującej się na pasku zadań ukazuje nam się miniatura okna (podgląd). Winindows 7 wprowadza ciekawą funkcjonalność – przyciski na tej miniaturze. Możemy sterować aplikacją, która jest „zwinięta” na pasek! Jest to na pewno bardzo wygodne :). Oto jak coś takiego wygląda:
Do oprogramowania tego służą następujące funkcje:
C/C++
HRESULT ThumbBarAddButtons( HWND hwnd, UINT cButtons, LPTHUMBBUTTON pButton );
HRESULT ThumbBarUpdateButtons( HWND hwnd, UINT cButtons, LPTHUMBBUTTON pButton );
Tu twórcy tego interfejsu trochę namieszali... zamiast zrobić jedną funkcję - są dwie i trzeba wiedzieć, którą kiedy wywołać. W przeciwnym razie nie uzyskamy przycisków :(. Za pierwszym razem wywołuje się ThumbBarAddButtons, a za każdym następnym – ThumbBarUpdateButtons. Ta pierwsza funkcja jest jakby inicjalizująca. Tutaj drobna uwaga – za każdym razem, gdy okno zostanie usunięte, a następnie dodane przy pomocy AddTab oraz DeleteTab trzeba od nowa je inicjalizować, tzn. wywołać ThumbBarAddButtons. W przypadku powodzenia funkcje zwracają S_OK. Znaczenie argumentów:
  • hwnd – uchwyt do okna, na którym mają znaleźć się przyciski;
  • cButtons – liczba przycisków; max to 7;
  • pButton – wskaźnik na tablicę struktur THUMBBUTTON.
Oto struktura THUMBBUTTON:
C/C++
typedef struct THUMBBUTTON {
    THUMBBUTTONMASK dwMask;
    UINT iId;
    UINT iBitmap;
    HICON hIcon;
    WCHAR szTip[ 260 ];
    THUMBBUTTONFLAGS dwFlags;
} THUMBBUTTON, * LPTHUMBBUTTON;
Pole strukturyZnaczenie
dwMaskMaska określająca, które pola struktury wypełniliśmy (zawierają poprawne dane).
iIdUnikalne ID, takie jak mają zwykłe kontrolki, np. przyciski. Gdy przycisk zostanie kliknięty zostaje wysłany komunikat WM_COMMAND z ID kontrolki, tak jak przy zwykłych przyciskach :). Pole wymagane.
iBitmapIndeks liczony od zera bitmapy ustawionej przez ITaskbarList3::ThumbBarSetImageList.
hIconUchwyt do ikony.
szTipTekst tooltipa, który jest wyświetlany gdy kursor myszy znajduje się nad przyciskiem. Należy zwrócić uwagę, na to że jest to tablica, a nie wskaźnik (tekst najlepiej ustawić za pomocą algorytmu kopiującego).
dwFlagsFlagi określające stan przycisku.
Wartości, jakie może przyjąć maska:
  • THB_BITMAP
  • THB_ICON
  • THB_TOOLTIP
  • THB_FLAGS
Można oczywiście je łączyć operatorem alternatywy bitowej |. Określają one, które pola struktury wypełniliśmy. Znaczenia tych wartości chyba nie muszę objaśniać :).

FlagaZnaczenie
THBF_ENABLEDPrzycisk jest aktywny.
THBF_DISABLEDPrzycisk jest widoczny, ale nie reaguje na użytkownika; jakby wyłączony.
THBF_DISMISSONCLICKGdy przycisk zostanie naciśnięty, miniaturka okna się zamyka.
THBF_NOBACKGROUNDRamka obrazka nie jest rysowana; widoczny jest tylko sam obrazek.
THBF_HIDDENPrzycisk nie jest rysowany; nie jest widoczny dla użytkownika (jakby go nie było).
THBF_NONINTERACTIVEPrzycisk jest aktywny, ale nie jest animowany; może służyć np. jako powiadomienie.
Kod podsumowujący ten dział:
C/C++
void przepisz( LPWSTR dokad, LPWSTR skad )
{
    for(; * skad; skad++, dokad++ ) * dokad = * skad;
   
}
//...
if( OSVersion )
{
    THUMBBUTTON data;
    ZeroMemory( & data, sizeof( THUMBBUTTON ) );
    data.dwMask = THB_ICON | THB_TOOLTIP | THB_FLAGS;
    data.dwFlags = THBF_ENABLED;
    data.iId = ID_TBB;
    przepisz( data.szTip, L"Nasz tooltip :D" ); // szTip to tablica
    data.hIcon = LoadIcon( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDI_ICON ) );
    ptl->ThumbBarAddButtons( hwnd, 1, & data );
}
Ten kod spowoduje utworzenie jednego przycisku na miniaturce. Nie ładujemy tu bitmapy z tego powodu, że wystarczy sama ikona. Nawet gdybyśmy załadowali bitmapę i ikonę, to zostanie użyta ikona :P. Co do tooltipa... musieliśmy użyć funkcji przepisującej z tego względu, że pole szTip jest tablicą, a nie wskaźnikiem na rzekomą tablicę. Efekt naszej pracy:

Link do dokumentacji:
http://msdn.microsoft.com/en-us/library/windows/desktop/dd391692(v=vs.85).aspx

Tooltip i obszar podglądu okna na miniaturce

Na koniec dwie, prościutkie funkcje :). Czasem są nawet przydatne. Jedną określa się obszar okna, który ma być wyświetlany w podglądzie na miniaturce. Druga służy do ustawienia tekstu tooltipa (podpowiedzi). Jest on wyświetlany, gdy kursor myszy znajdzie się nad miniaturką okna. Wygląda to mniej więcej tak:
Jak można zauważyć tooltip jest wyświetlany nad miniaturką. Obszar okna wyświetlany na miniaturce został przycięty. Ot co możemy osiągnąć :). Deklaracja pierwszej metody interfejsu:
C/C++
HRESULT SetThumbnailTooltip( HWND hwnd, LPCWSTR pszTip );
Nie ma tu nic trudnego :). hwnd to uchwyt do okna, któremu ustawiamy tooltip. pszTip to wskaźnik na łańcuch znaków rozszerzonych (wchar_t*). Przyjrzyjmy się drugiej funkcji:
C/C++
HRESULT SetThumbnailClip( HWND hwnd, RECT * prcClip );
Pierwszy argument jak zapewne wiesz – jest to uchwyt okna, nad którego miniaturką pracujemy :). prcClip jest to natomiast wskaźnik na strukturę RECT, która zawiera informacje (współrzędne) o obszarze, który ma być wyświetlany na miniaturce okna. Obie funkcje w przypadku powodzenia zwracają S_OK. Przykładowe wywołania:
C/C++
if( OSVersion ) // nie zapomnij o tym! :)
{
    ptl->SetThumbnailTooltip( hwnd, L"Podgląd" ); // ustawienie tooltipu
    RECT rect;
    GetClientRect( hwnd, & rect ); // pobieramy wymiary okna
    rect.top = 10;
    rect.left += 5; // "obcinamy" sobie okno :D
    rect.right -= 5;
    rect.bottom = 115;
    ptl->SetThumbnailClip( hwnd, & rect ); // ustawiamy wyświetlany obszar
}

Link do dokumentacji:
http://msdn.microsoft.com/en-us/library/windows/desktop/dd391692(v=vs.85).aspx