閉じる


はじめに

 

・まだ執筆中なんですが、例によって

 公開しながらの加筆、という形をとっています。

 

・「構文編」から読まれている皆さんは、

 他サイトなどをご覧になって、独習を進めておられると思いますが、

 本書では、その中でも、特に詰まりそうな箇所について

 優先的に書き進めております。

 

ある程度理解できた段階でハマる「サブクラス化」についても、

 ググっても理解できなかった方でもわかるように

 わかりやすいサンプルコードを載せています。

 

画面描画については、そのうち書きますが、

 msdnでも解説されていますので、そちらをご覧ください。↓

 

 http://msdn.microsoft.com/ja-jp/library/windows/desktop/ff381399(v=vs.85).aspx


DefWindowProc関数 ( メッセージループとウィンドゥプロシージャ )

 

 

 

・さて、あなたのつくえの上には、パソコンがあって、

 画面には、Windowsのデスクトップ画面

 ウィンドゥ表示されているとします。

 

・その手前に置かれたイスには、

 あなた(または別のユーザー)が座っていて、マウス動かしている

 

・そしてたまに、キーボードに手をうつして、キーをカタカタ打っている

 

・これをもう少しコンピュータ寄りの視点でまとめると、

 おおよそ、次のような流れになります。↓

 


1. ユーザー入力した情報は、入力機器からWindowsに送られる。

 

 [ユーザー] -(入力)[キーボードorマウス] -(入力信号)[Windows]


2. Windowsは、アプリケーションプログラムの「メッセージキュー」に、

  ウィンドゥメッセージ送信する。

 

 [Windows] - (ウィンドゥメッセージ)[ メッセージキュー ]


 

3. アプリケーション (あなたの作ったプログラム) は、

 GetMessage関数を、定期的に呼び出して、

 「メッセージキュー」に詰め込まれたウィンドゥメッセージ」を

 1つずつ取り出しては、それに応じた処理を行います。

 

・このメッセージ取り出しは、通常、

 「メッセージループ」といわれる無限ループで行います。↓

 


// 「プロジェクト」を作成した時に、「Windowアプリケーション」を選択した場合は、

// _WinMain関数最後に、下記のような「メッセージループ」が書かれている。↓

 

MSG msg; // ウィンドゥメッセージを受け取るMSG構造体


while ( ::GetMessageW( &msg, NULL, 0, 0 ) > 0 ) // キューがカラなら0、エラー時は-1を返す。
{
    ::TranslateMessage( &msg );  // 文字キーの入力なら、「WM_CHAR」を送信する。
    ::DispatchMessageW( &msg ); // 送信先ウィンドゥの「ウィンドゥプロシージャ」を呼ぶ。
}

 

return msg.wParam; // プログラムを終了する。


 

GetMessage関数に渡したMSG構造体には、

 送信先のウィンドゥを示す「ハンドル」、(HWND型)

 メッセージの種類を示す「メッセージコード」、

 クリックされた位置などの「パラメータ」、(メッセージにより異なる)

 が格納されます。

 

・これを、DispatchMessage関数渡すと、

 「ウィンドゥクラス」を登録した時に指定した「ウィンドゥプロシージャ」が呼ばれます。

 

・たとえば、ボタンクリックされた時

 ウィンドゥの上に絵を表示させたりする場合は、

 ウィンドゥプロシージャの中に、その処理を書いておきます。

 

・このように、パソコン上でのあらゆる操作は、

 Windowsを介して行われます。

 

アプリケーションプログラムは、

 WindowsAPIの関数呼び出すことで

 コンピューター間接的に操作します。

 


// ウィンドゥプロシージャ

 

LRESULT CALLBACK WindowProc(

                                              HWND h_wnd_,  // 送信先ウィンドゥハンドル

                                              UINT   msg_,     // メッセージコード

                                              WPARAM wp_,  // Wパラメーター

                                              LPARAM  lp_    // Lパラメーター

                                             )
{

 

  switch ( msg_ ) // メッセージコード判定していく。
  {

 

    // ------------
    case WM_LBOTTONDOWN: // 左クリックなら、

 

     // ここに、左クリックされた時の処理を書く。

     break;

    // ------------

 

  } // end switch

 

  ::DefWindowProcW( h_wnd_, msg_, wp_, lp_ ); // 親ウィンドゥに「通知コード」を送信する。

 

} // end proc


 

メッセージ処理は、体感的には、

 入力されたと同時に実行されているかのように見えますが、

 厳密には、そうではありません。

 

