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

Obszar statusu (Tray)

[lekcja] Rozdział 34. Omówienie obszaru powiadomień na pasku zadań: tworzenie i usuwanie ikony, dymki i balonowe komunikaty.
Na pasku zadań Windowsa jest taki fajny obszar, zwany obszarem statusu (a popularnie - tray'em). Znajdują się tam najczęściej ikonki programów działających w tle. Pewnie nieraz zastanawiało cię, jak się dobrać do tego miejsca:

To jest właśnie tray (Windows 98)
To jest właśnie tray (Windows 98)

Dzięki temu artykułowi stanie się to proste ;-).

Ikonka

Wszelkie zadania związane z ikonkami w tray'u (dodawanie, modyfikowanie, usuwanie) wykonuje funkcja Shell_NotifyIcon. Jej składnia jest bardzo prosta, pobiera ona tylko rodzaj operacji do wykonania oraz adres struktury typu NOTIFYICONDATA. Struktura ta zawiera wszystkie niezbędne informacje o ikonce. W plikach nagłówkowych naszego SDK zadeklarowana ona jest następująco:

C/C++
typedef struct _NOTIFYICONDATA
{
   
DWORD cbSize;
   
HWND hWnd;
   
UINT uID;
   
UINT uFlags;
   
UINT uCallbackMessage;
   
HICON hIcon;
   
char szTip[ 64 ];
} NOTIFYICONDATA, * PNOTIFYICONDATA;

Przeznaczenia większości tych pól nietrudno się domyślić. Podobnie jak w przypadku innych struktur charakterystycznych dla WinAPI, pole cbSize należy ustawić na rozmiar struktury, pobrany operatorem sizeof. Jako hWnd podajemy uchwyt okna, które będzie otrzymywało komunikaty związane z ikonką - zazwyczaj jest to główne okno naszego programu. Pole uID to identyfikator ikonki, zaś uFlags będzie określało, które z pozostałych trzech pól struktury są w danym momencie aktywne.
 
Bierzmy się zatem do dzieła, czyli do umieszczenia ikony naszego programu na tray'u. Najpierw, jak zwykle, wypełnimy strukturę:

C/C++
LPSTR sTip = "Moja własna ikonka";
NOTIFYICONDATA nid;

nid.cbSize = sizeof( NOTIFYICONDATA );
nid.hWnd = hwnd;
nid.uID = ID_TRAY1;
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
nid.uCallbackMessage = CMSG_TRAY1;
nid.hIcon = LoadIcon( NULL, IDI_APPLICATION );
lstrcpy( nid.szTip, sTip );

Teraz niezbędne wyjaśnienia. Użyliśmy wszystkich trzech możliwych flag, żeby za jednym zamachem ustawić ikonkę, tooltipa (czyli napis, który się wyświetla po najechaniu na ikonkę myszą) oraz kod specjalnego komunikatu (o nim później). Oczywiście stałe ID_TRAY1 oraz CMSG_TRAY1 musimy sobie sami zdefiniować:

C/C++
#define ID_TRAY1   601
#define CMSG_TRAY1 0x8001

Twórcy Windowsa nie byliby sobą, gdyby od czasu do czasu nie zrobili programistom jakiegoś kawału, i oto mamy go - pole szTip nie jest wskaźnikiem (jak w przypadku większości tego typu pól w WinAPI), lecz tablicą o stałym rozmiarze64 znaków, dlatego wypełniamy ją przy pomocy funkcji lstrcpy.
 
Wreszcie ostatnia rzecz: zakładam, że powyższa struktura będzie wypełniana już PO utworzeniu głównego okna programu, o uchwycie hwnd. Zakładam również, że wincl to nazwa struktury typu WNDCLASSEX, która była użyta do rejestracji klasy okna i zawiera poprawny uchwyt do ikony.
 
Po tych przygotowaniach możemy wreszcie dodać ikonę do obszaru statusu:

C/C++
BOOL r;
r = Shell_NotifyIcon( NIM_ADD, & nid );
if( !r ) MessageBox( hwnd, "No niestety, z ikonki nici...", "Łeeee...", MB_ICONEXCLAMATION );

Wszystko pięknie, ikonka widnieje sobie na tray'u:

