- ウィンドウ・プロシージャ
-
GUI アプリの場合、Hellow World の例では
myMainで拡張するのは初期化だけで、アプリケーションの処理はMyWndProcのイベント処理をするのが基本です。const wstring[] MyMsg = [ "Hellow World", "ようこそD言語の世界へ", "次で終了です" ]; int nCount; extern( Windows ){ int MyWndProc( HWND hw, UINT msg, WPARAM wp, LPARAM lp ){ switch( msg ){ case WM_DESTROY: .PostQuitMessage( 0 ); break; case WM_PAINT: PAINTSTRUCT ps; RECT rc; HDC dc = .BeginPaint( hw, &ps ); .GetClientRect( hw, &rc ); .DrawText( dc, MyMsg[ nCount ].ptr, MyMsg[ nCount ].length, &rc, DT_SINGLELINE | DT_VCENTER | DT_CENTER ); .EndPaint( hw, &ps ); return 0; case WM_LBUTTONDOWN: if( nCount < 2 ){ nCount++; .InvalidateRect( hw, null, TRUE ); } else{ .PostMessage( hw, WM_CLOSE, 0, 0 ); } break; default: } return .DefWindowProc( hw, msg, wp, lp ); } }
書き換えれば
Window をクリックするとメッセージが変わり最後終了するようになります。
Win32 API の扱いは C や C++ とほとんど同じです。 - ウィンドウ・クラス
-
メンバー関数をまんま ウィンドウ・プロシージャ に出来れば良いのですが、それは多分出来ませんから、ウィンドウのハンドルがらラッパークラスのインスタンスを得る必要があります。
その場合
- ウィンドウ プロパティを使う
-
GWL_USERDATAを使う - D言語側で変換バッファーを用意する
などが考えられます。
D言語側で変換バッファーを使って処理する方法が、最も安全で汎用性も高くなりますが、いくらか冗長で検索処理が速度面でも不利だと思われます( API 検索処理されていては元も子もありませんが )。
常に通るとこですから速度優先で
GWL_USERDATAを使います(安全性が犠牲になることへの考慮をする必要もありますが、面倒だから、とりあえず無視しちゃいます)。基本はこんなコード
extern( Windows ){ int windProc( HWND hw, UINT msg, WPARAM wp, LPARAM lp ){ auto w = cast( CsWindow )cast( void* ).GetWindowLong( hw, GWL_USERDATA ); assert( w !is null ); return w.mainProc( ScWindMsg( hw, msg, wp, lp ) ); } } struct ScWindMsg{ HWND hWind; UINT nMsg; WPARAM wParam; LPARAM lParam; } class CsWindow{ int mainProc( ref ScWindMsg msg ){ with( msg ){ scope( exit ){ if( nMsg == WM_DESTROY ){ delete this; } } return .CallWindowProc( &.DefWindowProc, hWind, nMsg, wParam, lParam ); } } }
ここの
windowProcをRegisterClassExで渡すと、USER_DATAをセットする前に飛んできてしまいますから、USER_DATAをセットした後にウィンドウ・プロシージャを書き換えます。WM_CREATEを拾う必要があるかどうかで違ってきますが、CreateWindowExでハンドルを得たところでの処理の方がスマートですが、都合が悪いことがあったりもしますから初期化用ウィンドウ・プロシージャと
extern( Windows ){ int createWindowProc( HWND hw, UINT msg, WPARAM wp, LPARAM lp ){ if( msg == WM_CREATE ){ auto w = cast( CsWindow )( cast( LPCREATESTRUCT )lp ).lpCreateParams; assert( w !is null ); // ウィンドウクラス・インスタンスのセット w.handle = hw; .SetWindowLong( hw, GWL_WNDPROC, cast( LONG )&windProc ); } return .DefWindowProc( hw, msg, wp, lp ); } }
CsWindowにインスタンスのセットをclass CsWindow{ private{ HWND hWindow; } public: ~this(){ .SetWindowLong( hWindow, GWL_WNDPROC, cast( LONG )&.DefWindowProc ); } @property{ void handle( HWND hw ){ hWindow = hw; .SetWindowLong( hw, GWL_USERDATA, cast( LONG )cast( void* )this ); } HWND handle(){ return hWindow; } } int mainProc( ref ScWindMsg msg ){ with( msg ){ scope( exit ){ if( nMsg == WM_DESTROY ){ delete this; } } return .CallWindowProc( &.DefWindowProc, hWind, nMsg, wParam, lParam ); } } }
ついでにモジュールを追加します。wind.dmodule wind; import windows; public import std.utf; // win32.windows を public import mixin windows.ImpWinAPI; extern( Windows ){ // WM_CREATE を拾うための int createWindowProc( HWND hw, UINT msg, WPARAM wp, LPARAM lp ){ if( msg == WM_CREATE ){ auto w = cast( CsWindow )( cast( LPCREATESTRUCT )lp ).lpCreateParams; assert( w !is null ); // ウィンドウクラス・インスタンスのセット w.handle = hw; .SetWindowLong( hw, GWL_WNDPROC, cast( LONG )&windProc ); } return .DefWindowProc( hw, msg, wp, lp ); } // int windProc( HWND hw, UINT msg, WPARAM wp, LPARAM lp ){ auto w = cast( CsWindow )cast( void* ).GetWindowLong( hw, GWL_USERDATA ); assert( w !is null ); return w.mainProc( ScWindMsg( hw, msg, wp, lp ) ); } } struct ScWindMsg{ HWND hWind; UINT nMsg; WPARAM wParam; LPARAM lParam; } class CsWindow{ private{ HWND hWindow; } public: ~this(){ .SetWindowLong( hWindow, GWL_WNDPROC, cast( LONG )&.DefWindowProc ); } @property{ void handle( HWND hw ){ hWindow = hw; .SetWindowLong( hw, GWL_USERDATA, cast( LONG )cast( void* )this ); } HWND handle(){ return hWindow; } } int mainProc( ref ScWindMsg msg ){ with( msg ){ scope( exit ){ if( nMsg == WM_DESTROY ){ delete this; } } return .CallWindowProc( &.DefWindowProc, hWind, nMsg, wParam, lParam ); } } }
app.dはcreateWindowに少しmodule app; public import core.runtime, wind; class CsMain{ private{ // 書き換え禁止 const{ HINSTANCE hMainInst; LPTSTR lpCmdLine; } } public: this( in HINSTANCE hi, in LPTSTR cmd ){ hMainInst = hi; lpCmdLine = cmd; } ATOM registClass( in string nam, in int icon, HBRUSH hbr ){ WNDCLASSEX wc; with( wc ){ cbSize = WNDCLASSEX.sizeof; hInstance = cast( HINSTANCE )hMainInst; style = CS_HREDRAW | CS_VREDRAW; lpszClassName = toUTF16z( nam ); // UTF8 -> UTF16 ( 0 終端追加 ) std.utf lpfnWndProc = &createWindowProc; hIcon = .LoadIcon( cast( HINSTANCE )hMainInst, .MAKEINTRESOURCE( icon ) ); hCursor = .LoadCursor( null, IDC_ARROW ); hbrBackground = hbr; } return .RegisterClassEx( &wc ); } HWND createWindow( in LPWSTR cnam, in string wnam, in DWORD style, in DWORD exstyle, in int x, in int y, in int cx, in int cy, CsWindow wnd ){ return .CreateWindowEx( exstyle, cnam, toUTF16z( wnam ), style, x, y, cx, cy, null, null, cast( HINSTANCE )hMainInst, cast( void* )wnd ); } int opCall(){ MSG msg; while( .GetMessage( &msg, null, 0, 0 ) > 0 ){ .TranslateMessage( &msg ); .DispatchMessage( &msg ); } return msg.wParam; } } // // WinMain のテンプレート template TpWinMain( Tc : CsMain, string Tappnam ){ extern( Windows ){ int WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow ){ int ret; void exc( Throwable e ){ throw e; } try{ Runtime.initialize( &exc ); ret = ( new Tc( hInstance, lpCmdLine ) )(); Runtime.terminate( &exc ); } catch( Throwable o ){ ret = .MessageBox( null, toUTF16z( o.toString ), toUTF16z( Tappnam ), MB_OK | MB_ICONERROR | MB_TOPMOST ); } return ret; } } }
hellow.dでCsWindowのmainProcをimport app; const wstring[] MyMsg = [ "Hellow World", "ようこそD言語の世界へ", "次で終了です" ]; int nCount; class CsMyWindow : CsWindow{ override int mainProc( ref ScWindMsg msg ){ with( msg ) switch( nMsg ){ case WM_PAINT: PAINTSTRUCT ps; RECT rc; HDC dc = .BeginPaint( hWind, &ps ); .GetClientRect( hWind, &rc ); .DrawText( dc, MyMsg[ nCount ].ptr, MyMsg[ nCount ].length, &rc, DT_SINGLELINE | DT_VCENTER | DT_CENTER ); .EndPaint( hWind, &ps ); return 0; case WM_LBUTTONDOWN: if( nCount < 2 ){ nCount++; .InvalidateRect( hWind, null, TRUE ); } else{ .PostMessage( hWind, WM_CLOSE, 0, 0 ); } break; default: } return super.mainProc( msg ); } ~this(){ .PostQuitMessage( 0 ); } } class CsMyMain : CsMain{ this( in HINSTANCE hi, in LPTSTR cmd ){ super( hi, cmd ); } override{ int opCall(){ createWindow( cast( LPWSTR )registClass( "MyMainClass", 101, cast( HBRUSH )( COLOR_WINDOW + 1 ) ), "Hellow", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, CW_USEDEFAULT, CW_USEDEFAULT, 300, 200, new CsMyWindow() ); return super.opCall(); } } } mixin TpWinMain!( CsMyMain, "Hellow" );
実行しても変化はありませんが、
- ステップアップ
-
ラッパークラスもこれだけじゃあただコードが肥大化しただけですから、もう少しラッパークラスらしくしてみましょう。
ウィンドウに関連する API にはちょっと見ただけで
などなどあって、Window Notifications だとかもあって、全部やっつけるのちょっと無理。
とりあえず必要なものから、遊びながら
方針をいくつか
- API のハンドルを出来るだけ隠す
- 引数にポインターも出来るだけ使わない
- 汎用性をよく考える
- 遊ぶ
こんなところで
DC関連、とりあえずDrawTextだけstruct ScDc{ HDC hDc; void drawText( in wstring msg, ref RECT rc, in UINT fg ){ .DrawText( hDc, msg.ptr, msg.length, &rc, fg ); } }
CsWindowはclass CsWindow{ private{ HWND hWindow; } protected{ int onPaint( ref ScDc ){ return 0; } void onClick(){} } public: ~this(){ .SetWindowLong( hWindow, GWL_WNDPROC, cast( LONG )&.DefWindowProc ); } @property{ void handle( HWND hw ){ hWindow = hw; .SetWindowLong( hw, GWL_USERDATA, cast( LONG )cast( void* )this ); } HWND handle(){ return hWindow; } RECT clientRect(){ RECT rc; .GetClientRect( hWindow, &rc ); return rc; } } int mainProc( ref ScWindMsg msg ){ with( msg ){ scope( exit ){ if( nMsg == WM_DESTROY ){ delete this; } } int ret; switch( nMsg ){ case WM_PAINT: PAINTSTRUCT ps; scope( success ) .EndPaint( hWindow, &ps ); ret = onPaint( ScDc( .BeginPaint( hWindow, &ps ) ) ); break; case WM_LBUTTONDOWN: onClick(); default: ret = .CallWindowProc( &.DefWindowProc, hWind, nMsg, wParam, lParam ); } return ret; } } int postMessage( in UINT msg, in WPARAM wp = 0, in LPARAM lp = 0 ){ return .PostMessage( hWindow, msg, wp, lp ); } void invalidateRect( in RECT* rc = null, in BOOL fg = TRUE ){ .InvalidateRect( hWindow, rc, fg ); } }
onPaintとonClickをCsMyWindowでclass CsMyWindow : CsWindow{ immutable wstring[] MyMsg = [ "Hellow World", "ようこそD言語の世界へ", "次で終了です" ]; int nCount; override{ int onPaint( ref ScDc dc ){ auto rc = clientRect; dc.drawText( MyMsg[ nCount ], rc, DT_SINGLELINE | DT_VCENTER | DT_CENTER ); return 0; } void onClick(){ if( nCount < 2 ){ nCount++; invalidateRect(); } else{ postMessage( WM_CLOSE ); } } } ~this(){ .PostQuitMessage( 0 ); } }
- タプルで遊ぶ
-
CsWindowのonClickは実装なしで問題ありませんが、onPaintは若干・・・なので、// タプル Ts に値 T が含まれるかを検索する template IsInt( UINT T, Ts ... ){ immutable: static if( Ts.length == 0 ){ int IsInt = 0; } else static if( is( typeof( Ts[ 0 ] ) : immutable( UINT ) ) && Ts[ 0 ] == T ){ int IsInt = 1; } else{ int IsInt = IsInt!( T, Ts[ 1 .. $ ] ); } }
何とも効率の悪そうなコードしか書けませんが、重くなるのはコンパイルで、実行時には関係ありませんから気にしないことにします。
ウィンドウメッセージを拾うためのテンプレートクラスを
class TcWindowProc( T ... ) : CsWindow{ private{ immutable{ int isOnPaint = IsInt!( WM_PAINT, T ); int isOnClick = IsInt!( WM_LBUTTONDOWN, T ); } } protected{ abstract{ static if( isOnPaint ){ int onPaint( ref ScDc ); } static if( isOnClick ){ void onClick(); } } } public: override int mainProc( ref ScWindMsg msg ){ int ret; with( msg ) switch( nMsg ){ static if( isOnPaint ){ case WM_PAINT: PAINTSTRUCT ps; scope( success ) .EndPaint( hWindow, &ps ); ret = onPaint( ScDc( .BeginPaint( hWindow, &ps ) ) ); break; } static if( isOnClick ){ case WM_LBUTTONDOWN: onClick(); } default: ret = super.mainProc( msg ); } return ret; } }
CsWindowのmainProcは最小限に戻して、CsMyWindowをclass CsMyWindow : TcWindowProc!( WM_PAINT, WM_LBUTTONDOWN ){
2010年6月26日