・「メッセージループ」は、ものすごい速度で回っているので気付かないんですが、

 「メッセージキュー」のでは、多数のメッセージが、常に順番待ちをしていますので 

 送信されたからといって、すぐに取り出して処理することはできません。

 

ウィンドゥメッセージは、通常、Windows送信するものですが、

 アプリケーションから送信することもできます。↓

 


// メッセージキューメッセージ送信したら、すぐに制御を戻す。↓

 PostMessageW( h_wnd_, msg_, wparam_, lparam_ );


// メッセージキュー送信したメッセージ処理されるまで制御を戻さない。↓

 SendMessageW( h_wnd_, msg_, wparam_, lparam_ );


 

・「メッセージキュー」は、「スレッド」ごとに持つことができます。

 

・「スレッド」というのは、同じプロセス内で、

 さらに複数の処理を、同時に実行するための

 より細かな実行単位です。( スレッドについては、詳細は後述します )

 

上の図では、「プロセスは、同時には動いていない」と書きましたが、

 最近は、演算装置(コア)が複数付いた「マルチコアプロセッサ」ですから、

 同時に動いています。

 

 

ボタンなどのコントロールは、通常、ウィンドゥの上表示されますが、

 これは、ボタンが、ウィンドゥ子ウィンドゥとして登録されているからです。

 

子ウィンドゥでは、クリックなどのイベント発生すると、

 ウィンドゥメッセージ ( WM_ほにゃらら ) が送信されず、

 親ウィンドゥに向けて、WM_COMMAND送信されます。

 

 ( ※ SendMessage関数などで送信した場合は、送信される。)

 

・そんなわけで、子ウィンドゥイベント処理は、

 親ウィンドゥ側ウィンドゥプロシージャの中

 書いていくことになるわけです。

 

WM_COMMANDパラメータには、通知種類や、

 送信者である子ウィンドゥの識別IDなどが格納されています。

 

コモンコントロールの場合は、WM_COMMANDではなく、

 WM_NOTIFY送信されます。

 

 


CreateWindow関数 ( ウィンドゥを作成する )

 

 

ウィンドゥ」を、画面上に表示させるには、

 次のような手順が必要です。↓

 


  1. 「ウィンドゥクラス」を登録する。( ※いわゆる「クラス」とは別物 )

  2. それを指定して、「ウィンドゥ」を作成する。

  3. その「ウィンドゥ」を表示させる。


 

・「プロジェクト」を作成した時に、

 「Windowアプリケーション」を選択した場合は、

 メインとなるウィンドゥ作成する処理すでに書いてあるはずですが、

 ここではクラス化した場合のサンプルコードで説明していきます。↓

 



