スマートフォン・ジン | Smartphone-Zine

引っ越し先→ https://smartphone-zine.com/

021:子ウィンドウを作る

親ウィンドウの中に子ウィンドウを作成してみましょう。
親ウィンドウと同じサイズの子ウィンドウを2つ作成して、表示、非表示を切り替える事で、アプリケーション内での画面遷移を実現してみましょう。

子ウィンドウの作成は、親ウィンドウと同じくCreateWindow関数を使用します。

HWND CreateWindow(
  LPCTSTR lpClassName,  // 登録されているクラス名
  LPCTSTR lpWindowName, // ウィンドウ名
  DWORD dwStyle,        // ウィンドウスタイル
  int x,                // ウィンドウの横方向の位置
  int y,                // ウィンドウの縦方向の位置
  int nWidth,           // ウィンドウの幅
  int nHeight,          // ウィンドウの高さ
  HWND hWndParent,      // 親ウィンドウまたはオーナーウィンドウのハンドル
  HMENU hMenu,          // メニューハンドルまたは子ウィンドウ ID
  HINSTANCE hInstance,  // アプリケーションインスタンスのハンドル
  LPVOID lpParam        // ウィンドウ作成データ
);

子ウィンドウを作成するには、親ウィンドウのWM_CREATE時に、CreateWindow関数でウィンドウを生成します。
第3引数のdwStyleには、WS_CHILDスタイルを与えます。
このとき第8引数のhWndParentに親ウィンドウのハンドルを指定します。今までは親ウィンドウの生成でしたので、NULLにしていましたね。

GetWindowRect関数で親ウィンドウのサイズを取得して子ウィンドウにも同じサイズを指定します。

BOOL GetWindowRect(
  HWND hWnd,      // ウィンドウのハンドル
  LPRECT lpRect   // ウィンドウの座標値
);

GetWindowRect関数はウィンドウのハンドルで指定したウィンドウのサイズを、RECT構造体に格納します。この関数より取得したサイズで、子ウィンドウを作成すれば、親ウィンドウと同じサイズの子ウィンドウができます。

親ウィンドウのウィンドウプロシージャWndProc内では、ウィンドウの初期化時にWM_CREATEメッセージが送られてきます。ここで子ウィンドウA、子ウィンドウBを生成しています。


親ウィンドウの「OPEN_A」,「OPEN_B」ボタンを押すとそれぞれ子ウィンドウA、子ウィンドウBを表示します。
子ウィンドウに配置した「CLOSE」ボタンを押すと、親ウィンドウへ戻ります。これは子ウィンドウを非表示にすることで実現しています。

親ウィンドウプロシージャ内の記述としては
 「OPEN_A」が押されたら子ウィンドウAを表示状態にする。
 「OPEN_B」が押されたら子ウィンドウBを表示状態にする。
子ウィンドウプロシージャ内の記述としては
 「CLOSE」が押されたら、自ウィンドウを非表示にする。(親ウィンドウが表示される)。

とすれば実現できますが、今回は、画面遷移の処理をすべて親ウィンドウに集めてみましょう。

親ウィンドウプロシージャ内の記述としては
 「OPEN_A」が押されたら子ウィンドウAを表示状態にする。
 「OPEN_B」が押されたら子ウィンドウBを表示状態にする。
 「CLOSE」が押されたら、子ウィンドウAが表示状態なら、子ウィンドウAを非表示にする。
 子ウィンドウBが表示状態なら、子ウィンドウBを非表示にする。
子ウィンドウプロシージャ内の記述としては
 「CLOSE」ボタンが押されたら、親ウィンドウに対してメッセージを送る。

とすることで、画面遷移の処理を親ウィンドウに集めることができそうです。

// ChildWindow.cpp
//
// リージョンの結合

#include <windows.h
>


#define BUTTON_ID   1000
#define CHILD_ID_A  5000
#define CHILD_ID_B  5001
#define BUTTON_ID_OPEN_A 6000
#define BUTTON_ID_OPEN_B 6001




LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM
);
LRESULT CALLBACK ChildWindowProc(HWND , UINT , WPARAM , LPARAM 
);
ATOM InitApp(HINSTANCE
);
HWND InitInstance(HINSTANCE, int
);
HINSTANCE hInst
;

