管理應用程序狀態
- WM_NCCREATE
- WM_CREATE
// 定義一個結構保存一些狀態信息
struct StateInfo {
// ... (結構成員沒有顯示)
};
當你調用CreateWindowsEx函數,將結構指針傳遞給最後的void*參數。
StateInfo *pState = new (std::nothrow) StateInfo;
if (pState == NULL)
{
return 0;
}
// 在這裏初始化結構成員 (示例沒有顯示).
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 // 額外的數據信息
);
當你收到WM_NCCREATE和WM_CREATE消息,每個消息的 lParam的參數指向CREATESTRUCT結構的指針。而後,CREATESTRUCT結構包含的指針傳入CreateWindowEx。
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
CREATESTRUCT
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
下一步,調用 SetWindowLoingPtr函數並把指針傳入你的數據結構。
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
最後一個函數的調用的目的是將StateInfo指針儲存在窗口的實例數據中。一旦你這樣做,你總是可以從窗口調用GetWindowLongPtr得到指針。
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
每個窗口都有自己的實例數據,所以你可以創建多個窗口,每個窗口都有自己的數據結構的實例。這種方法是特別有用的,如果你定義一個窗口類和創建該類的多個窗口。比如你創建了一個自定義控件類。GetWindowsLongPtr調用可以很方便的封裝在一個小的輔助函數。
inline StateInfo* GetAppState(HWND hwnd)
{
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
return pState;
}
現在你可以寫你的窗口過程如下:
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;
}
面向對象的方法
// 僞代碼
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.
}
}
到這:
// 僞代碼
LRESULT MyWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_SIZE:
this->HandleResize(...);
break;
case WM_PAINT:
this->HandlePaint(...);
break;
}
}
唯一的問題是用什麼方法連接MyWindow::WindowProc。RegisterClass函數需要的窗口過程是一個函數指針,在此上下文中,你不能將指針傳遞給(非靜態)成員函數。然而,你可以將指針傳遞給一個靜態成員函數,然後委託給成員函數。以下是一個類模板,顯示這種方法:
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是一個抽象類,從特定的窗口了派生。例如,以下是一個簡單的派生類的basewindow的聲明:
class MainWindow : public BaseWindow<MainWindow>
{
public:
PCWSTR ClassName() const { return L"Sample Window Class"; }
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
};
要創建一個窗口,調用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;
}
純虛擬的 BaseWindow::HandleMessage
方法用於執行窗口過程。下面的執行是相當於開始顯示窗口過程:
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;
}
注意,窗口句柄存儲在成員變量(m_hwnd)所以我們不需要將它作爲一個參數傳遞給HandleMessage。
許多現有的Windows編程框架,如Microsoft基礎類(MFC)和活動模板庫(ATL)使用的基本上類似於此處所示的方法。當然,一個完全廣義的框架(如MFC)比這種相對簡單的例子更復雜。