Po přechodu na Windows 7 mě zaujala možnost zobrazování průběhu v titulku okna na hlavním panelu (Taskbaru). V tomto článku vám ukážu, jak ji dostat do aplikace i se starší verzí SDK. A pro majitele
starších Windows, aby také něco viděli, uděláme zobrazení průběhu (procent) pomocí tooltipu.
K zobrazování průběhu se používá rozhraní ITaskbarList3 (popis rozhraní). Toto rozhraní je definováno ve Windows SDK od verze 7.0, ale protože všechny moje dosavadní aplikace využívají starší verzi 6.1 (a Visual Studio 2008) a zatím je potřeba, aby běhaly i na Windows 2000 s posledním SP, tak jsem hledal způsob, jak mít v programu podporu pro zobrazování průběhu i se starší verzí SDK.
První, co mě napadlo, je nahrazení pouze hlavičkového souboru z nového SDK ve starém SDK, ale to se nesetkalo s úspěchem. Takže jsem na to šel jinak. Každé rozhraní je definováno v souboru .idl (IDL je zkratka pro Interface Definition Language - jazyk pro definici rozhraní komponent) a stejně tak i rozhraní ITaskbarList3. Jeho definice je uložena v souboru ShObjIdl.idl. Odtud jsem ji zkopíroval do vlastního souboru TaskbarList3.idl. Následně jsem provedl kompilaci MIDL kompilátorem, který mi vytvořil zdrojové a hlavičkové soubory potřebné pro začlenění rozhraní do programu.
Poznámka: Výsledný hlavičkový soubor jsem ještě mírně upravil, a to definice výčtových typů, aby vše fungovalo i na (mém) VS 2008.
Nyní můžeme vytvořit zkušební aplikace (základem bude vygenerovaná Win32 okenní aplikace). Do této aplikace přidáme například do menu položku (Soubor -> Start), kterou budeme spouštět zkušební vlákno. Reakcí na tuto volbu bude zavolání naší funkce OnStart, kde si jako parametr předáme handle hlavního okna. Úkolem této funkce bude spuštění testovacího vlákna (a zakázání položky Start, aby vlákno nešlo spustit vícekrát).
void OnStart(HWND hWnd)
{
DWORD dwThreadID;
HANDLE hThread;
EnableMenuItem(GetMenu(hWnd), ID_FILE_START,
MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
hThread = CreateThread(NULL, 0, ThreadProc, hWnd, 0, &dwThreadID);
assert(hThread != NULL);
}
Nyní se pustíme do samotného vlákna. V něm chceme pouze demonstrovat funkci zobrazení, takže provedeme jednoduchý průběh 0 až 99 v cyklu, který zpomalíme funkcí Sleep. Nejprve se pokusíme získat pointer na rozhraní ITaskbarList3. Pokud budeme aplikaci spouštět na Windows 7, tak pointer dostaneme. Na starším máme smůlu, protože takové rozhraní v systému neexistuje. Nemusíme to tedy nijak zvlášť rozlišovat, takže nám bude stačit rozhraní máme/nemáme. Před samotnou prací s COM samozřejmě musíme provést inicializaci.
ITaskbarList3* pTaskbar = NULL;
HRESULT hResult;
hResult = CoInitialize(NULL);
assert(hResult == S_OK);
hResult = CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_ALL,
IID_ITaskbarList3, (void**)&pTaskbar);
Poznámka: Do zdrojového souboru jsme samozřejmě přidali #include "TaskbarList3.h" a přidali nový soubor TaskbarList3_i.c, tj. použili jsme soubory, které jsou výsledkem snažení MIDL kompilátoru.
Nejprve je nutné nastavit, že budeme průběh zobrazovat:
if (pTaskbar != NULL) pTaskbar->SetProgressState(hWnd, TBPF_NORMAL);
A pak se do toho můžeme pustit:
for (int i = 0;i < 100;i++) {
if (pTaskbar != NULL) pTaskbar->SetProgressValue(hWnd, i, 100);
Sleep(100);
}
Po skončení zobrazování průběhu opět vše vrátíme do základního stavu, rozhraní uvolníme a nezapomene zavolat CoUninitialize pro deinicializaci COM:
if (pTaskbar != NULL) {
pTaskbar->SetProgressState(hWnd, TBPF_NOPROGRESS);
pTaskbar->Release();
pTaskbar = NULL;
}
CoUninitialize();
Tím jsme do naší aplikace zabudovali zobrazení průběhu, které ovšem uvidí pouze majitelé Windows 7 (a novějších). Pro ostatní bychom měli průběh také nějak zobrazovat. Výhodou zobrazení na hlavním panelu je to, že je vidět i při minimalizovaném stavu aplikace. My proto zobrazíme průběh (počet procent) v tooltip okně v pravém dolním rohu plochy, a to v pouze v případě, že průběh nezobrazujeme do Taskbaru (nemáme pointer na rozhraní ITaskbarList3).
Poznámka: Pro použití tooltipu v tomto stavu je potřeba používat novější verzi knihovny komponent Common Controls. To zařídíme přidáním odpovídajícího manifestu (v našem případě TaskbarProgress.exe.manifest) do projektu.
Nejprve si vytvoříme okno tooltipu a nastavíme mu základní vlastnosti, zejména specifikujeme, aby okno bylo vždy nahoře (TOPMOST) a vlastnost TTF_TRACK, tj. zobrazení v naší režii:
HWND CreateTooltip(HWND hWnd)
{
TOOLINFO sToolInfo;
// vytvoreni okna tooltipu
HWND hTooltip = CreateWindowEx(0, TOOLTIPS_CLASS, NULL,
WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, hWnd, NULL, hInst, NULL);
assert(hTooltip != NULL);
// nastaveni okna jako TOPMOST
::SetWindowPos(hTooltip, HWND_TOPMOST,0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
// vyplneni struktury TOOLINFO (TTF_TRACK) a zatim zadny text
sToolInfo.cbSize = sizeof(TOOLINFO);
sToolInfo.uFlags = TTF_TRACK;
sToolInfo.hwnd = NULL;
sToolInfo.hinst = NULL;
sToolInfo.uId = 0;
sToolInfo.lpszText = L"";
// inicializace velikosti okna tooltipu
sToolInfo.rect.left = 0;
sToolInfo.rect.top = 0;
sToolInfo.rect.right = 0;
sToolInfo.rect.bottom = 0;
// nastaveni maximalni delky textu, jinak se provede zalomeni
::SendMessage(hTooltip, TTM_SETMAXTIPWIDTH, 0, (LPARAM)650);
// nastaveni titulku v tootltipu
::SendMessage(hTooltip, TTM_SETTITLE, (WPARAM)TTI_INFO,
(LPARAM)L"programujte.com");
// registrace tipu
::SendMessage(hTooltip, TTM_ADDTOOL, 0,
(LPARAM)(LPTOOLINFO)&sToolInfo);
return(hTooltip);
}
Vytvořenému tooltipu nyní stačí nastavovat pozici a text a bude zobrazovat průběh:
void TooltipProgress(HWND hTooltip, UINT nPos, UINT nMax)
{
wchar_t wzTemp[65];
RECT rtRect;
RECT rtScreen;
TOOLINFO sToolInfo;
int nLeft;
int nTop;
assert(nMax != 0);
// zobrazovany text - prubeh
swprintf_s(wzTemp, 64, L"%u%%", (nPos * 100) / nMax);
// vyplneni noveho obsahu TOOLINFO (s novym textem)
sToolInfo.cbSize = sizeof(TOOLINFO);
sToolInfo.uFlags = TTF_TRACK;
sToolInfo.hwnd = NULL;
sToolInfo.hinst = NULL;
sToolInfo.uId = 0;
sToolInfo.lpszText = wzTemp;
// velikost oblasti se prizpusobi sama
sToolInfo.rect.left = 0;
sToolInfo.rect.top = 0;
sToolInfo.rect.right = 0;
sToolInfo.rect.bottom = 0;
// zjisteni aktualni velikosti okna tooltipu
GetWindowRect(hTooltip, &rtRect);
// zjisteni okraju pracovni plochy
GetDesktopRect(&rtScreen);
// zjisteni leveho horniho bodu pro zobrazni okna tooltipu
nLeft = rtScreen.right - (rtRect.right - rtRect.left);
nTop = rtScreen.bottom - (rtRect.bottom - rtRect.top);
// aktualizace textu tooltipu
::SendMessage(hTooltip, TTM_UPDATETIPTEXT, 0,
(LPARAM)(LPTOOLINFO)&sToolInfo);
// nastaveni pozice tooltipu
::SendMessage(hTooltip, TTM_TRACKPOSITION, 0,
(LPARAM)MAKELONG(nLeft, nTop));
// aktivace posunuti tooltipu
::SendMessage(hTooltip, TTM_TRACKACTIVATE, true,
(LPARAM)(LPTOOLINFO)&sToolInfo);
}
Pro získání oblasti plochy máme svoji funkci, protože chceme tooltip zobrazit mimo případný taskbar:
void GetDesktopRect(RECT* pRect)
{
APPBARDATA sData;
HWND hDesktop;
assert(pRect != NULL);
// handle plochy
hDesktop = GetDesktopWindow();
assert(hDesktop != NULL);
// klientska oblast plochy
GetClientRect(hDesktop, pRect);
// zjisteni pozice a velikosti Taskbaru
ZeroMemory((LPBYTE)&sData, sizeof(sData));
sData.cbSize = sizeof(sData);
SHAppBarMessage(ABM_GETTASKBARPOS, &sData);
SHAppBarMessage(ABM_GETAUTOHIDEBAR, &sData);
// podle jeho umisteni uprav i klientskou oblast plochy
switch(sData.uEdge) {
case(ABE_BOTTOM):
pRect->bottom = sData.rc.top;
break;
case(ABE_LEFT):
pRect->left = sData.rc.right;
break;
case(ABE_RIGHT):
pRect->right = sData.rc.left;
break;
case(ABE_TOP):
pRect->top = sData.rc.bottom;
break;
};
}
Výsledný kód vlákna vypadá následovně:
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
HWND hWnd = (HWND)lpParameter;
HWND hTooltip = NULL;
ITaskbarList3* pTaskbar = NULL;
HRESULT hResult;
// inicializace COM
hResult = CoInitialize(NULL);
assert(hResult == S_OK);
// ziskani pointer na rozhrani ITaskbarList3
hResult = CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_ALL,
IID_ITaskbarList3, (void**)&pTaskbar);
// pokud mame pointer na rozhrani, tak nastav stav zobrazovani
if (pTaskbar != NULL) pTaskbar->SetProgressState(hWnd, TBPF_NORMAL);
// jinak vytvor tootlip
else hTooltip = CreateTooltip(hWnd);
// zobrazovani prubehu 0 az 99
for (int i = 0;i < 100;i++) {
// pokud mame pointer na rozhrani, tak zobraz novou hodnotu
if (pTaskbar != NULL) pTaskbar->SetProgressValue(hWnd, i, 100);
// jinak zobrazuj prubeh v tooltipu
else TooltipProgress(hTooltip, i, 100);
// pockej 100ms
Sleep(100);
}
// pokud jsme pracovali s rozhranim
if (pTaskbar != NULL) {
// tak nastav vychozi stav zobrazovani
pTaskbar->SetProgressState(hWnd, TBPF_NOPROGRESS);
// a rozhrani uvolni
pTaskbar->Release();
pTaskbar = NULL;
}
// jinak zrus okno tooltipu
else DestroyWindow(hTooltip);
// povol polozku Start v menu
EnableMenuItem(GetMenu(hWnd), ID_FILE_START,
MF_BYCOMMAND | MF_ENABLED);
// deinicializace COM
CoUninitialize();
return(0);
}
Zdrojový kód celé aplikace je ke stažení zde. Přeložená aplikace na vyzkoušení je ke stažení zde.