ウィンドウプロシージャをメンバ関数にしたい。しかしWNDCLASSEXのlpfnWndProcに指定できるのは普通の関数かstaticメンバ関数のみ。
しかし一工夫することで、ウィンドウをカプセル化することができる。コードは以下。
(ソースコードをまとめたファイル:CnuWindow.zip )
CnuWindow.h
#pragma once #include <windows.h> class CnuWindow { TCHAR WINDOW_CLASS_NAME[1024]; static LRESULT CALLBACK StaticWndProc( HWND hWnd, UINT msg, WPARAM wp, LPARAM lp); private: HWND m_hwnd; HINSTANCE m_hInstance; public: CnuWindow(); virtual ~CnuWindow(); //! @brief ウィンドウ定義用メンバ関数 bool RegistWindow( const TCHAR* window_class_name, const HINSTANCE hInst ); //! @brief ウィンドウプロシージャ virtual LRESULT WndProc( HWND hWnd, UINT msg, WPARAM wp, LPARAM lp ); HWND GetHWnd()const { return m_hwnd; } HINSTANCE GetHInstance()const { return m_hInstance; } //! @brief ウィンドウ作成用メンバ関数 bool nuCreateWindow( LPCTSTR lpWindowName, // ウィンドウ名 DWORD dwStyle, // ウィンドウスタイル int x, // ウィンドウの横方向の位置 int y, // ウィンドウの縦方向の位置 int nWidth, // ウィンドウの幅 int nHeight, // ウィンドウの高さ HWND hWndParent, // 親ウィンドウまたはオーナーウィンドウのハンドル HMENU hMenu // メニューハンドルまたは子ウィンドウ ID ); };
CnuWindow.cpp
#include "CnuWindow.h" #include <windows.h> #include <tchar.h> CnuWindow::CnuWindow() { } CnuWindow::~CnuWindow() { } // ウィンドウを作成する bool CnuWindow::RegistWindow(const TCHAR* window_class_name, const HINSTANCE hInst) { _tcscpy_s(WINDOW_CLASS_NAME, window_class_name); m_hInstance = hInst; WNDCLASSEX wc; // ウィンドウクラスの情報を設定 wc.cbSize = sizeof(wc); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = CnuWindow::StaticWndProc; // ウィンドウプロシージャ wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; // インスタンスハンドル wc.hIcon = (HICON)LoadIcon(NULL, IDI_APPLICATION); wc.hIconSm = wc.hIcon; wc.hCursor = (HCURSOR)LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = WINDOW_CLASS_NAME; // ウィンドウクラス名 // ウィンドウクラスを登録する if (RegisterClassEx(&wc) == 0) { return false; } return true; } bool CnuWindow::nuCreateWindow( LPCTSTR lpWindowTitle, // ウィンドウ名 DWORD dwStyle, // ウィンドウスタイル int x, // ウィンドウの横方向の位置 int y, // ウィンドウの縦方向の位置 int nWidth, // ウィンドウの幅 int nHeight, // ウィンドウの高さ HWND hWndParent, // 親ウィンドウまたはオーナーウィンドウのハンドル HMENU hMenu // メニューハンドルまたは子ウィンドウ ID ) { m_hwnd = CreateWindow( WINDOW_CLASS_NAME, // ウィンドウクラス名 lpWindowTitle, // タイトルバーに表示する文字列 dwStyle, // ウィンドウの種類 x, // ウィンドウを表示する位置(X座標) y, // ウィンドウを表示する位置(Y座標) nWidth, // ウィンドウの幅 nHeight, // ウィンドウの高さ hWndParent, // 親ウィンドウのウィンドウハンドル hMenu, // メニューハンドル m_hInstance, // インスタンスハンドル this // その他の作成データ ); if (m_hwnd == nullptr) return false; return true; } LRESULT CnuWindow::WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, msg, wp, lp); } LRESULT CALLBACK CnuWindow::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { CnuWindow* This = (CnuWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA); if (!This) {//取得できなかった(ウィンドウ生成中)場合 if (message == WM_CREATE) { This = (CnuWindow*)((LPCREATESTRUCT)lParam)->lpCreateParams; if (This) { SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)This); return This->WndProc(hWnd, message, wParam, lParam); } } } else {//取得できた場合(ウィンドウ生成後) return This->WndProc(hWnd, message, wParam, lParam); } return DefWindowProc(hWnd, message, wParam, lParam); }
main.cpp
#include<windows.h> #include <tchar.h> #include "CnuWindow.h" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { CnuWindow wino; wino.RegistWindow(_T("WINO"), hInstance); wino.nuCreateWindow( _T("ウィンドウO"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 300, 300, nullptr, nullptr ); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; }
クラス名はCnuWindowにしているので、それを置換すれば独自のクラス名にできる。
内容的にはCreateWindow時にlpParamに自身のthisを入れる典型的な方法なので詳細は他所へ譲るとして、多少解説をする
LRESULT CALLBACK CnuWindow::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { CnuWindow* This = (CnuWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA); if (!This) {//取得できなかった(ウィンドウ生成中)場合 if (message == WM_CREATE) { This = (CnuWindow*)((LPCREATESTRUCT)lParam)->lpCreateParams; if (This) { SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)This); return This->WndProc(hWnd, message, wParam, lParam); } } } else {//取得できた場合(ウィンドウ生成後) return This->WndProc(hWnd, message, wParam, lParam); } return DefWindowProc(hWnd, message, wParam, lParam); }
64ビット環境ではSetWindowLongではなくSetWindowLongPtrを使わなくてはならない。それに伴い、GetWindowLongPtr,GWLP_USERDATAに変更する。
virtual LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp);
これをvirtualにする理由は、main.cppでいずれ以下のように書きたいからである。
#include<windows.h> #include <tchar.h> #include <cstdint> #include "CnuWindow.h" class CWin1 : public CnuWindow { LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_CREATE: break; case WM_LBUTTONDOWN: MessageBox(hWnd, _T("CWin1"), 0, 0); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, msg, wp, lp); } }; class CWin2 : public CnuWindow { LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_CREATE: break; case WM_LBUTTONDOWN: MessageBox(hWnd, _T("CWin2"), 0, 0); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, msg, wp, lp); } }; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { CWin1 win1; win1.RegistWindow(_T("WIN1"), hInstance); win1.nuCreateWindow(_T("ウィンドウ1"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 300, 300, nullptr, nullptr); CWin2 win2; win2.RegistWindow(_T("WIN2"), hInstance); win2.nuCreateWindow(_T("ウィンドウ2"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 400, 300, 300, nullptr, nullptr); CnuWindow wino; wino.RegistWindow(_T("WINO"), hInstance); wino.nuCreateWindow(_T("ウィンドウO"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 800, 300, 300, nullptr, nullptr); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; }
上記コードは、CnuWindowを継承したCWin1,CWin2クラスを作成し、それぞれのインスタンスがまったく異なるウィンドウとして動作する。
WndProcを仮想関数にしないと、CWin1,CWin2を作成してもメッセージが全てCnuWindowの方へ行ってしまうので、変数をグローバルにしなくていい程度のメリットしかなくなってしまう。このようにクラスを作れば、同じコードで違う挙動をするウィンドウを最小限の労力で作ることができる。