Programujeme pro Pocket PC - položky pro obrazovku Dnes
16.10.2003, eXEden, návod
Obrazovka Dnes je jakousi "domovskou stránkou" Vašeho PocketPC. Zobrazují se na něm položky, které umožňují sledovat systém, zadávat údaje (např. Kalendář) nebo zobrazovat souhrnná data patřící nějaké aplikaci. V dnešní době se objevuje spousta aplikací, které rozšiřují možnosti obrazovky Dnes a přidávají do ní svoje vlastní položky. Pokud jste kdy chtěli nebo chcete vytvořit vlastní položku, pak Vám tento článek může být návodem "jak na to"...
Co k tomu?
Tento díl seriálu (kterýžto jest dílem prvním) je určen pro mírně pokročilé (nebo alespoň znalé programování pro PocketPC) a vyžaduje nainstalované eMbedded Visual C++ 3.0 (4.0) a znalost Win32 API. Dobrým výchozím bodem je vytvoření nového projektu: File-New-Projects-WCE Dynamick Link Library a během průvodce zvolit "A simple Windows CE DLL project". Takhle si vytvoříte prázdný projekt nachystaný pro vytváření položky obrazovky Dnes.
Úvod
Položky pro obrazovku Dnes jsou implementovány pomocí jednoduchých DLL knihoven. Systém automaticky hledá položky v registrech a najde-li takovou, zavede do paměti patřičnou DLL knihovnu a zobrazí položku definovanou v této DLL knihovně. Všechny položky je možné spravovat v nastavení obrazovky Dnes a tahle správa se týká povolování (popř. zakazování) jednotlivých položek, nastavování každé položky (za předpokladu že položka umožňuje nastavení), změny pořadí položek na obrazovce Dnes apod.
Princip zobrazování položek obrazovky Dnes
Jak jsem napsal výše, systém ví o položkách pro obrazovku Dnes z registrů. Informace o každé položce je umístěna v klíči HKEY_LOCAL_MACHINESOFTWAREMicrosoftTodayItems. Každá položka zde vytváří svůj vlastní podklíč, což je patrné z obrázku:
Název podklíče se objevuje v seznamu položek v nastavení obrazovky Dnes (Settings-Today) a to bez počátečních i koncových uvozovek. Jednotlivé hodnoty, které si s sebou podklíč (a potažmo i Vaše položka) nese, jsou následující:
- DLL: řetězcová hodnota udávající úplnou cestu k DLL knihovně položky
- Flags: uživatelsky definovaná DWORD hodnota (viz. Struktura TODAYLISTITEM)
- Options: DWORD hodnota označující přítomnost konfiguračního dialogu pro položku - 1 = ano, 0 = ne (přístupné v nastavení obrazovky Dnes)
- Enabled: DWORD hodnota označující, zda je položka povolena, tzn. zda se zobrazuje - 1 = ano, 0 = ne
- Type: DWORD hodnota určující typ položky. Pro jiné než systémové položky (Kalendář, Kontakty atd.) nabývá hodnoty 4
DLL knihovna položky obrazovky Dnes
Základním požadavkem pro DLL knihovnu je existence jedné exportované funkce InitializeCustomItem, která musí být exportována pod číslem 240. Položka může podporovat i konfigurační okno a pak je nutné přidat a exportovat ještě jednu funkci CustomItemOptionsDlgProc, která musí být exportována pod číslem 241. Konfigurační okno se standardně definuje ve zdrojích (resources) a jeho identifikátorem musí být IDD_TODAY_CUSTOM, který je definovaný v hlavičkovém souboru todaycmn.h pod číslem 500. Jak jsem již uvedl, po zavedení knihovny do paměti systém volá funkci InitializeCustomItem, jejíž funkcí je správně zaregistrovat třídu okna položky, vytvořit toto okno a říct systému, že okno položky je připraveno.
Struktura TODAYLISTITEM
Vzhledem k tomu, že tato struktura je pro položky obrazovky Dnes velmi důležitá, řekneme se o ní něco předem a použití necháme na později. Zatím nám stačí vědět, že tato struktura se používá během "života" položky neustále a definuje položku samotnou. Je definována v hlavičkovém souboru todaycmn.h následovně:
- typedef struct _TODAYLISTITEM {
TCHAR szName[MAX_ITEMNAME];
TODAYLISTITEMTYPE tlit;
DWORD dwOrder;
DWORD cyp;
BOOL fEnabled;
BOOL fOptions;
DWORD grfFlags;
TCHAR szDLLPath[MAX_PATH];
HINSTANCE hinstDLL;
HWND hwndCustom;
BOOL fSizeOnDraw;
BYTE *prgbCachedData;
DWORD cbCachedData;
} TODAYLISTITEM;
Význam jednotlivých členů této struktury:
- szName: název klíče v registrech, který položku identifikuje a pod kterým je v systému uložena
- tlit: typ položky (pro Vámi vytvářené položky bude vždy nabývat hodnoty tlitCustom)
- dwOrder: pořadí položky tak, jak se objevuje na obrazovce Dnes (možno měnit v nastavení obrazovky Dnes)
- cyp: výška položky v pixelech
- fEnabled: označuje, zda je položka povolená, či zakázaná (možno nastavit v nastavení obrazovky Dnes). Nabývá-li hodnoty 0 (položka je zakázaná), je potřeba funkci InitializeCustomItem ihned ukončit s návratovou hodnotou 0.
- fOptions: příznak pro přítomnost konfiguračního dialogu
- szDLLPath: cesta a název DLL knihovny položky
- hinstDLL: handle instance DLL knihovny (hodnota totožná s parametrem hModule funkce DLLMain - viz. Registrace třídy okna)
- hwndCustom: handle okna položky. Tato proměnná je platná až v okamžiku vytvoření okna, kdy už bude handle známý.
- fSizeOnDraw: rezervováno systémem
- grfFlags, prgbCachedData, cbCachedData: slouží pro vnitřní uchovávání hodnot. Programátor může tato pole využít dle svého. Zatímco grfFlags se používá spíše pro statická data, tak prgbCachedData se používá pro uchovávání paměťového bloku, jehož velikost programátor ukládá do hodnoty cbCachedData
Funkce InitializeCustomItem
Předpis této funkce je HWND APIENTRY InitializeCustomItem(TODAYLISTITEM *ptli, HWND hWndParent). Jak je vidět tato funkce přebírá dva parametry.
- TODAYLISTITEM *ptli: je ukazatel na strukturu položky
- HWND hWndParent: handle nadřízeného okna, které bude vlastníkem okna položky (což je vlastně obrazovka Dnes)
Prvním krokem by měl být test na to, je-li položka vůbec povolena (hodnota Enabled v registrech, proměnná ptli->fEnabled). Není-li, je potřeba funkci okamžitě ukončit s návratovou hodnotou 0. Dalším krokem by mělo být nastavení výšky okna položky (proměnná ptli->cyp) také na 0. Pak již můžeme přistoupit k registraci třídy okna položky. Registrace obecně jakékoli třídy se provádí voláním API funkce RegisterClass. Více o této funkci se dozvíte v nápovědě k eVC++ nebo přímo z MSDN. Kroku registrace by ale měla (a nezbytně musí) předcházet samozřejmě odregistrace. Je to z důvodu zabezpečení správné funkcionality Vaší položky. Odregistrace se provádí volání API funkce UnregisterClass. Nevšímejte si návratové hodnoty funkce UnregisterClass - pokud bude volána poprvé (při prvním volání Vaší položky), skončí samozřejmě s chybou. Při dalších voláních, ale již provede to, co se od ní očekává. Máme-li hotovy tyhle všechny kroky, můžeme vytvořit okno položky jako takové. Vytvoření okna položky se provádí voláním API funkce CreateWindow. Její návratová hodnota (handle vytvořeného okna) je zároveň i návratovou hodnotou funkce InitializeCustomItem. Pro větší názornost a přehlednost se můžete podívat i na výpis samotného kódu:
Samotná registrace se nemusí bezpodmínečně provádět během volání funkce InitializeCustomItem. Docela dobře nám tomuto účelu může posloužit také vstupní bod DLL knihovny. Tímto vstupním bodem je funkce DLLMain, která je předepsána následovně: BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved). Tato funkce je součástí každé knihovny a námi vytvořený projekt (viz Co k tomu?) ji obsahuje také. Druhý parametr této funkce určuje stav modulu (DLL knihovny). Nabývá 4 různých hodnot (více viz. dokumentace k Win32 API) a nás bude zajímat okamžit zavedení knihovny do paměti, tzn. dwReason má hodnotu DLL_PROCESS_ATTACH. Registrace třídy okna by potom mohla vypadat následovně:
Pozornějším z vás určitě neuniklo, že při registraci třídy okna položky se používá zatím neznámý identifikátor WndProc. Tento identifikátor označuje okenní proceduru pro okno položky a více si o ní povíme v následujícím odstavci.
Okenní procedura pro okno položky
V rámci Vašeho projektu položky obrazovky Dnes je potřeba nadefinovat okenní proceduru, která bude obsluhovat všechny zprávy, které okno položky přijímá. Předpis této funkce může být následující:
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam). Parametry této funkce jsou nasnadě:
- HWND hWnd: handle okna, které zprávu přijímá (v tomto případě handle okna Vaší položky)
- UINT uMsg: identifikátor zprávy (o zprávách zasílaných oknu položky později)
- WPARAM wParam a LPARAM lParam: parametry zprávy
Okno položky obrazovky Dnes přijímá zprávy stejné jako jakékoli jiné okno v systému (WM_PAINT, WM_ERASEBKGND, WM_CREATE, WM_LBUTTONUP, WM_LBUTTONDOWN apod.). Jednou z nejpoužívanějších zpráv je WM_PAINT, která se stará o správné vykreslení a vzhled Vaší položky. Vyskytují se však i zprávy, se kterými se můžete setkat pouze u položek obrazovky Dnes, jako jsou WM_TODAYCUSTOM_QUERYREFRESHCACHE, WM_TODAYCUSTOM_CLEARCACHE, TODAYM_DRAWWATERMARK a TODAYM_GETCOLOR. Asi by nebylo od věci říct si o každé z nich něco navíc.
Specifické zprávy zasílané oknu položky
WM_TODAYCUSTOM_CLEARCACHE
Tato zpráva je poslána oknu položky v okamžiku, kdy systém potřebuje kompletně obnovit položky na obrazovce Dnes nebo když dochází k odstranění položky. V parametru wParam je uložen odkaz na strukturu TODAYLISTITEM, která byla předána jako parametr funkci InitializeCustomItem. Při obsluze této zprávy by mělo dojít k uvolnění paměti, která byla dříve alokována a uložena do proměnné prgbCachedData.
WM_TODAYCUSTOM_QUERYREFRESHCACHE
Tato zpráva je v periodických úsecích (cca 3 vteřiny) posílána oknu položky. Parametr wParam obsahuje to stejné, co zpráva předchozí. Zpráva je jakýmsi dotazem systému na to, má-li být okno položky (vlastně položka sama) překreslena. Překreslení se většinou řídí sledování nějakých hodnot a jejich změnou. Bude-li návratovou hodnotou TRUE, pak se okno překreslí (FALSE = nepřekreslí se). Je-li zpráva volána poprvé, je proměnná cyp nulová (pokud jste ji takhle během volání InitializeCustomItem nastavili) a potřebujeme nastavit správnou výšku okna položky a v každém případě vrátit TRUE (proto, aby se okno položky správně překreslilo, nyní již se správnými rozměry). Pokud zprávu okno položky obdrží v jiných případech, je potřeba s rozumem vracet hodnotu TRUE. Ne vždy je potřeba překreslovat celé okno, což způsobuje probliknutí obrazovky Dnes. Mnohdy (a ve většině případů) nám postačuje překreslit pouze klientskou část okna položky.
TODAYM_DRAWWATERMARK
Tato zpráva se používá hlavně ve spojitosti se zprávou jinou - WM_ERASEBKGND. V její (WM_ERASEBKGND) obsluze se posílá nadřazenému oknu (obrazovce Dnes) a říká obrazovce Dnes, aby pozadí naší položky vykreslil pozadím obrazovky Dnes, tzn. aby se položka chovala, jako by byla průhledná. Tato zpráva se posílá s parametrem wParam nastaveným na 0 a lParam nastaveným na ukazatel na strukturu TODAYDRAWWATERMARKINFO. Její členské proměnné jsou následující:
- HDC hdc: handle kontextu zařízení (na který se kreslí)
- RECT rc: vymezuje velikost oblasti, která se má vyplnit (většinou celá klientská část)
- HWND hwnd: handle okna, jehož pozadí bude takhle překresleno (ve všech případech handle okna naší položky)
TODAYM_GETCOLOR
Tato zpráva se používá pro zjištění barvy obrazovky Dnes a posílá se opět nadřazenému oknu (obrazovka Dnes). Pokud jako wParam použijeme TODAYCOLOR_TEXT, obdržíme jako návratovou hodnotu typu COLORREF udávající nám barvu textu pro obrazovku Dnes (barva textu se může měnit spolu s použitými tématy).
Příklady použití těchto zpráv opět připojuji:
Přidáváme podporu pro konfigurační dialogy
Jak jsem se již zmínil, můžeme naší položce také přidat konfigurační dialog, který je přístupný přes nastavení obrazovky Dnes. Chceme-li jej využít pro naši položku, musíme nejprve vytvořit ve zdrojích dialog s identifikátorem IDD_TODAY_CUSTOM. Vytvoření konfiguračního dialogu je ryze systémovou záležitostí a nijak je neovlivníme. Jediné, co můžeme ovlivnit, je chování a reakce dialogu a to vytvořením okenní procedury pro náš konfigurační dialog, která musí být vyexportována z DLL knihovny pod ordinálním číslem 241 (viz. DLL knihovna položky obrazovky Dnes). Předpis této procedury je LRESULT WINAPI CustomItemOptionsDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam). Tahle procedure je klasickou okenní procedurou (vlastně hodně podobná okenní proceduře pro okno položky) a její typická šablona by měla vypadat zhruba následovně:
Příklad
Pro ilustraci všeho, co jsme si doposud pověděli, přikládám samozřejmě i vzorový projekt s pokusnou položkou pro obrazovku Dnes. Tato položka zobrazuje dva řádky textu - každý jiným stylem fontu, ale správnou barvou textu převzatou z obrazovky Dnes - a dále zobrazuje ikonu napájení. Kliknete-li na ikonu napájení, otevře se automaticky související aplet Napájení z Ovládacích panelů. Položka podporuje samozřejmě i konfigurační dialog, v němž si můžete zvolit, které části položky se mají zobrazovat. Ukázkový projekt 1 (11,84 KB).
Zjednodušení práce
Možná jste si všimli, že zde nepadla zmínka o MFC. Nepadla a ani padnout nemohla. MFC je "tabu" pro položky obrazovky Dnes. Z toho důvodu zde neexistuje žádné zapouzdření a všechna práce se provádí ručně "na koleně". Tahle skutečnost dokáže pěkně znepříjemnit život tím, že neustále píšeme stejný kód dokola. V článku, který jsem publikoval na jiném webu jsem představil vlastní třídy, které zapouzdřují celou logiku do dvou tříd podobných třídám MFC. Použití těchto tříd je velmi snadné. Jedná se o třídy CTodayWindow a CTodayOptionsDialog. Tyhle dvě třídy stačí vložit do Vašeho projektu, vytvořit si jejich potomky (Vaši budoucí položku), přetížit některou z virtuálních metod a je hotovo. Vše je podrobně ukázané v druhém příkladě. Podrobnější vysvětlení naleznete na (bohužel anglickém) webu CodeProject.COM, ale osobně si myslím, že z ukázkového přikladu bude vše dostatečně jasné. Ukázkový projekt 2 (29,14 KB).
Závěr
Po úporném boji jsem se dostal k samotnému závěru :-) Snažil jsem se zde nastínit a alespoň trošku detailněji vysvětlit problematiku vytváření položek pro obrazovku Dnes. Nemyslím si, že by mé "učitelské" schopnosti byly na lepší úrovni, to vůbec, ale i přes poněkud diskutabilní kvalitu snad tento článek někomu pomůže. Proto Vás prosím, nebojte se a dejte mi svoje názory, připomínky či vylepšení najevo a já se pokusím na sobě zapracovat a zlepšení přenést do dalšího dílu našeho seriálu.