Hmm, powiedzmy, że się udało :-P (Windows 98)
Hmm, powiedzmy, że się udało :-P (Windows 98)

Trochę nam się co prawda pokaszaniła ta ikona, ale dzięki temu mamy nauczkę na przyszłość: jeden z kolorów jest najwyraźniej używany jako kolor maski, więc należy się liczyć z tym, że na tray'u nie będzie go widać.
 
Poza tym drobiazgiem wszystko jest jednak OK. Zadbajmy jeszcze o to (programiści lubią sobie o tym zapominać), żeby nasza ikonka samoczynnie usuwała się w momencie zniszczenia głównego okna:

C/C++
case WM_DESTROY:
{
   
NOTIFYICONDATA nid;
   
nid.cbSize = sizeof( NOTIFYICONDATA );
   
nid.hWnd = hwnd;
   
nid.uID = ID_TRAY1;
   
nid.uFlags = 0;
   
   
Shell_NotifyIcon( NIM_DELETE, & nid );
   
   
PostQuitMessage( 0 );
}
break;

Oczywiście w przypadku usuwania ikony nie musimy zawracać sobie głowy polami hIcon, uCallbackMessage i szTip, dlatego też ustawiliśmy uFlags na 0. Teraz ikonka będzie znikała po wyjściu z programu (wcześniej działo się to dopiero po najechaniu na nią myszą).

Komunikaty


Teraz pora zająć się praktyczniejszym wykorzystaniem takich ikonek. Jak wiadomo, zwykle kliknięcie prawym przyciskiem myszy na takiej ikonce rozwija menu kontekstowe, z którego można wybrać pewne opcje programu (np. w WinAmp-ie można w ten sposób włączyć lub zatrzymać odtwarzanie, zmieniać skórki i wtyczki oraz wiele innych rzeczy). Zwykle też pojedyncze kliknięcie lewym przyciskiem przywołuje program na pierwszy plan (jeśli wcześniej działał on w tle). My zrobimy sobie to ostatnie; sprawimy, że po zminimalizowaniu naszego głównego okienka będzie ono znikało z głównego obszaru paska zadań i pojawiało się w tray'u.
 
Przede wszystkim musimy teraz sprawić, żeby po zrzuceniu do paska zadań okienko znikało z niego. Pomoże nam w tym znana nam dobrze funkcja ShowWindow. Umieścimy ją w części kodu odpowiedzialnej za obsługę komunikatu WM_SIZE. Tam będziemy sprwdzać, czy okienko jest minimalizowane (wtedy wywołamy ShowWindow z parametrem SW_HIDE), czy nie:

C/C++
case WM_SIZE:
{
   
if( wParam == SIZE_MINIMIZED )
   
{
       
ShowWindow( hwnd, SW_HIDE );
       
// 1
   
}
   
else
   
{
       
ShowWindow( hwnd, SW_SHOW );
       
// 2
   
}
}
break;

W miejscach oznaczonych komentarzami wstawiamy ponadto: 1) kod dodający ikonkę do tray'a, 2) kod usuwający tę ikonkę. Warto zwrócić uwagę, że kod usuwający ikonkę z tray'a powinien wystąpić zarówno w powyższym, oznaczonym miejscu, jak i tam, gdzie wstawialiśmy go wcześniej, czyli w obsłudze komunikatu WM_DESTROY, przy czym w tym drugim miejscu musimy teraz sprawdzić, czy okno jest zminimalizowane (bo tylko wtedy będzie sens usuwania ikony). Tak więc komunikat WM_DESTROY będziemy teraz obsługiwać tak:

C/C++
case WM_DESTROY:
{
   
if( IsIconic( hwnd ) )
   
{
       
//
   
}
   
   
PostQuitMessage( 0 );
}
break;

 
Nowością jest funkcja IsIconic, która sprawdza, czy okno jest zminimalizowane - tylko wówczas pokazywana jest w naszym programie ikonka na tray'u. Równie dobrze moglibyśmy w tym przypadku użyć funkcji IsWindowVisible.
 