BOOL CForm::Init(
                           HWND h_parent_,              // 親ウィンドゥハンドル
                           const wchar_t* p_name_,  // ウィンドゥクラス名前
                           const wchar_t* p_text_,    // タイトルバーに表示される文字列
                           int x_, int y_,              // ウィンドゥの表示位置 (左上端の座標)
                           int width_, int height_ // ウィンドゥのサイズ
                              )
{

 

// ------------------------
// ウィンドゥクラススタイルを決めておく。

 

DWORD c_style = CS_BYTEALIGNWINDOW | CS_BYTEALIGNCLIENT |

// 速度的に有利 (クライアント領域のX座標が8の倍数になるように配置 )

// CS_CLASSDC | // このクラスに属するすべてのウィンドゥで、DCを共有する。
CS_OWNDC | // このクラスに属するウィンドゥには、それぞれ独自のDCを割り当てる。
// CS_PARENTDC | // 子ウィンドゥの場合は、これが必要。

// (子ウィンドゥクリッピング領域に、親ウィンドゥクリッピング領域を設定)
// CS_HREDRAW | CS_VREDRAW | // ウィンドウサイズ変化した際に、画面更新する。

// ( ※ちらつきが発生するので使用しない。

//      代わりに、WM_SIZEではInvalidateRect関数を呼んでいる )
CS_DBLCLKS; // Wクリック時にもメッセージを発生させる。

// CS_NOCLOSE | //「閉じる」を無効にします。
//CS_SAVEBITS // メニューダイアログなどで使用、無くてもいい。

 

// ------------------------
// ウィンドゥクラス登録する。

 

WNDCLASSEXW wc = {}; // ウィンドゥクラス構造体

 

wc.hInstance = p_appli->GetInstance(); // アプリケーションインスタンスハンドラ HINSTANCE

// これは、_tWinMain関数第一引数hInstance。関数から取得することもできる。↑
wc.lpszClassName = p_name_; // ウィンドゥクラス名
wc.lpfnWndProc = WindowProc_Init; // ★ウィンドゥプロシージャ。 (初期化用を格納しておく)

wc.style = c_style; // ウィンドゥクラススタイル。 ( 先ほどフラグ定数を詰め込んだ整数値 )
wc.cbSize = sizeof(WNDCLASSEXW); // この構造体のサイズ。 (※必須)

wc.hIcon      = NULL;  // 大きめのアイコンハンドル
wc.hIconSm = NULL;  // 小さめアイコンハンドル。 (タイトルバー)
wc.hCursor  = NULL;  // マウスポインタハンドル
wc.lpszMenuName = NULL; // メニューを付ける場合は、その名前

wc.cbClsExtra   = 0; // ウィンドウクラスごと固有のデータを格納できる。
wc.cbWndExtra = 0; // ウィンドウごと固有のデータを格納できる。

wc.hbrBackground = (HBRUSH) COLOR_APPWORKSPACE + 1; // 作業領域背景色

 

// ウィンドゥクラス登録する。
if ( ::RegisterClassExW( &wc ) == 0 ) return FALSE; 

 

// ------------------------
// ウィンドゥスタイルを決めておく。 ( 後で再設定できるので、適当でいい )

 

DWORD w_ex_style = WS_EX_CONTROLPARENT | // タブ移動を有効にする。
// WS_EX_APPWINDOW | // 一番上にあるウィンドゥを表示するときに、強制的にタスクバーに含みます。
// WS_EX_ACCEPTFILES | // ウィンドゥファイルドロップした際、WM_DROPFILESが送られる。

// WS_EX_LAYERED | // 半透明になる。 ( 不透明度は、SetLayeredWindowAttributes関数で指定 )
// WS_EX_TRANSPARENT | // 透明になる。 ( 下のウィンドウが更新された時にだけ、WM_PAINTを受信 )

WS_EX_TOPMOST | // 常に最前面に表示される。( SetWindowPos関数で変更する)

// WS_EX_TOOLWINDOW | // [X]ボタンだけ表示。

// ( hwnd引数にNULLを指定すると、タスクバーに表示されないトップレベルウィンドゥ扱いになる。

//   一方、hwnd引数に親のhwndを指定すると、 親ウィンドゥ連動して最小化するウィンドゥになる。

//   hwnd引数に親のhwndを指定して、さらにWS_CHILDウィンドゥIDを指定した場合は、

//   親ウィンドゥクライアント領域内だけで動作する子ウィンドゥになる。)
// WS_EX_CONTEXTHELP | // タイトルバー[?]ボタン を表示。 ( 子ウィンドゥWM_HELPを受け取る )

// WS_EX_MDICHILD // MDI 子ウィンドゥを作成する。
// WS_EX_NOPARENTNOTIFY // 子ウィンドゥの場合、作成・破棄時に

                                       // 親ウィンドゥWM_PARENTNOTIFYを送らない。

WS_EX_COMPOSITED; // ウィンドゥへの描画が、自動的にダブルバッファリングされ、ちらつきがなくなる。

 

DWORD w_style = WS_OVERLAPPED | // オーバーラップウィンドゥを作成する。

// ( タイトルバー外枠があり、親ウィンドゥ移動できる。いわゆる「ウィンドゥ」のこと。 )
WS_CAPTION | // タイトルバーを持つウィンドゥを作成する。
WS_SYSMENU|// タイトルバーに、最大化最小化閉じるなどのボタンを配置する。

WS_HSCROLL | WS_VSCROLL | // 縦・横のスクロールバーを追加する。
WS_THICKFRAME |    // サイズ変更許可する。 ( リサイズ用太めの枠が付く )
WS_MINIMIZEBOX |  // 最小化ボタンの使用を許可する。
WS_MAXIMIZEBOX | // 最大化ボタンの使用を許可する。
WS_VISIBLE ; // 表示する。


// WS_DISABLED | // ウィンドゥを無効にする。
// WS_MAXIMIZE | WS_MINIMIZE; // 最大or最小表示する。

// WS_POPUP // ポップアップ。( ※WS_CHILD では使えない。ポップアップの親はデスクトップ画面)

// WS_TABSTOP // タブストップを許可する。 ( 子コントロール向け )
// WS_DLGFRAME // 二重境界を持ち、タイトル無し。
// WS_GROUP // コントロールグループの最初のコントロール。(方向キーで移動できる)

                   // 次に指定したコントロールまでが同じグループとなる。

 

// ------------------------
// ウィンドゥ作成する。

 

h_window = ::CreateWindowExW(
                                   w_ex_style, // 拡張ウィンドゥスタイルを指定 ( これは0でもいい )
                                   p_name_, // ウィンドゥクラス名
                                   p_text_, // タイトルバーに表示される文字列
                                   w_style, // ウィンドゥスタイル
                                   x_, // ウィンドゥの左端 (x座標)
                                   y_, // ウィンドゥの上端 (y座標)
                                   width_, // ウィンドゥの横幅
                                   height_, // ウィンドゥの縦幅
                                   h_parent_, // 親ウィンドゥハンドル HWND
                            NULL, // メニューハンドル HMENU
                                   wc.hInstance , // アプリケーションインスタンスハンドラ HINSTANCE
                            NULL ); // CREATESTRUCT構造体へのポインタ。( MDIクライアント作成時には必須 )

 

if ( h_window == NULL ) // 作成に失敗したら、
{
   ::UnregisterClassW( p_name_, wc.hInstance ); // ウィンドゥクラス抹消する。

 

   return FALSE;
}

 

// ------------------------
// ★thisポインタを、格納しておく。( ウィンドゥプロシージャから、メッセージハンドラ呼ぶのに使う )

 

::SetWindowLongPtrW( h_window, GWLP_USERDATA, (LONG) this );
// ------------------------
// ★ウィンドゥプロシージャを、本番用すり替える

 

::SetWindowLongPtrW( h_window, GWLP_WNDPROC, (LONG) WindowProc );

// ------------------------
::UpdateWindow( h_window ); // ウィンドウ更新する。
::ShowWindow( h_window, SW_SHOWNORMAL ); // ウィンドゥ表示する。
// ------------------------

 

return TRUE; // 作成に成功した。


}


 // 初期化時にだけ使用するウィンドゥプロシージャ

 

