Přepínání mezi dialogy ve stylu Palm Pilotů
Uživatelské rozhraní počítačů Palm Pilot nemá z úsporných důvodů koncepci "hlavního okna aplikace". Místo toho je složeno z řady formulářů, z nichž vždy je alespoň jeden zobrazen na displeji.
Pokud bychom chtěli teoreticky vytvářet aplikaci se společným uživatelským rozhraním na počítači s PalmOS i na PocketPC, pak je přirozené použít právě aplikaci založenou na dialogu. Každý formulář PalmOS budeme simulovat jedním dialogem PocketPC.
V naší aplikaci musíme vyřešit, jak mezi sebou jednoduchým způsobem přepínat dialogy na displeji. Nabízím vám jedno z možných řešení, na kterém si ukážeme několik dalších vlastností dialogových oken.
Bude se nám hodit i (doteď nepoužité) hlavní okno aplikace, které bude v pozadí a které bude zajišťovat zprávy společné pro naši aplikaci. Současně zabrání nepříjemnému blikání při přepínání mezi dialogy. Připomenu úplně standardní funkci WinMain
:
#define WM_PREPNIDIALOG (WM_USER + 16) // Konstantni jmeno okna a trida okna TCHAR* g_mainWinName = TEXT("Aplikace s dialogy"); TCHAR* g_mainWinClass = TEXT("dialogyV1"); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { g_hInst = hInstance; HWND other = FindWindow(g_mainWinClass, g_mainWinName); if(other) { SetForegroundWindow(other); return 0; } WNDCLASS wc = { CS_VREDRAW|CS_HREDRAW, MainWindowProc, 0, 0, g_hInst, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, g_mainWinClass }; RegisterClass(&wc); HWND hwndMain = CreateWindowEx( 0, g_mainWinClass, g_mainWinName, WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow(hwndMain, SW_MAXIMIZE); // Zobrazime prvni vybrany dialog PostMessage(hwndMain, WM_PREPNIDIALOG, (WPARAM)IDD_TABLE, (LPARAM)TableDlgProc); MSG msg; // Smycka zpracovani udalosti while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; }
Základem je implementace zpracování zprávy WM_PREPNIDIALOG
, při kterém obsahuje wParam
resource identifikator dialogu a lParam
adresu funkce dialogu. Zpracovaní této události je vyřešeno nasledujícím způsobem:
case WM_PREPNIDIALOG: { int newid = DialogBox(g_hInst, (LPCTSTR)wParam, hwnd, (DLGPROC)lParam); switch(newid) { case IDD_TABLE: PostMessage(hwnd, WM_PREPNIDIALOG, (WPARAM)IDD_TABLE, (LPARAM)TableDlgProc); break; case IDD_OPTIONS: PostMessage(hwnd, WM_PREPNIDIALOG, (WPARAM)IDD_OPTIONS, (LPARAM)OptionsDlgProc); break; ... default: PostQuitMessage(0); break; } } return 0L;
Jak vidíme, tak na začátku zavoláme dialog, jehož resource identifikátor a adresa funkce byly ve zprávě předány. Poté vezmeme hodnotu, vrácenou funkcí DialogBox
(je to druhý parametr funkce EndDialog
) a podle této hodnoty zařadíme do fronty zpráv další zprávu WM_PREPNIDIALOG
(nebo ukončíme aplikaci).
Pokud si ve funkci dialogu přejeme přepnout se do jiného dialogu, zavoláme funkci EndDialog
s vhodným parametrem, například:
EndDialog(hDlg, IDD_OPTIONS);
Příklad si můžete stáhnout (ZIP) zde.Povšimněte si prosím, že takto napsanou aplikaci není možné hladce ukončit z ovládacích panelů PocketPC.
Důvod je jednoduchý: pokud zavoláme modální (=blokující) dialog, je jeho rodič zablokován. Významem tohoto zablokování je zabránit uživateli interagovat s rodičovským oknem, je-li "nad ním" zobrazen dialog. Bohužel, je to právě ono rodičovské okno (v našem případě hlavní okno aplikace), kterému by byla zaslána zpráva WM_CLOSE
, požadující ukončení aplikace.
Řešení je několik, ale nejjednodušší je přidat do každé funkce dialogu, v naší aplikaci zpracování zprávy WM_ERASEBKGND
, při kterém opět "odblokujeme" hlavní okno aplikace:
case WM_ERASEBKGND: { HWND hOwner = GetWindow(hDlg, GW_OWNER); if(IsWindow(hOwner)) { EnableWindow(hOwner, TRUE); return FALSE; } } break;
Implementování společného mechanismu pro přepínání formulářů by byl vhodný základ pro vytváření multiplatformních aplikací; třeba tento příklad inspiruje některého čtenáře...
Příště ukončíme minisérii příkladem programování vícestránkových dialogů typu PropertySheet.