Teraz pora na pozostałą część zadania - obsłużenie kliknięcia na ikonce. Do tej pory bowiem po zminimalizowaniu naszego okna nie dało się już bez stosowania tzw. cudów przywrócić do normalnego stanu - mogliśmy jedynie wcisnąć Ctrl+Alt+Del i wykopać program z pamięci siłą. Teraz sprawimy, by kliknięcie na ikonę sprowadzało okno z powrotem na pulpit.
 
Wykorzystamy w tym celu zdefiniowany przez nas komunikat CMSG_TRAY1. Dlaczego nadaliśmy jej akurat taką wartość? Żeby nasz komunikat dało się odróżnić od tych systemowych - wszystkie numery od 0x8000 do 0xBFFF są do dyspozycji użytkownika jako "prywatne" komunikaty do użytku wewnątrz danej aplikacji.
 
Pozostaje nam teraz jedynie obsłużenie kliknięcia na naszej ikonce. Parametr wParam komunikatu CMSG_TRAY1 będzie zawierał identyfikator ikonki (w naszym przypadku ID_TRAY1), natomiast lParam - rodzaj komunikatu (w przypadku kliknięcia lewym przyciskiem - np. WM_LBUTTONDOWN). Obsługa może wyglądać jakoś tak:

C/C++
case CMSG_TRAY1:
{
   
if( wParam == ID_TRAY1 )
   
if( lParam == WM_LBUTTONDOWN )
       
 ShowWindow( hwnd, SW_RESTORE );
   
}
break;

Ważne jest tutaj sprawdzenie, od której ikonki pochodzi komunikat (jeśli dodaliśmy kilka ikon; od "obcych" ikon komunikaty nie przychodzą) oraz jakie zdarzenie nam oznajmia. Szczególnie na to drugie należy zwrócić uwagę; gdybyśmy nie sprawdzili wartości lParam, to nasze okno pojawiałoby się nawet po samym najechaniu myszą na ikonę (co jest efektem raczej niepożądanym).

Balonowe Komunikaty

Każdy użytkownik Windowsa zapewne wie, że w tray'u pojawiają się także takie balonowe komunikaty informujące o różnych bzdetach w stylu "Dostępne są nowe aktualizacje". Nic nie stoi jednak na przeszkodzie (przynajmniej w teorii ;-)) by takie komunikaty wykorzystać w naszej aplikacji.

Komunikaty balonowe działają na Windowsie 2000, XP, Vista i Seven czyli praktycznie na każdym w miarę nowym komputerze. Wróćmy do struktury NOTIFYICONDATA. Przedstawiona ona została już na początku artykułu, jednak jest to wersja dla Windows 98. Nowe Windowsy mają "lepszą" wersję tej struktury. Oto jak mniej więcej jest ona zadeklarowana:

C/C++
typedef struct _NOTIFYICONDATA {
   
DWORD cbSize;
   
HWND hWnd;
   
UINT uID;
   
UINT uFlags;
   
UINT uCallbackMessage;
   
HICON hIcon;
   
#if _WIN32_IE >= 0x0500
   
CHAR szTip[ 128 ];
   
DWORD dwState;
   
DWORD dwStateMask;
   
CHAR szInfo[ 256 ];
   
union {
       
UINT uTimeout;
       
UINT uVersion;
   
} DUMMYUNIONNAME;
   
CHAR szInfoTitle[ 64 ];
   
DWORD dwInfoFlags;
   
#else
   
CHAR szTip[ 64 ];
   
#endif
    #if _WIN32_IE >= 0x600
   
HICON hBalloonIcon;
   
GUID guidItem;
   
#endif
} NOTIFYICONDATA, * PNOTIFYICONDATA;
Jeżeli na komputerze użytkownika jest Internet Explorer w wersji 5 lub wyższej (czyli praktycznie na każdym komputerze z Windows) to możemy korzystać z balonowych komunikatów. Żeby je uaktywnić, na początku programu definiujemy wersję IE taką dyrektywą:

C/C++
#define _WIN32_IE 0x0600

Teraz mamy do dyspozycji dodatkowe pola struktury NOTIFYICONDATA. Oto pola, które będą nas interesować:

Struktura NOTIFYICONDATA:
Argument Znaczenie
szTip Tekst pokazujący się po najechaniu na ikonkę. Mamy do dyspozycji 128 znaków (a nie 64).
szInfo tekst, który wyświetli się w balonie. Maksymalnie 256 znaków.
szInfoTitle Tytuł komunikatu balonowego. Maksymalnie 64 znaki.
hIcon Ikonka komunikatu balonowego. (Ta sama co ikonka w tray'u)
dwInfoFlags różne flagi

W parametrze dwInfoFlags możemy wstawić różne flagi, oto kilka najczęściej używanych:

Flagi dwInfoFlags:
Flaga Znaczenie
NIIF_NONE Brak ikony.
NIIF_INFO Ikona informacji.
NIIF_WARNING Ikona ostrzeżenia
NIIF_ERROR Ikona błędu
NIIF_USER Ikonka zdefiniowana w hIcon czyli ta sama co w tray'u.
NIIF_NOSOUND Nie będzie charakterystycznego dźwięku podczas wyświetlania komunikatu.
NIIF_LARGE_ICON Windows Vista i nowsze Windowsy - będą używane duże wersje ikon.
NIIF_RESPECT_QUIET_TIME Windows 7 - Komunikat nie pojawi się w tzw. czasie cichym czyli przez pierwszą godzinę od pierwszego włączenia kompa.

Niektóre z tych flag są zdefiniowane w zależności od wartości makra _WIN32_IE, innych w ogóle może nie być w starszych wersjach SDK, można więc na początku programu (przed #include <windows.h>) dorzucić taki kod:

C/C++
#define NIIF_NONE 0x00000000
#define NIIF_INFO 0x00000001
#define NIIF_WARNING 0x00000002
#define NIIF_ERROR 0x00000003
#define NIIF_USER 0x00000004
#define NIIF_NOSOUND 0x00000010
#define NIIF_LARGE_ICON 0x00000020
#define NIIF_RESPECT_QUIET_TIME 0x00000080

#define NIF_INFO 0x00000010

Teraz możemy już zrobić nasz balonowy komunikat:

C/C++
LPSTR sTip = "Moja własna ikonka";
LPSTR sTytul = "Mój własny komunikat";
LPSTR sOpis = "To jest mój własny komunikat balonowy";
NOTIFYICONDATA nid;

nid.cbSize = sizeof( NOTIFYICONDATA );
nid.hWnd = hwnd;
nid.uID = ID_TRAY1;
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO;
nid.uCallbackMessage = CMSG_TRAY1;
nid.hIcon = LoadIcon( NULL, IDI_APPLICATION );
nid.dwInfoFlags = NIIF_WARNING; // ikonka ostrzeżenie
lstrcpy( nid.szTip, sTip );
lstrcpy( nid.szInfoTitle, sTytul );
lstrcpy( nid.szInfo, sOpis );

Kod raczej nie jest skomplikowany. Na początku dorzuciliśmy flagę NIF_INFO, żeby uaktywnić tą część struktury, która odpowiada za balonowe komunikaty. Do pola dwInfoFlags wrzucamy flagę NIFF_WARNING aby nasz balonik miał ikonkę ostrzeżenia. Potem kopiujemy zawartość stringów do struktury. Teraz tylko trzeba włożyć naszą ikonkę do tray'a:

C/C++
BOOL r;
r = Shell_NotifyIcon( NIM_ADD, & nid );
if( !r ) MessageBox( hwnd, "No niestety, z ikonki nici...", "Łeeee...", MB_ICONEXCLAMATION );

i już możemy podziwiać rezultaty:

Komunikat balonowy w trayu (Windows Vista)
Komunikat balonowy w trayu (Windows Vista)

Obsługa balonowych komunikatów

Jak zapewne zauważyłeś, naszemu komunikatowi czegoś brakuje. Przecież w "profesjonalnych" aplikacjach, jak choćby Windows Update, po kliknięciu na dymek "Dostępne są nowe aktualizacje" wyświetla się okno, gdzie możemy instalować aktualizacje. Po kliknięciu naszego dymka nie dzieje się nic.

Najwyższy czas, żeby coś zaczęło się dziać. Jeżeli w naszym dymku wystąpi jakieś zdarzenie (np. kliknięcie, zamknięcie, itp.) to system wysyła do naszego programu odpowiednie powiadomienie. Znajdziemy je w komunikacie (zdefiniowanym w polu uCallbackMessage) w niższym słowie parametru lParam. Żeby go wydobyć można użyć makra LOWORD. Najpierw jednak omówimy powiadomienia, które system może do naszego programu wysłać:

Powiadomienia związane z balonowym komunikatem (dymkiem) w trayu:
Powiadomienie Znaczenie
NIN_BALLOONSHOW Wysyłane, kiedy dymek się pokazuje (dymki są ustawiane w kolejce więc nie muszą się pokazać od razu)
NIN_BALLOONHIDE Wysyłane, kiedy dymek znika. Nie dotyczy zniknięcia przez kliknięcie w balonik ani zniknięcia po upływie jakiegoś czasu bez żadnej akcji. W Windows 7 wysyłane jest, gdy ustawiliśmy flagę NIIF_RESPECT_QUIET_TIME i aktualnie trwa Czas cichy a próbujemy wyświetlić komunikat.
NIN_BALLOONTIMEOUT Wysyłane, gdy dymek znika z powodu upływu jakiegoś czasu bez żadnej akcji.
NIN_BALLOONUSERCLICK Wysyłane, gdy user kliknie w dymek (chyba najczęściej używane)

Możliwe, że w twojej wersji SDK nie będzie definicji tych powiadomień. W takim wypadku powinieneś wstawić taki kod na początku programu:

C/C++
#define NIN_BALLOONSHOW 0x00000402
#define NIN_BALLOONHIDE 0x00000403
#define NIN_BALLOONTIMEOUT 0x00000404
#define NIN_BALLOONUSERCLICK 0x00000405

Jak już wspomniałem wcześniej, powiadomienie wysyłane jest w niższym słowie parametru lParam. Oto jak takie powiadomienia obsługiwać (zakładam że pole uCallbackMessage ma wartość CMSG_TRAY1 z początku artykułu, a ID_TRAY1 to ID ikonki):

C/C++
LRESULT CALLBACK WindowProcedure( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) {
   
switch( message ) {
   
case CMSG_TRAY1:
       
switch( wParam ) {
       
case ID_TRAY1: // dla naszej ikonki
           
switch( LOWORD( lParam ) ) {
           
case NIN_BALLOONUSERCLICK: // gdy user kliknie balonik
               
MessageBox( hwnd, "Fajnie że kliknąłeś mój balonik.", ";-)", MB_ICONEXCLAMATION );
               
break;
           
case NIN_BALLOONTIMEOUT: // gdy balonik zgaśnie samoczynnie
               
MessageBox( hwnd, "Dlaczego nie kliknąłeś mojego balonika, co ?!", ";-(", MB_ICONEXCLAMATION );
               
break;
               
               
// tu inne case'y powiadomień np.: WM_LBUTTONDOWN, itp.
               
               
default:
               
               
// tu instrukcje nie związane z powiadomieniami
               
               
break;
           
}
           
break;
           
           
// tu case'y dla innych ikonek
           
       
}
       
break;
       
       
// tu inne case'y komunikatów
       
   
}
   
return 0;
}

W powyższym kodzie mamy podaną przykładową obsługę komunikatu balonowego. W komunikacie CMSG_TRAY1 w części dotyczącej naszej ikonki (case ID_TRAY1:) sprawdzamy, jakie powiadomienie zostało wysłane (switch (LOWORD(lParam)) { ... }). Jeżeli nie wysłano żadnego powiadomienia, to instrukcja LOWORD(lParam) będzie mieć wartość 0. Wtedy wykona się kod oznaczony "tu instrukcje nie związane z powiadomieniami". Możemy dodać też inne case'y, np. WM_LBUTTONDOWN do obsługi minimalizacji okienka do tray'a z poprzedniego rozdziału.

W naszym przykładzie po kliknięciu dymka, wyświetlają się głupawe komunikaty. Nic nie stoi na przeszkodzie, by wykorzystać nasz dymek do różnych niecnych celów ;-). W dziale
[[Download|download]]
 znajdziesz gotowy nagłówek wypełniający ewentualne braki w twojej wersji SDK.

Kompatybilność pod XP i Vistą

W systemie Windows Vista można umieścić ikonę w balonowym komunikacie niezależnie od ikonki w tray'u, umieszczając uchwyt do ikony w polu hBalloonIcon. Jeżeli używamy pola hBallonIcon to nasz komunikat nie wyświetli się pod XP ;-(. Ponadto flaga NIIF_USER działa dopiero od Windows XP SP2.
Nie wszystkie kompilatory (np. Dev-C++) posiadają strukturę z tym polem. Jeżeli używasz Dev-C++ to ściągnij z
[[Download|downloadu]]
 plik związany z tym artykułem, gdzie znajdziesz poprawiony nagłówek shellapi.h dla kompilatora Dev-C++. Użytkownicy Visuala nie muszą się tym przejmować - u nich to pole jest.
Tak więc co zrobić, żeby nasz balonik działał zarówno pod i XP jak i Vistą? Moglibyśmy po prostu korzystać z wersji struktury NOTIFYICONDATA pod XP, ale pod Vistą można mieć w balonie niezależną ikonkę, a my bardzo taką chcemy ;-). Żeby to osiągnąć musimy w zależności od wersji DLL shell'a podać różny rozmiar struktury NOTIFYICONDATA. Najpierw jedna trzeba poznać wersję tej DLL'ki. Robimy to za pomocą funkcji DllGetVersion. Nie jest ona częścią API więc musimy ją "ręcznie" załadować z DLL'ki, której wersję chcemy poznać. Przyjmuje ona jeden argument, jakim jest wskaźnik na strukturę DLLVERSIONINFO. Oto ona:

