今回はウィンドウをクラス化してみます ウィンドウをクラス化することで ウィンドウをオブジェクトとして扱えるようになるので 複数のウィンドウを楽に取り扱うことができます クラス化に関して気を付けなければいけないのはただ一点でウィンドウプロシージャのメンバ関数化です これに関しては後で説明します 今回は3つのファイルを用意します ・WindowClass.h ・WindowClass.cpp ・main.cpp ---------------------------------------------------------------- ウィンドウクラスのヘッダーファイル(WindowClass.h)です これまでの回で説明した 基本ウィンドウクラスの登録 ウィンドウの作成 初期化、描画、キー、マウス、MCIメッセージ処理を行えるようにします //---------------------------------------// // ウィンドウクラス(WindowClass.h) // // 作成者:Daiki Terai // 作成日:2010/11/29 // 修正履歴: //---------------------------------------// #ifndef _WINDOWCLASS_H_ #define _WINDOWCLASS_H_ // ウィンドウズバージョン指定 #define _WIN32_WINNT 0x0500 #include <tchar.h> #include <stdio.h> #include <stdlib.h> #include <windows.h> #include <windowsx.h> #include <commctrl.h> #pragma comment(lib,"comctl32.lib") #pragma comment(lib,"winmm.lib") static const TCHAR* WC_BASIC = _T("Basic"); // 基本ウィンドウクラス名 // ウィンドウクラス class CSWindowClass { private: static HINSTANCE m_hInstance; // インスタンスハンドル HWND m_hWnd; // ウィンドウハンドル static CSWindowClass* m_pThis; // 登録用 public: // コンストラクタ CSWindowClass(); // デストラクタ virtual ~CSWindowClass(); // 基本ウィンドウクラスの登録 static bool RegistBasicWindowClass(); //--------------------------------------------------------------------// // ウィンドウ作成 // // 引数: Title タイトル // Width 幅 // Height 高さ // X X座標 // Y Y座標 // hParentWnd 親ウィンドウハンドル // hMenu メニューハンドルもしくはリソースID // ClassName ウィンドウクラス名 // Style ウィンドウスタイル // ExStyle ウィンドウ拡張スタイル // // 戻り値: true: 作成成功 // false: 作成失敗 //--------------------------------------------------------------------// virtual bool CreateWnd( const TCHAR* Title,int Width = CW_USEDEFAULT,int Height = CW_USEDEFAULT, int X = CW_USEDEFAULT,int Y = CW_USEDEFAULT, HWND hParentWnd = NULL,HMENU hMenu = NULL, const TCHAR* ClassName = WC_BASIC, DWORD Style = WS_OVERLAPPEDWINDOW | WS_VISIBLE,DWORD ExStyle = 0); private: // ウィンドウプロシージャの管理者 static LRESULT CALLBACK WndProcManager(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam); // ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam); public: // メッセージループ static void MsgLoop(int ArrayLength = 0,CSWindowClass* pWindowArray = NULL); // メッセージ処理していないときにする処理 virtual void Idle(); // ウィンドウ作成メッセージ virtual BOOL OnCreate(HWND hWnd,LPCREATESTRUCT lpCreateStruct); // ウィンドウ破棄メッセージ virtual void OnDestroy(HWND hWnd); // ウィンドウ描画メッセージ virtual void OnPaint(HWND hWnd); // キー入力関係メッセージ virtual void OnChar(HWND hWnd, TCHAR ch, int cRepeat); // マウス関連メッセージ virtual void OnLButtonDown(HWND hWnd, BOOL isDoubleClick, int x, int y, UINT keyFlags); virtual void OnRButtonDown(HWND hWnd, BOOL isDoubleClick, int x, int y, UINT keyFlags); virtual void OnMButtonDown(HWND hWnd, BOOL isDoubleClick, int x, int y, UINT keyFlags); virtual void OnLButtonUp(HWND hWnd,int x, int y, UINT keyFlags); virtual void OnRButtonUp(HWND hWnd,int x, int y, UINT keyFlags); virtual void OnMButtonUp(HWND hWnd,int x, int y, UINT keyFlags); virtual void OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags); virtual void OnMouseWheel(HWND hWnd, int xPos, int yPos, int zDelta, UINT fwKeys); }; #endif // _WINDOWCLASS_H_ まずWindowClassのメンバ変数の説明です private: static HINSTANCE m_hInstance; // インスタンスハンドル HWND m_hWnd; // ウィンドウハンドル static CSWindowClass* m_pThis; // 登録用 m_hInstanceはインスタンスハンドル(アプリケーションのハンドル)で アプリケーションにつき、ひとつしかないのでstaticとしております m_hWndはウィンドウを作成した際、ウィンドウのハンドルを格納します 最後のm_pThisは自クラスのポインタです これについては何に使うかは実装ファイルで説明します 基本ウィンドウクラス登録メソッド ウィンドウ作成メソッドと続き ウィンドウプロシージャを宣言します ここでウィンドウプロシージャは二つあります private: // ウィンドウプロシージャの管理者 static LRESULT CALLBACK WndProcManager(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam); // ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam); WndProcManagerメンバ関数のほうは、基本ウィンドウクラスの登録時に指定してあるウィンドウプロシージャです 静的(static)メンバ関数にしなければ基本ウィンドウクラスの登録ができないのでこうしてあります WndProcメンバ関数はウィンドウ別の本当のプロシージャです WndProcManagerから適切なウィンドウのプロシージャを呼び出します ここにウィンドウのクラス化のギミックがあるのですが 詳しい説明は実装ファイルでします メッセージループの処理もクラス化しました // メッセージループ static void MsgLoop(int ArrayLength = 0,CSWindowClass* pWindowArray = NULL); // メッセージ処理していないときにする処理 virtual void Idle(); Idleに関してはメッセージ処理をしていないときにする処理を書きます この講座では説明しませんがこの関数内でDirectXなどの処理をさせるとよいでしょう メッセージ処理は、このクラスを継承して、以下のメソッドを実装します。 // メッセージ処理していないときにする処理 virtual void Idle(); // ウィンドウ作成メッセージ virtual BOOL OnCreate(HWND hWnd,LPCREATESTRUCT lpCreateStruct); // ウィンドウ破棄メッセージ virtual void OnDestroy(HWND hWnd); // ウィンドウ描画メッセージ virtual void OnPaint(HWND hWnd); // キー入力関係メッセージ virtual void OnChar(HWND hWnd, TCHAR ch, int cRepeat); // マウス関連メッセージ virtual void OnLButtonDown(HWND hWnd, BOOL isDoubleClick, int x, int y, UINT keyFlags); virtual void OnRButtonDown(HWND hWnd, BOOL isDoubleClick, int x, int y, UINT keyFlags); virtual void OnMButtonDown(HWND hWnd, BOOL isDoubleClick, int x, int y, UINT keyFlags); virtual void OnLButtonUp(HWND hWnd,int x, int y, UINT keyFlags); virtual void OnRButtonUp(HWND hWnd,int x, int y, UINT keyFlags); virtual void OnMButtonUp(HWND hWnd,int x, int y, UINT keyFlags); virtual void OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags); virtual void OnMouseWheel(HWND hWnd, int xPos, int yPos, int zDelta, UINT fwKeys); ---------------------------------------------------------------- ウィンドウクラスの実装ファイル(WindowClass.cpp)です // WindowClass.cpp #include "WindowClass.h" // インスタンスハンドル HINSTANCE CSWindowClass::m_hInstance = NULL; // 登録用 CSWindowClass* CSWindowClass::m_pThis = NULL; // コンストラクタ CSWindowClass::CSWindowClass() :m_hWnd(NULL) { } // デストラクタ CSWindowClass::~CSWindowClass(){ } // シンプルウィンドウクラスの登録 bool CSWindowClass::RegistBasicWindowClass(){ m_hInstance = GetModuleHandle(NULL); /********************************************************/ /* ウィンドウクラスの定義と作成 */ /********************************************************/ // シンプルウィンドウクラス設定 WNDCLASSEX wcex ={sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, WndProcManager, 0,0,m_hInstance, (HICON)LoadImage(NULL,MAKEINTRESOURCE(IDI_APPLICATION),IMAGE_ICON,0,0,LR_DEFAULTSIZE | LR_SHARED), (HCURSOR)LoadImage(NULL,MAKEINTRESOURCE(IDC_ARROW), IMAGE_CURSOR,0,0,LR_DEFAULTSIZE | LR_SHARED), (HBRUSH)GetStockObject(WHITE_BRUSH), NULL, WC_BASIC , NULL}; // シンプルウィンドウクラス作成 if(!RegisterClassEx(&wcex)) return false; return true; } //--------------------------------------------------------------------// // ウィンドウ作成 // // 引数: Title タイトル // Width 幅 // Height 高さ // X X座標 // Y Y座標 // hParentWnd 親ウィンドウハンドル // hMenu メニューハンドルもしくはリソースID // ClassName ウィンドウクラス名 // Style ウィンドウスタイル // ExStyle ウィンドウ拡張スタイル // // 戻り値: true: 作成成功 // false: 作成失敗 //--------------------------------------------------------------------// bool CSWindowClass::CreateWnd( const TCHAR* Title,int Width,int Height,int X ,int Y , HWND hParentWnd,HMENU hMenu,const TCHAR* ClassName,DWORD Style,DWORD ExStyle) { // 重要:あとで正しいウィンドウプロシージャを呼び出すために覚えておきます m_pThis = this; /********************************************************/ /* ウィンドウの作成 */ /********************************************************/ m_hWnd = CreateWindowEx( ExStyle, //拡張ウィンドウスタイル ClassName, //ウィンドウクラス名 Title, //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW | WS_VISIBLE, //ウィンドウスタイル X, //X座標 Y, //Y座標 Width, //幅 Height, //高さ hParentWnd, //親ウィンドウのハンドル、親を作るときはNULL hMenu, //メニューハンドルorリソースID m_hInstance, //インスタンスハンドル NULL); // ウィンドウ作成失敗 if (m_hWnd == NULL) return false; return true; } //-----------------------------------------------------// // ウィンドウプロシージャの管理者 // 基本ウィンドウクラスではこの関数を登録して // この関数を経由して各ウィンドウの本当のプロシージャを呼び出します // // 引数: hWnd ウィンドウハンドル // msg メッセージ // wParam パラメータ // lParam パラメータ // // 戻り値:処理結果 //-----------------------------------------------------// LRESULT CALLBACK CSWindowClass::WndProcManager(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam){ CSWindowClass* pWindow = (CSWindowClass*)(LONG_PTR)GetWindowLong(hWnd,GWL_USERDATA); //thisポインタが取得できた場合 if( pWindow != NULL) { // 本当のウィンドウプロシージャを呼び出しメッセージ処理する LRESULT lResult = pWindow->WndProc( hWnd, msg, wParam, lParam); return lResult; } //thisポインタが取得できなかった場合 else { // ウィンドウの作成時の場合 if( msg == WM_CREATE || msg == WM_INITDIALOG){ // プロパティリストにオブジェクトポインタ(thisポインタ)を設定する SetWindowLongPtr( hWnd, GWL_USERDATA, (LONG)(LONG_PTR)(m_pThis) ); // 初期化メッセージの再送信 SendMessage(hWnd,msg,wParam,lParam); m_pThis = NULL; // 登録したので一時インスタンスポインタ格納の解除 } } return DefWindowProc(hWnd,msg,wParam,lParam); } //-----------------------------------------------------// // ウィンドウプロシージャ // デフォルトの処理はDefWindowProc関数で行う // // 引数: hWnd ウィンドウハンドル // msg メッセージ // wParam パラメータ // lParam パラメータ // // 戻り値:処理結果 //-----------------------------------------------------// LRESULT CALLBACK CSWindowClass::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){ switch(msg){ HANDLE_MSG(hWnd,WM_CREATE,OnCreate); HANDLE_MSG(hWnd,WM_DESTROY,OnDestroy); HANDLE_MSG(hWnd,WM_PAINT,OnPaint); // キーボード HANDLE_MSG(hWnd,WM_CHAR,OnChar); // マウス HANDLE_MSG(hWnd,WM_LBUTTONDOWN,OnLButtonDown); HANDLE_MSG(hWnd,WM_RBUTTONDOWN,OnRButtonDown); HANDLE_MSG(hWnd,WM_MBUTTONDOWN,OnMButtonDown); HANDLE_MSG(hWnd,WM_LBUTTONDBLCLK,OnLButtonDown); HANDLE_MSG(hWnd,WM_RBUTTONDBLCLK,OnRButtonDown); HANDLE_MSG(hWnd,WM_LBUTTONUP,OnLButtonUp); HANDLE_MSG(hWnd,WM_RBUTTONUP,OnRButtonUp); HANDLE_MSG(hWnd,WM_MBUTTONUP,OnMButtonUp); HANDLE_MSG(hWnd,WM_MOUSEMOVE,OnMouseMove); HANDLE_MSG(hWnd,WM_MOUSEWHEEL,OnMouseWheel); // MCI処理終了通知メッセージ case MM_MCINOTIFY:{ MCI_PLAY_PARMS mciPlay; // 通知メッセージ失敗 if (wParam != MCI_NOTIFY_SUCCESSFUL) return 0; // 初期位置に戻す mciSendCommand((MCIDEVICEID)lParam, MCI_SEEK, MCI_SEEK_TO_START, 0); mciPlay.dwCallback = (DWORD_PTR)hWnd; // ループ再生 mciSendCommand((MCIDEVICEID)lParam, MCI_PLAY, MCI_NOTIFY, (DWORD_PTR)&mciPlay); return 0; } } return DefWindowProc(hWnd, msg, wParam, lParam); } // メッセージループ void CSWindowClass::MsgLoop(int ArrayLength,CSWindowClass* pWindowArray){ /********************************************************/ /* メッセージループ */ /********************************************************/ MSG msg = {}; while(msg.message != WM_QUIT) { if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){ TranslateMessage(&msg); DispatchMessage(&msg); } else{ // メッセージ処理をしてないときする処理 for(int i = 0;i < ArrayLength;++i){ pWindowArray[i].Idle(); } } } } // メッセージ処理していないときにする処理 void CSWindowClass::Idle(){ // 継承時に処理を書く } // ウィンドウ作成メッセージ BOOL CSWindowClass::OnCreate(HWND hWnd,LPCREATESTRUCT lpCreateStruct){ return TRUE; } // ウィンドウ破棄メッセージ void CSWindowClass::OnDestroy(HWND hWnd){ PostQuitMessage(0); } // ウィンドウ描画メッセージ void CSWindowClass::OnPaint(HWND hWnd){ PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd,&ps); EndPaint(hWnd,&ps); } // キー入力メッセージ void CSWindowClass::OnChar(HWND hWnd, TCHAR ch, int cRepeat){} // マウス左ボタン押下メッセージ void CSWindowClass::OnLButtonDown(HWND hWnd, BOOL isDoubleClick, int x, int y, UINT keyFlags){} // マウス右ボタン押下メッセージ void CSWindowClass::OnRButtonDown(HWND hWnd, BOOL isDoubleClick, int x, int y, UINT keyFlags){} // マウス真ん中ボタン押下メッセージ void CSWindowClass::OnMButtonDown(HWND hWnd, BOOL isDoubleClick, int x, int y, UINT keyFlags){} // マウス左ボタン押上メッセージ void CSWindowClass::OnLButtonUp(HWND hWnd,int x, int y, UINT keyFlags){} // マウス右ボタン押上げメッセージ void CSWindowClass::OnRButtonUp(HWND hWnd,int x, int y, UINT keyFlags){} // マウス真ん中ボタン押上メッセージ void CSWindowClass::OnMButtonUp(HWND hWnd,int x, int y, UINT keyFlags){} // マウス移動メッセージ void CSWindowClass::OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags){} // マウスホイールメッセージ void CSWindowClass::OnMouseWheel(HWND hWnd, int xPos, int yPos, int zDelta, UINT fwKeys){} ほとんどは今までの回でやったことをそのまま実装しただけです ただし、クラス化する上でいくつか注意しなければならない点があります。 まず、基本ウィンドウクラスの登録用の設定です // 基本ウィンドウクラス設定 WNDCLASSEX wcex ={sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, WndProcManager, 0,0,m_hInstance, (HICON)LoadImage(NULL,MAKEINTRESOURCE(IDI_APPLICATION),IMAGE_ICON,0,0,LR_DEFAULTSIZE | LR_SHARED), (HCURSOR)LoadImage(NULL,MAKEINTRESOURCE(IDC_ARROW),IMAGE_CURSOR,0,0,LR_DEFAULTSIZE | LR_SHARED), (HBRUSH)GetStockObject(WHITE_BRUSH), NULL, WC_BASIC , NULL}; 静的メンバ関数のWndProcManager関数ポインタを指定しています グローバルな関数ポインタあるいは静的なメンバ関数ポインタでないとエラーになるため仕方なく渡しております なので、ウィンドウ別のウィンドウプロシージャはWndProcManagerから呼び出す必要があります これを実現するために CreateWndメンバ関数の実装でまず、オブジェクト自分自身のポインタ(thisポインタ)を覚えて置きます これは、インスタンス化されたオブジェクトと同じポインタとなります // 重要:あとで正しいウィンドウプロシージャを呼び出すために覚えておきます m_pThis = this; このあとCreateWindowEx関数を呼び出します /********************************************************/ /* ウィンドウの作成 */ /********************************************************/ m_hWnd = CreateWindowEx( ExStyle, //拡張ウィンドウスタイル ClassName, //ウィンドウクラス名 Title, //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW | WS_VISIBLE, //ウィンドウスタイル X, //X座標 Y, //Y座標 Width, //幅 Height, //高さ hParentWnd, //親ウィンドウのハンドル、親を作るときはNULL hMenu, //メニューハンドルorリソースID m_hInstance, //インスタンスハンドル NULL); CreateWindowEx関数は基本ウィンドウクラスの 指定されたウィンドウプロシージャ(WndProcManager)へWM_CREATEメッセージを送ります //-----------------------------------------------------// // ウィンドウプロシージャの管理者 // 基本ウィンドウクラスではこの関数を登録して // この関数を経由して各ウィンドウの本当のプロシージャを呼び出します // // 引数: hWnd ウィンドウハンドル // msg メッセージ // wParam パラメータ // lParam パラメータ // // 戻り値:処理結果 //-----------------------------------------------------// LRESULT CALLBACK CSWindowClass::WndProcManager(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam){ CSWindowClass* pWindow = (CSWindowClass*)(LONG_PTR)GetWindowLong(hWnd,GWL_USERDATA); //thisポインタが取得できた場合 if( pWindow != NULL) { // 本当のウィンドウプロシージャを呼び出しメッセージ処理する LRESULT lResult = pWindow->WndProc( hWnd, msg, wParam, lParam); return lResult; } //thisポインタが取得できなかった場合 else { // ウィンドウの作成時の場合 if( msg == WM_CREATE || msg == WM_INITDIALOG){ // プロパティリストにオブジェクトポインタ(thisポインタ)を設定する SetWindowLongPtr( hWnd, GWL_USERDATA, (LONG)(LONG_PTR)(m_pThis) ); // 初期化メッセージの再送信 SendMessage(hWnd,msg,wParam,lParam); m_pThis = NULL; // 登録したので一時インスタンスポインタ格納の解除 } } return DefWindowProc(hWnd,msg,wParam,lParam); } まずいきなりGetWindowLong関数というものが出てきました CSWindowClass* pWindow = (CSWindowClass*)(LONG_PTR)GetWindowLong(hWnd,GWL_USERDATA); GetWindowLong関数の第二引数にGWL_USERDATAを指定すると 第一引数のウィンドウに関連付けられたデータを取得できます 何もデータがない場合は0が入っています (この関数は他にも用途が色々あるのですが、それはまた今度説明します) WM_CREATEが送られてきたウィンドウにはまだ何もデータを入れていないので0(すなわちNULLポインタ)が入っていますので次の処理が行われます //thisポインタが取得できなかった場合 else { // ウィンドウの作成時の場合 if( msg == WM_CREATE || msg == WM_INITDIALOG){ // プロパティリストにオブジェクトポインタ(thisポインタ)を設定する SetWindowLongPtr( hWnd, GWL_USERDATA, (LONG)(LONG_PTR)(m_pThis) ); // 初期化メッセージの再送信 SendMessage(hWnd,msg,wParam,lParam); m_pThis = NULL; // 登録したので一時インスタンスポインタ格納の解除 } } WM_INITDIALOGメッセージについてはまだ説明していませんでしたが、 ダイアログの作成時にはWM_CREATEの代わりにWM_INITDIALOGが一度だけ送られてきます (詳しくはダイアログの回に説明します) 今回の肝はここです // プロパティリストにオブジェクトポインタ(thisポインタ)を設定する SetWindowLongPtr( hWnd, GWL_USERDATA, (LONG)(LONG_PTR)(m_pThis) ); SetWindowLongPtr関数でウィンドウハンドルと対応のあるウィンドウオブジェクト(CSWindowClass型)のポインタをウィンドウデータ領域に保存しておきます そして、WM_CREATEメッセージまたはWM_INITDIALOGメッセージを再送信します // 初期化メッセージの再送信 SendMessage(hWnd,msg,wParam,lParam); メッセージの送信にはSendMessage関数を使います LRESULT SendMessage( HWND hWnd, // 送信先ウィンドウのハンドル UINT Msg, // メッセージ WPARAM wParam, // メッセージの最初のパラメータ LPARAM lParam // メッセージの 2 番目のパラメータ ); SendMessage関数は単純にメッセージ送るだけでなく,コントロールの制御にも使う非常に使い道の広い関数です さて、WndProcManagerに再びメッセージが送られることになるのですが CSWindowClass* pWindow = (CSWindowClass*)(LONG_PTR)GetWindowLong(hWnd,GWL_USERDATA); これ以降は、ウィンドウハンドルに対応付けられたウィンドウデータ領域には対応したウィンドウオブジェクトが入っています メンバ関数化されたウィンドウプロシージャを呼び出すには GetWindowLong関数で取得したウィンドウオブジェクトのポインタから呼びだせばいいだけです //thisポインタが取得できた場合 if( pWindow != NULL) { // 本当のウィンドウプロシージャを呼び出しメッセージ処理する LRESULT lResult = pWindow->WndProc( hWnd, msg, wParam, lParam); return lResult; } なぜ、これだけ回りくどいことをしているのでしょうか? これではいけないのでしょうか? LRESULT CALLBACK CSWindowClass::WndProcManager(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam){ // 本当のウィンドウプロシージャを呼び出しメッセージ処理する return WndProc( hWnd, msg, wParam, lParam); } 実は静的(static)なメンバ関数では通常のメンバ関数やメンバ変数は使えない(コンパイルエラーになる)のです (静的なメンバ関数や静的なメンバ変数はOK) なので一度、ウィンドウデータ空間にウィンドウオブジェクトのポインタ(m_pThis)を持たせ そのウィンドウに対応する本当のウィンドウプロシージャを呼び出す必要があります ---------------------------------------------------------------------------------- メインコード(main.cpp)です ウィンドウをクラス化したことによりすっきりしました #include "WindowClass.h" // コンソール画面隠蔽 #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") int main(int argc,char* argv[]) { // 基本ウィンドウクラス登録 CSWindowClass::RegistBasicWindowClass(); // 基本ウィンドウ作成 CSWindowClass Window; Window.CreateWnd(_T("タイトル")); // メッセージループ CSWindowClass::MsgLoop(); return 0; } 今回のソースファイルは下からダウンロードできます &ref(WindowClass.zip); #vote((^ω^)やったお[1],何これwww意味不すぎwww[0],。(`ω´#)。あぁん?最近、だらしねぇな[0])