Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Procedura okna je pouze funkce, která se vyvolá pro každou zprávu, takže je ze své podstaty bezstavová. Proto potřebujete způsob, jak sledovat stav aplikace z jednoho volání funkce na další.
Nejjednodušším přístupem je jednoduše dát všechno do globálních proměnných. To funguje dostatečně dobře pro malé programy a mnoho ukázek sady SDK tento přístup používá. Ve velkém programu však vede k šíření globálních proměnných. Můžete mít také několik oken, z nichž každé má vlastní proceduru okna. Sledování toho, které okno by mělo přistupovat k proměnným, jsou matoucí a náchylné k chybám.
Funkce CreateWindowEx poskytuje způsob předání jakékoli datové struktury do okna. Při volání této funkce odešle následující dvě zprávy do procedury okna:
Tyto zprávy se posílají v uvedeném pořadí. (Nejedná se o jediné dvě zprávy odeslané během CreateWindowEx, ale můžeme ignorovat ostatní pro tuto diskuzi.)
Zpráva WM_NCCREATE a WM_CREATE se odešle před zobrazením okna. Díky tomu je vhodné inicializovat uživatelské rozhraní – například určit počáteční rozložení okna.
Poslední parametr CreateWindowEx je ukazatel typu void*. V tomto parametru můžete předat libovolnou hodnotu ukazatele. Když procedura okna zpracovává WM_NCCREATE nebo WM_CREATE zprávu, může tuto hodnotu extrahovat z dat zprávy.
Pojďme se podívat, jak byste pomocí tohoto parametru předali data aplikace do okna. Nejprve definujte třídu nebo strukturu, která obsahuje informace o stavu.
// Define a structure to hold some state information.
struct StateInfo {
// ... (struct members not shown)
};
Při volání CreateWindowExpředáte ukazatel na tuto strukturu v posledním void* parametru.
StateInfo *pState = new (std::nothrow) StateInfo;
if (pState == NULL)
{
return 0;
}
// Initialize the structure members (not shown).
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
pState // Additional application data
);
Když obdržíte WM_NCCREATE a WM_CREATE zprávy, lParam parametr každé zprávy je ukazatel na strukturu CREATESTRUCT. Struktura CREATESTRUCT zase obsahuje ukazatel, který jste předali do CreateWindowEx.
Tady je, jak získat ukazatel na vaši datovou strukturu. Nejprve získejte strukturu CREATESTRUCT přetypováním parametru lParam.
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
lpCreateParams člen struktury CREATESTRUCT je původní ukazatel void, který jste zadali v CreateWindowEx. Získejte ukazatel ke své vlastní datové struktuře přetypováním lpCreateParams.
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
Dále zavolejte funkci SetWindowLongPtr a předejte ukazatel na datovou strukturu.
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
Účelem tohoto posledního volání funkce je uložit ukazatel StateInfo v datech instance okna. Jakmile to uděláte, můžete kdykoli získat ukazatel zpět z okna voláním GetWindowLongPtr:
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
Každé okno má vlastní data instance, takže můžete vytvořit více oken a dát každému okně vlastní instanci datové struktury. Tento přístup je užitečný zejména v případě, že definujete třídu oken a vytvoříte více než jedno okno této třídy – například když vytvoříte vlastní třídu ovládacího prvku. Je vhodné zabalit volání GetWindowLongPtr do malé pomocné funkce.
inline StateInfo* GetAppState(HWND hwnd)
{
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
return pState;
}
Nyní můžete napsat proceduru okna následujícím způsobem.
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
StateInfo *pState;
if (uMsg == WM_CREATE)
{
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
}
else
{
pState = GetAppState(hwnd);
}
switch (uMsg)
{
// Remainder of the window procedure not shown ...
}
return TRUE;
}
Přístup Object-Oriented
Tento přístup můžeme dále rozšířit. Už jsme definovali datovou strukturu pro uchovávání informací o stavu okna. Je vhodné poskytnout této datové struktuře členské funkce (metody), které pracují s daty. To přirozeně vede k návrhu, ve kterém je struktura (nebo třída) zodpovědná za všechny operace v okně. Procedura okna by se pak stala součástí třídy.
Jinými slovy, chtěli bychom přejít z tohoto:
// pseudocode
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
StateInfo *pState;
/* Get pState from the HWND. */
switch (uMsg)
{
case WM_SIZE:
HandleResize(pState, ...);
break;
case WM_PAINT:
HandlePaint(pState, ...);
break;
// And so forth.
}
}
Na toto:
// pseudocode
LRESULT MyWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_SIZE:
this->HandleResize(...);
break;
case WM_PAINT:
this->HandlePaint(...);
break;
}
}
Jediným problémem je způsob, jak propojit metodu MyWindow::WindowProc. Funkce RegisterClass očekává, že procedura okna bude ukazatelem funkce. V tomto kontextu nelze předat ukazatel na nestatickou členskou funkci. Můžete ale předat ukazatel na statickou členovou funkci a pak delegovat na členovou funkci. Tady je šablona třídy, která ukazuje tento přístup:
template <class DERIVED_TYPE>
class BaseWindow
{
public:
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
DERIVED_TYPE *pThis = NULL;
if (uMsg == WM_NCCREATE)
{
CREATESTRUCT* pCreate = (CREATESTRUCT*)lParam;
pThis = (DERIVED_TYPE*)pCreate->lpCreateParams;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pThis);
pThis->m_hwnd = hwnd;
}
else
{
pThis = (DERIVED_TYPE*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
}
if (pThis)
{
return pThis->HandleMessage(uMsg, wParam, lParam);
}
else
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
BaseWindow() : m_hwnd(NULL) { }
BOOL Create(
PCWSTR lpWindowName,
DWORD dwStyle,
DWORD dwExStyle = 0,
int x = CW_USEDEFAULT,
int y = CW_USEDEFAULT,
int nWidth = CW_USEDEFAULT,
int nHeight = CW_USEDEFAULT,
HWND hWndParent = 0,
HMENU hMenu = 0
)
{
WNDCLASS wc = {0};
wc.lpfnWndProc = DERIVED_TYPE::WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = ClassName();
RegisterClass(&wc);
m_hwnd = CreateWindowEx(
dwExStyle, ClassName(), lpWindowName, dwStyle, x, y,
nWidth, nHeight, hWndParent, hMenu, GetModuleHandle(NULL), this
);
return (m_hwnd ? TRUE : FALSE);
}
HWND Window() const { return m_hwnd; }
protected:
virtual PCWSTR ClassName() const = 0;
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
HWND m_hwnd;
};
BaseWindow třída je abstraktní základní třída, ze které jsou odvozeny konkrétní třídy oken. Zde je například deklarace jednoduché třídy odvozené z BaseWindow:
class MainWindow : public BaseWindow<MainWindow>
{
public:
PCWSTR ClassName() const { return L"Sample Window Class"; }
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
};
Okno vytvoříte voláním BaseWindow::Create:
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
MainWindow win;
if (!win.Create(L"Learn to Program Windows", WS_OVERLAPPEDWINDOW))
{
return 0;
}
ShowWindow(win.Window(), nCmdShow);
// Run the message loop.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
Metoda čistě virtuální BaseWindow::HandleMessage se používá k implementaci procedury okna. Například následující implementace je ekvivalentní postupu okna zobrazenému na začátku Modulu 1.
LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(m_hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
EndPaint(m_hwnd, &ps);
}
return 0;
default:
return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
}
return TRUE;
}
Všimněte si, že popisovač okna je uložen v členské proměnné (m_hwnd), takže ho není třeba předávat jako parametr do HandleMessage.
Mnohé z existujících programovacích architektur windows, jako jsou microsoft foundation classes (MFC) a active template library (ATL), používají přístupy, které jsou v podstatě podobné té, která je zde uvedena. Samozřejmě, plně zobecněná architektura, jako je MFC, je složitější než tento relativně zjednodušený příklad.
Další
Modul 2: Použití COM ve Vašem programu Windows