TCHAR szClassName[] = T("ParentWindow"); // ウィンドウクラス。UNICODEとしての文字列定数

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine,int nShowCmd
)
{
   MSG msg
;
   BOOL bRet
;
   HWND hWnd
;

   hInst = hInstance
;

   if (!InitApp(hInstance
))
       return FALSE
;
   if (!(hWnd = InitInstance(hInstance,nShowCmd
)))
       return FALSE
;
   while((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) 
{
       if (bRet == -1
){
           break
;
       } else 
{
           TranslateMessage(&msg
);
           DispatchMessage(&msg
);
       
}
   
}
   return (int)msg.wParam
;
}

// ウィンドウクラスの登録

ATOM InitApp(HINSTANCE hInst
)
{
   WNDCLASS wc
;
   wc.style        = CS_HREDRAW|CS_VREDRAW
;
   wc.lpfnWndProc  = WndProc;  // プロシージャ名
   wc.cbClsExtra   = 0
;
   wc.cbWndExtra   = 0
;
   wc.hInstance    = hInst
;
   wc.hIcon        = NULL;     // 未サポート
   wc.hCursor      = NULL;     // 未サポート
   wc.hbrBackground= (HBRUSH) COLOR_WINDOW
;
   wc.lpszMenuName = NULL;     // 未サポート
   wc.lpszClassName=(LPCTSTR) szClassName
;

   return (RegisterClass(&wc
));
}

// ウィンドウの生成
HWND InitInstance(HINSTANCE hInst, int nShowCmd
)
{
   HWND hWnd
;

   hWnd = CreateWindow
(
       szClassName,
T("ChildWindowサンプル"),
       WS_CLIPCHILDREN,    // ウィンドウの種類
       CW_USEDEFAULT,      // x座標
       CW_USEDEFAULT,      // y座標
       CW_USEDEFAULT,      // 幅
       CW_USEDEFAULT,      // 高さ
       NULL,               // 親ウィンドウのハンドル。親を作るのでNULL
       NULL,               // メニューハンドルまたは子ウィンドウID
       hInst,              // インスタンスハンドル
       NULL
);
   if (!hWnd
)
       return FALSE
;
   ShowWindow(hWnd, nShowCmd
);
   UpdateWindow(hWnd
);
   return hWnd
;
}
// ウィンドウクラスの登録
void CreateWindowClass(HINSTANCE hInst, LPCTSTR szClassName, WNDCLASSW &wc
)
{
   wc.style  = CS_HREDRAW|CS_VREDRAW
;
   wc.lpfnWndProc = ChildWindowProc ; // プロシージャ名
   wc.cbClsExtra = 0
;
   wc.cbWndExtra = 0
;
   wc.hInstance = hInst
;
   wc.hIcon  = NULL
;
   wc.hCursor  = NULL
;
   wc.hbrBackground= reinterpret_cast<HBRUSH>(GetStockObject(GRAY_BRUSH)); //グレーにする
   wc.lpszMenuName = NULL;  // 未サポート
   wc.lpszClassName=(LPCWSTR) szClassName
;
}

// 子ウィンドウの生成
HWND CreateChildWindow(HINSTANCE hInst, int nShowCmd,LPTSTR ClassName,HMENU hMenuChildId,HWND hParent
)
{
   HWND hWnd
;

   // 親ウィンドウのサイズを取得
   RECT rect
;
   GetWindowRect(hParent, (LPRECT)&rect
);

   hWnd = CreateWindow
(
       ClassName,
       NULL,
       WS_CHILD,
       0,                      //親ウィンドウと同じサイズ
       0,
       rect.right - rect.left,
       rect.bottom - rect.top,
       hParent,        //親ウィンドウのハンドル
       (HMENU)hMenuChildId,    // 子ウィンドウを判別するためのID
       hInst,          //インスタンスハンドル
       NULL
);
   if (!hWnd
)
       return FALSE
;
   ShowWindow(hWnd, nShowCmd
);
   UpdateWindow(hWnd
);
   return hWnd
;
}
// ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp
)
{
   PAINTSTRUCT ps
;
   HDC hdc
;

   TCHAR buf[256
];
   size_t size;        // 文字列のサイズを格納する

   WNDCLASSW wc_A
;
   WNDCLASSW wc_B
;
   static HWND hChildWnd_A
;
   static HWND hChildWnd_B
;

   static HWND hButton
;

   switch (msg
){
       case WM_CREATE
:

           // 子ウィンドウAの生成
           CreateWindowClass(hInst,T("ChildWindow_A"),wc_A
);
           RegisterClassW(&wc_A
);
           hChildWnd_A = CreateChildWindow(hInst,SW_HIDE,
T("ChildWindow_A"),(HMENU)CHILD_ID_A,hWnd);

           // 子ウィンドウBの生成
           CreateWindowClass(hInst,T("ChildWindow_B"),wc_B
);
           RegisterClassW(&wc_B
);
           hChildWnd_B = CreateChildWindow(hInst,SW_HIDE,
T("ChildWindow_B"),(HMENU)CHILD_ID_B,hWnd);

           // プッシュボタンを作成する
           hButton = CreateWindow
(
               T("BUTTON"),                            // ウィンドウクラス名
               
T("OPEN A"),                            // キャプション
               WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,   // スタイル指定
               50, 50,                                  // 座標
               60, 40,                                  // サイズ
               hWnd,                                    // 親ウィンドウのハンドル
               (HMENU)BUTTON_ID_OPEN_A,                 // メニューハンドル
               hInst,          & nbsp;                        // インスタンスハンドル
               NULL                                     // その他の作成データ
               
);

           // プッシュボタンを作成する
           hButton = CreateWindow
(
               T("BUTTON"),                            // ウィンドウクラス名
               
T("OPEN B"),                            // キャプション
               WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,   // スタイル指定
               50, 100,                                 // 座標
               60, 40,                                  // サイズ
               hWnd,                                    // 親ウィンドウのハンドル
               (HMENU)BUTTON_ID_OPEN_B,                 // メニューハンドル
               hInst,                                   // インスタンスハンドル
               NULL                                     // その他の作成データ
               
);

           break
;
       case WM_COMMAND
:
           GetClassName(hWnd, buf, 255
);
           NKDbgPrintfW(T("%s\n"),buf
);
           switch(LOWORD(wp) 
){
           case BUTTON_ID_OPEN_A
:
               // 親ウィンドウの「OPEN_A」ボタン押下時の処理
               // 子ウィンドウAを表示する
               ShowWindow(hChildWnd_A,SW_SHOW
);
               UpdateWindow(hChildWnd_A
);

               return (DefWindowProc(hWnd, msg, wp, lp
));
               break
;
           case BUTTON_ID_OPEN_B
:
               // 親ウィンドウの「OPEN_B」ボタン押下時の処理
               // 子ウィンドウBを表示する
               ShowWindow(hChildWnd_B,SW_SHOW
);
               UpdateWindow(hChildWnd_B
);

               return (DefWindowProc(hWnd, msg, wp, lp
));
               break
;

           case BUTTON_ID
:
               // 子ウィンドウの「CLOSE」ボタン押下字の処理
               if( IsWindowVisible(hChildWnd_A) 
){
                   ShowWindow(hChildWnd_A,SW_HIDE
);
               }else
{
                   ShowWindow(hChildWnd_B,SW_HIDE
);
               
}

               break
;
           default
:
               return (DefWindowProc(hWnd, msg, wp, lp
));
           
}

           break
;
       case WM_PAINT
:
           hdc = BeginPaint(hWnd,&ps);     // 描画処理を開始します。

           SetBkMode(hdc, TRANSPARENT);    // 背景を透過モードにします

           GetClassName(hWnd, buf, 255);   // クラス名を取得

           StringCchLength(buf,255,&size); // 文字列長の取得

       & nbsp;   ExtTextOut
(
               hdc,    // デバイスコンテキストのハンドル
               0,      // 開始位置(基準点)の x 座標
               20,     // 開始位置(基準点)の y 座標
               NULL,   // 長方形領域の使い方のオプション
               NULL,   // 長方形領域の入った構造体へのポインタ
               buf,    // 文字列
               size,   // 文字数
               NULL    // 文字間隔の入った配列
               
);

           EndPaint(hWnd,&ps);             // 描画処理を終了します。

           break
;
       case WM_DESTROY
:
           PostQuitMessage(0
);
           break
;
       default
:
           return (DefWindowProc(hWnd, msg, wp, lp
));
   
}
   return 0
;
}