LRESULT CALLBACK WindowProc_Init(

      HWND h_wnd_, UINT msg_, WPARAM wparam_, LPARAM lparam_ )
{

 

   // -----------------
   // ウィンドゥ生成されたら、( CreateWindow関数が呼ばれたら )

 

   if ( msg_ == WM_CREATE )
   {

      // その他のメッセージは、システムに処理してもらう。 ( への通知コード送信標準描画処理 )
       ::DefWindowProcW( h_wnd_, msg_, wparam_, lparam_ ); 

       return 0; // 0を返すと、CreateWindow関数成功する。
    }

 

    // -----------------

    // その他のメッセージは、システムに処理してもらう。 ( への通知コード送信標準描画処理 )

    return ::DefWindowProcW( h_wnd_, msg_, wparam_, lparam_ )

    // -----------------
}


// ウィンドゥプロシージャ ( すべてのウィンドゥインスタンスで共有する )

 

LRESULT CALLBACK WindowProc(

        HWND h_wnd_, UINT msg_, WPARAM wparam_, LPARAM lparam_ )
{

 

   // -----------------
   // 送信先ウィンドゥのハンドルから、コントロールインスタンスへのポインタ取得する。

 

   CControl* p_control = (CControl*) ::GetWindowLongPtrW(

                                                                         h_wnd_, GWLP_USERDATA );
   // -----------------
   // インスタンスごとのメッセージ処理呼び出す

   // 送信先インスタンスハンドラ呼ぶ。( 処理した場合は、0を返す。 )
   return p_control->MessageHandler( h_wnd_, msg_, wparam_, lparam_ );


// -----------------
}


 

・通常は、ウィンドゥプロシージャの中に、処理を書いていくんですが、

 ウィンドゥプロシージャグローバル関数でないといけないので、

 ウィンドゥクラス化した場合に、メソッドを指定することができません

 

・しかし、ウィンドゥプロシージャには、

 送信先ウィンドゥのハンドル渡されますので、

 これを使って、ウィンドゥインスタンスthisポインタ取り出すことができ、

 インスタンスごとの処理メソッド呼ぶことができます。

 

 ・ただし、ここでthisポインタ受け取るには、あらかじめ、

 ウィンドゥの GWLP_USERDATA に、格納しておく必要があります。

 

MessageHandlerメソッドは、CControlクラスメンバです。

 

・「仮想関数」として定義されていますので、上書きして使うこともできます。

 

CFormクラスは、CControlクラス派生させたものです。

 

CControlクラスというのは、ウィンドゥのハンドルをくるんだクラスで、

 すべてのコントロールクラス親クラスです。

 

ウィンドゥのハンドルは、CreateWindow関数戻り値で、

 これがあれば、GetWindowLongPtr関数などから、

 そのウィンドゥに関する情報取得したり、

  SetWindowLongPtr関数などから

 変更し直したりすることができます。

 