C/C++
typedef struct _DllVersionInfo {
   
DWORD cbSize;
   
DWORD dwMajorVersion;
   
DWORD dwMinorVersion;
   
DWORD dwBuildNumber;
   
DWORD dwPlatformID;
} DLLVERSIONINFO;

Pole cbSize to rozmiar struktury. Najważniejsze są jednak pola dwMajorVersion, dwMiniorVersion i dwBuildNumber. To one przechowują informacje o wersji. Teraz możemy się zabrać do pobrania informacji o wersji:

C/C++
DLLVERSIONINFO dvi;
ZeroMemory( & dvi, sizeof( DLLVERSIONINFO ) );
dvi.cbSize = sizeof( DLLVERSIONINFO );

DllGetVersion( & dvi );

Mamy już wersję systemu, teraz w zależności od niej powinniśmy dać rozmiar strukturze NOTIFYICONDATA. Ale jaki rozmiar? Zostały one zebrane w tabelce:

Wersja DLL System Rozmiar NOTIFYICONDATA
6.0.6 Windows Vista i nowsze Windowsy sizeof(NOTIFYICONDATA)
6.0 Windows XP NOTIFYICONDATA_V3_SIZE
5.0 Windows 2000 NOTIFYICONDATA_V2_SIZE
Mniej niż 5.0 Windows 98 i 95 NOTIFYICONDATA_V1_SIZE