LRESULT CALLBACK ChildWindowProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp
)
{
   PAINTSTRUCT ps
;
   HWND hButton
;
   HDC hdc
;
   COLORREF bkColor
;
   TCHAR buf[256
];
   size_t size;        // 文字列のサイズを格納する


   switch (msg
){
       case WM_CREATE
:
           // プッシュボタンを作成する
           hButton = CreateWindow
(
               
T("BUTTON"),                            // ウィンドウクラス名
               T("CLOSE"),                             // キャプション
               WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,   // スタイル指定
               100, 200,                                // 座標
               60, 40,                                  // サイズ
               hWnd,                                    // 親ウィンドウのハンドル
               (HMENU)BUTTON_ID,                        // メニューハンドル
               hInst,                                   // インスタンスハンドル
               NULL                                     // その他の作成データ
               
);
           break
;
       case WM_PAINT
:
           hdc = BeginPaint(hWnd,&ps);     // 描画処理を開始します。

           SetBkMode(hdc, TRANSPARENT);    // 背景を透過モードにします

           GetClassName(hWnd, buf, 255);   // クラス名を取得

           StringCchLength(buf,255,&size); // 文字列長の取得

           ExtTextOut
(
               hdc,    // デバイスコンテキストのハンドル
               0,      // 開始位置(基準点)の x 座標
               20,     // 開始位置(基準点)の y 座標
               NULL,   // 長方形領域の使い方のオプション
               NULL,   // 長方形領域の入った構造体へのポインタ
               buf,    // 文字列
               size,   // 文字数
               NULL    // 文字間隔の入った配列
               
);

           EndPaint(hWnd,&ps
);

           break
;
       case WM_COMMAND
:
           GetClassName(hWnd, buf, 255
);
           NKDbgPrintfW(
T("%s\n"),buf);
           switch(LOWORD(wp) 
){
           case BUTTON_ID
:

               // 親ウィンドウへメッセージ伝播。処理は親に任せる。
               SendMessage(GetParent(hWnd),msg,wp,lp
);

               // 自ウィンドウを非表示にする
               //ShowWindow(hWnd,SW_HIDE);
               //UpdateWindow(hWnd);

               break
;
           default
:
               return (DefWindowProc(hWnd, msg, wp, lp
));
           
}

           break
;
       case WM_DESTROY
:
           PostQuitMessage(0
);
           break
;
       default
:
           return (DefWindowProc(hWnd, msg, wp, lp
));
   
}
   return 0
;
}

親ウィンドウにメッセージを送るには、SendMessage関数を使います。
子ウィンドウの「CLOSE」ボタンが押下されると、
子ウィンドウのウィンドウプロシージャChildWindowProcにWM_COMMANDメッセージが通知されます。ここでは

    // 親ウィンドウへメッセージ伝播
    SendMessage(GetParent(hWnd),msg,wp,lp);

としているだけです。

LRESULT SendMessage(
  HWND hWnd,      // 送信先ウィンドウのハンドル
  UINT Msg,       // メッセージ
  WPARAM wParam,  // メッセージの最初のパラメータ
  LPARAM lParam   // メッセージの 2 番目のパラメータ
);

hWnd 送信先ウィンドウハンドルには、親ウィンドウを指定します。Msg,wParam,lParamには、子ウィンドウのウィンドウプロシージャが受け取った値をそのままセットします。

これにより、親ウィンドウで一括してボタン押下時の処理を任せることができます。
親ウィンドウのウィンドウプロシージャでは、子ウィンドウAが表示されていれば、子ウィンドウAを非表示、子ウィンドウBが表示されていれば、子ウィンドウBを非表示にします。

これにより、子ウィンドウの管理を親ウィンドウに任せることができるようになりました。