ウィンドゥのハンドルは、「HWND型」です。

 

Windows は、

 ハードディスク上の領域や、

 メモリ上の領域など、

 すべてを管理しています。

 

アプリケーションソフトが、

 ハードディスク上のファイルや、

 ウィンドゥコントロールなどを使用するときは、

 Windowsからレンタルするような形を取り、

 不要になったら返却します。

 

アプリケーションソフトは、Windowsから

 「システムリソース」 (システム資源) をレンタルするときに、

 それらを識別するための「ハンドル」というものを取得します。

 

・たとえば、メニューコントロールハンドルは「HMENUで、

 CreateMenu関数戻り値として取得することができます。

 

WindowsAPIの関数には、

 リソースを操作するための関数が多数ありますが、

 操作対象となるリソース指定するために、

 第一引数に、この「ハンドル」を渡します。

 

 

・まるで部屋を借りるような感覚で

 Windowsからウィンドゥ貸してもらい

 料理を注文するような感覚で、

 Windows位置を移動させてもらうのです。

 

・そして、使い終わったら、ウィンドゥ返却します。

 

アプリケーションソフトは、

 こうした指示Windowsへと送るために、

 WindpwsAPIの関数呼び出しているのです。

 


同期オブジェクト

 

・「同期オブジェクト」は、

 複数のスレッドが並走している中で、

 スレッドを1つずつ動かしたい場合に使用します。

 

・複数のスレッドが並走している場合は、

 同じデータに対して同時に書き込みを行うことで、

 データが破損してしまうことがあります。

 

・「同期オブジェクト」は、複数のスレッドから

 同時にアクセスされるデータに対して設定され、

 複数のスレットが、同時にアクセスできなくします。

 

・WaitForSingleObject関数を呼ぶと、

 そのスレッドは順番待ちに加わります。

 

・この時点で、動作中のスレッドがなければ、

 このスレッドが動作を開始します。 ( スレッド1のケース )

 

・そうでない場合は、そのスレッドの処理が

 終わるまで待機します。 ( スレッド2のケース )

 

 

 


リソースファイルを編集する

 

コントロールメニューなど、一部のシステムリソースは、

 実行ファイルの内部埋め込むことができます。

 

埋め込むリソース情報は、「リソースファイル」(*.rc) に書きます。

 

  ( 「リソーススクリプト」という簡易スクリプト言語で記述します )

 


 

・「Express Edition」では、リソースファイル編集ができません

 

  ( ファイル上で右クリックして、「開く」 ではなく、「コードの表示」を選べば、表示はされます。 )


編集するには、「ResEdit」というエディタソフトを使用します。↓

 

 http://gurigumi.s349.xrea.com/programming/visualcpp/resedit.html



インストールした時点では、英語表示されていますが、

 日本語で表示することもできます。↓

 

 [Option]→[Preferences]→[General]→[Language] を、

 [Japanese] に切り替える。


 

プロジェクト側には、上記のエディタで作成した「リソースファイル」と、

 「リソースID定数#defineした「ヘッダファイル」を追加します。↓

 


// 《 dialog_test.dlg

MyDialog DIALOG 21, 16, 146, 55
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "タイトル"
FONT 12, "System"


BEGIN
   LTEXT "ラベルテキスト1", ID_LABEL1, 47, 6, 82, 8
   EDITTEXT ID_EDIT1, 44, 18, 94, 12, ES_AUTOHSCROLL
   PUSHBUTTON "OK", ID_OK, 44, 35, 40, 14
   PUSHBUTTON "キャンセル", ID_CANCEL, 96, 35, 40, 14
END


// 《 dialog_test.h


#define ID_LABEL1 102
#define ID_EDIT1 103
#define ID_OK 104

#define ID_CANCEL 105


// 《 dialog_test.rc


#include "test_dialog.h"
#include "test_dialog.dlg"


// 《 dialog_test.cpp


#include "test_dialog.h"

 

int APIENTRY _tWinMain( HINSTANCE h_instance_ , /* 省略 */ )

{
    ::DialogBox( h_instance_, "MyDialog", NULL, MyDialogProc );

 

    return 0;

}


 

ダイアログの場合は、「ダイアログファイル」 (*.dlg) に、リソーススクリプトを書きます。

 

・この中で、「ID_OK」といった定数が使われていますが、

 これは、コントロールリソースのIDで、ソースファイル上でも使用しますので、

 ヘッダファイル上定義しておきます。

 

 



読者登録

hasehamさんの更新情報・新作情報をメールで受取りますか?(読者登録について