Jeżeli w twojej wersji SDK te rozmiary nie są zdefiniowane to pobierz z
[[Download|download]]
 plik związany z tą częścią kursu. Tam znajdziesz odpowiedni plik nagłówkowy.

To teraz zobaczmy jak to wygląda w praktyce:

C/C++
// ten nagłówek jest potrzebny dla DLLVERSIONINFO
#include <shlwapi.h>

HINSTANCE hDll;
LPCTSTR lpszDllName = TEXT( "Shell32.dll" );

hDll = LoadLibrary( lpszDllName );

DLLGETVERSIONPROC DllGetVersion;

if( hDll != NULL ) {
   
DllGetVersion =( DLLGETVERSIONPROC ) GetProcAddress( hDll,
   
"DllGetVersion" );
   
   
if( DllGetVersion == NULL ) {
       
MessageBox( hwnd, "Nie można załadować funkcji!", "Bład!", MB_ICONSTOP );
       
return 0;
   
}
   
}
else {
   
MessageBox( hwnd, "Nie można załadować DLL'ki!", "Bład!", MB_ICONSTOP );
   
return 0;
}

DLLVERSIONINFO dvi;
ZeroMemory( & dvi, sizeof( DLLVERSIONINFO ) );
dvi.cbSize = sizeof( DLLVERSIONINFO );

