2010/6/23 - 2011/8/14
Pen Jr.D言語なんてやってみそWin32APIウィンドウ
<<・・ウィンドウ
・・>>
ウィンドウ・プロシージャ
GUI アプリの場合、Hellow World の例では myMain で拡張するのは初期化だけで、アプリケーションの処理は MyWndProc のイベント処理をするのが基本です。

最後の Hellow.d 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++ とほとんど同じです。
ウィンドウ・クラス
メンバー関数をまんま ウィンドウ・プロシージャ に出来れば良いのですが、それは多分出来ませんから、ウィンドウのハンドルがらラッパークラスのインスタンスを得る必要があります。

その場合

などが考えられます。

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 );
    }
  }
}

ここの windowProcRegisterClassEx で渡すと、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.d

module 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.dcreateWindow に少し

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.dCsWindowmainProc

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" );

実行しても変化はありませんが、

サンプル-1( Window00.zip ) のダウンロード
ステップアップ
ラッパークラスもこれだけじゃあただコードが肥大化しただけですから、もう少しラッパークラスらしくしてみましょう。

ウィンドウに関連する 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 );
  }
}

onPaintonClickCsMyWindow

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 );
  }
}

実行結果は同じですが、少しはラッパークラスらしくなってきました。

サンプル-2( Window01.zip ) のダウンロード
タプルで遊ぶ
CsWindowonClick は実装なしで問題ありませんが、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;
  }
}

CsWindowmainProc は最小限に戻して、

CsMyWindow

class CsMyWindow : TcWindowProc!( WM_PAINT, WM_LBUTTONDOWN ){
サンプル-3( Window02.zip ) のダウンロード

2010年6月26日

WinAPI
Recommend が前提です。
E-Mail : open@pen-jr.org

ご意見やご指摘は大歓迎ですが、質問など返信は期待しないでください

pen jr.
D言語研究
わかったつもりになるD言語
D言語友の会
News&Days
はじめてのブログ選び