アプリケーション状態の管理
ウィンドウ プロシージャは、すべてのメッセージに対して呼び出される単なる関数であるため、本質的にステートレスです。 そのため、ある関数呼び出しから次の関数呼び出しまでのアプリケーションの状態を追跡する方法が必要です。
最も簡単な方法は、すべてをグローバル変数に配置することです。 これは小規模なプログラムに対して十分に機能し、多くの SDK サンプルではこのアプローチが使用されています。 しかし、大規模なプログラムでは、グローバル変数の急増につながります。 また、複数のウィンドウがあり、それぞれに独自のウィンドウ プロシージャが含まれます。 どのウィンドウがどの変数にアクセスすべきかを追跡すると、混乱が生じ、エラーが発生しやすくなります。
CreateWindowEx 関数は、任意のデータ構造をウィンドウに渡す方法を提供します。 この関数が呼び出されると、次の 2 つのメッセージがウィンドウ プロシージャに送信されます。
これらのメッセージは、一覧表示された順序で送信されます。 ( これらは CreateWindowEx の間に送信される 2 つのメッセージだけではありませんが、このディスカッションでは他のメッセージは無視できます)。
WM_NCCREATEメッセージとWM_CREATE メッセージは、ウィンドウが表示される前に送信されます。 これにより、UI を初期化するのに適した場所になります。たとえば、ウィンドウの初期レイアウトを決定できます。
CreateWindowEx の最後のパラメーターは void* 型のポインターです。 このパラメーターには、任意のポインター値を渡すことができます。 ウィンドウ プロシージャは、 WM_NCCREATE または メッセージWM_CREATE 処理するときに、メッセージ データからこの値を抽出できます。
このパラメーターを使用して、アプリケーション データをウィンドウに渡す方法を見てみましょう。 まず、状態情報を保持するクラスまたは構造体を定義します。
// Define a structure to hold some state information.
struct StateInfo {
// ... (struct members not shown)
};
CreateWindowEx を呼び出すときは、最後の void* パラメーターでこの構造体へのポインターを渡します。
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
);
WM_NCCREATEおよびWM_CREATEメッセージを受信すると、各メッセージの lParam パラメーターは CREATESTRUCT 構造体へのポインターになります。 CREATESTRUCT 構造体には、CreateWindowEx に渡したポインターが含まれます。
データ構造へのポインターを抽出する方法を次に示します。 まず、lParam パラメーターをキャストして CREATESTRUCT 構造体を取得します。
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
CREATESTRUCT 構造体の lpCreateParams メンバーは、CreateWindowEx で指定した元の void ポインターです。 lpCreateParams をキャストして、独自のデータ構造へのポインターを取得します。
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
次に、 SetWindowLongPtr 関数を呼び出し、データ構造へのポインターを渡します。
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
この最後の関数呼び出しの目的は、ウィンドウのインスタンス データに StateInfo ポインターを格納することです。 これを行うと、 GetWindowLongPtr を呼び出すことで、いつでもウィンドウからポインターを取得できます。
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
各ウィンドウには独自のインスタンス データがあるため、複数のウィンドウを作成し、各ウィンドウにデータ構造の独自のインスタンスを与えることができます。 この方法は、ウィンドウのクラスを定義し、そのクラスの複数のウィンドウを作成する場合 (たとえば、カスタム コントロール クラスを作成する場合) に特に便利です。 GetWindowLongPtr 呼び出しを小さなヘルパー関数でラップすると便利です。
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;
}
Object-Orientedアプローチ
このアプローチをさらに拡張できます。 ウィンドウに関する状態情報を保持するデータ構造が既に定義されています。 このデータ構造には、データを操作するメンバー関数 (メソッド) を提供するのが理にかなっています。 これにより、当然、構造 (またはクラス) がウィンドウ上のすべての操作を担当する設計になります。 その後、ウィンドウ プロシージャは クラスの一部になります。
言い換えると、次から行きたいと考えます。
// 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.
}
}
この行を次のように変更します。
// pseudocode
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
メソッドは、ウィンドウ プロシージャを実装するために使用されます。 たとえば、次の実装は、 モジュール 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;
}
ウィンドウ ハンドルはメンバー変数 (m_hwnd) に格納されているため、 にパラメーター HandleMessage
として渡す必要はありません。
Microsoft Foundation Classes (MFC) や Active Template Library (ATL) などの既存の Windows プログラミング フレームワークの多くは、基本的にここに示すようなアプローチを使用します。 もちろん、MFC などの完全に一般化されたフレームワークは、この比較的単純な例よりも複雑です。
次へ
モジュール 2: Windows プログラムでの COM の使用