( * DllGetVersion )( & dvi );

LPSTR sTip = "Moja własna ikonka";
LPSTR sTytul = "Mój własny komunikat";
LPSTR sOpis = "To jest mój własny komunikat balonowy";
NOTIFYICONDATA nid;
ZeroMemory( & nid, sizeof( NOTIFYICONDATA ) );

nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO;

if(( dvi.dwMajorVersion == 6 ) &&(( dvi.dwMinorVersion == 0 ) &&( dvi.dwBuildNumber >= 6000 ) // VISTA
||( dvi.dwMinorVersion >= 1 ) ) ) { // i SEVEN
   
   
nid.cbSize = sizeof( NOTIFYICONDATA );
   
nid.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON;
   
nid.hBalloonIcon = LoadIcon( hInstance, MAKEINTRESOURCE( 666 ) );
   
} else if(( dvi.dwMajorVersion == 6 ) &&( dvi.dwMinorVersion == 0 ) ) { // XP
   
   
nid.cbSize = NOTIFYICONDATA_V3_SIZE;
   
if( dvi.dwPlatformID > 2800 ) { // SP2 i nowsze SP
       
nid.dwInfoFlags = NIIF_USER;
   
} else { // SP1 i bez SP
       
nid.dwInfoFlags = NIIF_WARNING;
   
}
   
}
else if(( dvi.dwMajorVersion == 5 ) &&( dvi.dwMinorVersion == 0 ) ) { // 2000
   
   
nid.cbSize = NOTIFYICONDATA_V2_SIZE;
   
nid.dwInfoFlags = NIIF_WARNING;
   
} else { // 98
   
   
nid.cbSize = NOTIFYICONDATA_V1_SIZE;
   
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; // 98 w ogóle nie obsługuje baloników
}

nid.hWnd = hwnd;
nid.uID = ID_TRAY1;
nid.uCallbackMessage = CMSG_TRAY1;
nid.hIcon = LoadIcon( NULL, IDI_APPLICATION );
lstrcpy( nid.szTip, sTip );
lstrcpy( nid.szInfoTitle, sTytul );
lstrcpy( nid.szInfo, sOpis );

BOOL r;
r = Shell_NotifyIcon( NIM_ADD, & nid );
if( !r ) MessageBox( hwnd, "No niestety, z ikonki nici...", "Łeeee...", MB_ICONEXCLAMATION );

FreeLibrary( hDll );

Kod jest dość przydługi, ale działa (co w Windowsie wcale nie jest takie oczywiste ;-)). No może nie do końca. Użyliśmy ikonki o id 666 z zasobów. Musimy więc stworzyć odpowiedni plik *.rc:

C/C++
666 ICON "bomb.ico"

Teraz nadszedł czas na wyjaśnienia. Na początku ładujemy funkcję DllGetVersion z Shell32.dll. Potem tworzymy strukturę DLLVERSIONINFO i wczytujemy do niej informacje o wersji funkcją DllGetVersion. Później przygotowujemy zmienne na napisy w balonie i strukturę NOTIFYICONDATA. Następnie mamy dużo instrukcji warunkowych, które w zależności od wersji systemu wypełniają strukturę NOTYFIICONDATA w róźny sposób. By rozpoznać, czy Windows jest SP2 lub nowszy sprawdzamy parametr dwPlatformID. Liczba 2800 oznacza SP1, stąd każda większa będzie oznaczać kolejne SP (2900 - SP3). Następnie wstawiamy naszą ikonę do tray'a i zwalniamy DLL'kę.

Dzięki naszym staraniom komunikat działa poprawnie pod większością Windowsów (kolejno Seven, Vista, XP SP3, XP SP1):

Komunikat balonowy (Windows Seven)
Komunikat balonowy (Windows Seven)

Komunikat balonowy (Windows Vista)
Komunikat balonowy (Windows Vista)

Komunikat balonowy (Windows XP SP3)
Komunikat balonowy (Windows XP SP3)

Komunikat balonowy (Windows XP SP1)
Komunikat balonowy (Windows XP SP1)
Poprzedni dokument Następny dokument
ListView Podpowiedzi (Tooltips)