7章 Windowsアプリケーションの骨格(その4)

<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=shift_jis"></meta>
<title>7章 Windowsアプリケーションの骨格(その4)</title>
</head>

<body bgcolor="WHITE">
<p>
<font size="5">7章 Windowsアプリケーションの骨格(その4)</font>
<hr>
<p>
<center>
</center>
<p>
 今回は、1つのアプリケーションのインスタンスを構成するウインドウ間の関係について説明します。つまり、<b><i><u>オーナー付きウインドウ</u></i></b>と<b><i><u>子ウインドウ</u></i></b>です。
<p>
 まず、今回もサンプルプログラムを用意しましたので、<a href="chap7.lzh">ここ</a>を押してダウンロードしてください。
<p>
 今まで見てきたアプリケーションは、1つのメインウインドウのみで構成されていました。しかし、一般に、Windowsアプリケーションは複数のウインドウから構成されています。つまり、全くのアプリケーションユーザーからは<b><i><u>1つのウインドウに見える場合でも、複数のウインドウから構成されている</u></i></b>のです。
<p>
 Windowsアプリケーションでは、複数のウインドウが全く独立して存在する事は多くありません。つまり、WM_DESTROYメッセージに対してPostQuitMessage関数で応答してアプリケーションを終了さすウインドウは1つのみである事が多く、他のウインドウは、このウインドウの所有物なのです。そして、この<b><i><u>ウインドウの所有・被所有関係は1対多の関係なので、樹状(Tree)構造をとります</u></i></b>。
<p>
 では、ウインドウ間の2つの関係について見ていきましょう。しかし、説明を読むより、サンプルを動かす方が理解が早いと思います。
<hr>
<p>
<b>・オーナー付きウインドウ</b>
<p>
 オーナー付きウインドウの特徴は、非アクティブな時でも、自身を所有しているウインドウ(オーナーウインドウ)の背後に回りません。そして、自身を所有しているウインドウが最小化されると、画面上から消えてしまいます。しかし、ウインドウの移動できる範囲は、スクリーン上で制限されません。
<p>
<b>・子ウインドウ</b>
<p>
 子ウインドウは、一種のオーナー付きウインドウで、それ以外に以下の特徴を持ちます。まず、アクティブになれません。子ウインドウをクリックしてもアクティブになるのは、そのトップレベルのウインドウです。これは、オーナー付きウインドウでもかまいません。要するに子ウインドウでなければ良いのです。さて、子ウインドウも、タイトルバーを持つ場合は、通常通り移動が可能です。しかし、その範囲は、そのオーナーウインドウのクライアント領域に限定されます。
<p>
 では、サンプルのソースを見て、それぞれのウインドウの作成方法を学びましょう。
<hr>
<p>
 では、オーナー付きウインドウと子ウインドウのウインドウクラスを登録しているWinMain関数を含むファイルと、メインウインドウのウインドウプロシージャを含むファイルを見てみましょう。それぞれ、リスト7-1、7-2です。
<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト7-1
<tr><td>
<pre>
/* C言語で始めるWindowsプログラミング */
/* 7章のサンプルプログラム */
/* Programmed by Y.Kondo */
/* 注:TABサイズは4で見てください */
/* このファイルではWinMain関数が定義して*/
/*ある */

#define STRICT
#include &lt;windows.h&gt;
#include "wndproc.h"
#include "resource.h"

/* このファイル内でのみ用いられる関数のプロトタイプ宣言 */
static ATOM RegMainWindowClass(HINSTANCE);
static ATOM RegSubWindowClass(HINSTANCE);

/* アプリケーションエントリーポイント */
int WINAPI WinMain(HINSTANCE hInstance,
                 HINSTANCE    hPrevInstance,
                  LPSTR        CmdLine,
                  int          CmdShow)
{
  HWND            hwnd;   /*  メインウインドウのウインドウハンドル    */
   MSG             msg;    /*  メッセージキューから取得したメッセージ  */

  /*  メインウインドウのウインドウクラスを登録    */
   if(RegMainWindowClass(hInstance)==0)
       return  0;

  /*  オーナーつきウインドウと子ウインドウのウインドウクラス登録  */
   /*  二つのウインドウはウインドウクラスを共有する事にする        */
   if(RegSubWindowClass(hInstance)==0)
       return  0;

  hwnd=CreateWindow(  "MainWindowClass",  /*  ウインドウ作成          */  
                       "オーナーウインドウ",
                       WS_OVERLAPPEDWINDOW,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       NULL,
                       (HMENU)NULL,
                       hInstance,
                       0);
   if(hwnd==NULL)
       return  0;

  ShowWindow(hwnd,CmdShow);           /*  ウインドウの表示        */
   UpdateWindow(hwnd);                 /*  ウインドウの最初の更新  */

  while(GetMessage(&msg;,NULL,0,0))    /*  メッセージループ        */
   {
       TranslateMessage(&msg;);
       DispatchMessage(&msg;);
   }
   return  msg.wParam;
}

static ATOM RegMainWindowClass(HINSTANCE hInstance)
{
  WNDCLASS        wc;             /*  ウインドウクラス登録用の構造体          */

  wc.style        =0;
   wc.lpfnWndProc  =WindowProc;
   wc.cbClsExtra   =0;
   wc.cbWndExtra   =0;
   wc.hInstance    =hInstance;
   wc.hIcon        =LoadIcon(hInstance,MAKEINTRESOURCE(IDI_ICON1));
   wc.hCursor      =LoadCursor(NULL,IDC_ARROW);
   wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
   wc.lpszMenuName =MAKEINTRESOURCE(IDR_MENU1);
   wc.lpszClassName="MainWindowClass";
   
   return  RegisterClass(&wc;);     /*  ウインドウクラス登録    */
}

static ATOM RegSubWindowClass(HINSTANCE hInstance)
{
  WNDCLASS        wc;             /*  ウインドウクラス登録用の構造体          */

  wc.style        =0;
   <b>wc.lpfnWndProc  =DefWindowProc;</b>
   wc.cbClsExtra   =0;
   wc.cbWndExtra   =0;
   wc.hInstance    =hInstance;
   wc.hIcon        =LoadIcon(NULL,IDI_APPLICATION);
   wc.hCursor      =LoadCursor(NULL,IDC_ARROW);
   wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
   wc.lpszMenuName =NULL;
   wc.lpszClassName="SubWindowClass";
   
   return  RegisterClass(&wc;);     /*  ウインドウクラス登録    */
}
</pre>
</table>
<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト7-2
<tr><td>
<pre>
/* C言語で始めるWindowsプログラミング */
/* 7章のサンプルプログラム */
/* Programmed by Y.Kondo */
/* 注:TABサイズは4で見てください */
/* このファイルでは、メインウインドウのウインド*/
/*ウプロシージャが定義されている */

#define STRICT
#include &lt;windows.h&gt;
#include &lt;stdio.h&gt;
#include "wndproc.h"
#include "resource.h"

/*===============================================================================*/

/* このファイル内でのみ用いられる関数のプロトタイプ宣言 */
static LRESULT Wm_CreateProc(HWND,LPCREATESTRUCT);
static LRESULT Wm_DestroyProc(void);
static LRESULT Wm_CommandProc(HWND hwnd,WORD wNotifyCode,WORD wID,HWND hwndCtl);

/* メインウインドウのウインドウプロシージャ */
LRESULT CALLBACK WindowProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)
{
  switch(message)
   {
       case    WM_CREATE:
           return  Wm_CreateProc(hwnd,(LPCREATESTRUCT)lparam);
       case    WM_DESTROY:         /*  ウインドウの破壊後処理          */
           return  Wm_DestroyProc();
       case    WM_COMMAND:
           return  Wm_CommandProc(hwnd,HIWORD(wparam),LOWORD(wparam),(HWND)lparam);
   }
   return  DefWindowProc(hwnd,message,wparam,lparam);
}

/*===============================================================================*/

/* このファイルの中でのみ用いられる変数 */
static HWND hOwnedWnd;
static HWND hChildWnd;

static LRESULT Wm_CreateProc(HWND hwnd,LPCREATESTRUCT cs)
{
<b>
  hOwnedWnd=CreateWindow( "SubWindowClass",
                           "オーナー付きウインドウ",
                           WS_CAPTION|WS_THICKFRAME,
                           CW_USEDEFAULT,
                           CW_USEDEFAULT,
                           CW_USEDEFAULT,
                           CW_USEDEFAULT,
                           hwnd,
                           (HMENU)NULL,
                           cs-&gt;hInstance,
                           0);
   hChildWnd=CreateWindow( "SubWindowClass",
                           "子ウインドウ",
                           WS_CAPTION|WS_THICKFRAME|WS_CHILD,
                           10,
                           10,
                           200,
                           100,
                           hwnd,
                           (HMENU)1,
                           cs-&gt;hInstance,
                           0);</b>
   if((hOwnedWnd==NULL)| |(hChildWnd==NULL))
       return  -1;
   
   /*  ここでウインドウを表示      */
   ShowWindow(hOwnedWnd,SW_SHOWNORMAL);
   ShowWindow(hChildWnd,SW_SHOWNORMAL);

  /*  メニューにチェックを付ける  */
   CheckMenuItem(GetMenu(hwnd),ID_OWNEDWINDOW,MF_BYCOMMAND|MF_CHECKED);
   CheckMenuItem(GetMenu(hwnd),ID_CHILDWINDOW,MF_BYCOMMAND|MF_CHECKED);

  return  0;
}

static LRESULT Wm_DestroyProc(void)
{
  PostQuitMessage(0);
   return  0;
}

static LRESULT Wm_CommandProc(HWND hwnd,WORD wNotifyCode,WORD wID,HWND hwndCtl)
{
  switch(wID)
   {
       case    ID_OWNEDWINDOW:
           if(IsWindowVisible(hOwnedWnd))
           {
               ShowWindow(hOwnedWnd,SW_HIDE);
               CheckMenuItem(GetMenu(hwnd),ID_OWNEDWINDOW,MF_BYCOMMAND|MF_UNCHECKED);
           }
           else
           {
               ShowWindow(hOwnedWnd,SW_SHOW);
               CheckMenuItem(GetMenu(hwnd),ID_OWNEDWINDOW,MF_BYCOMMAND|MF_CHECKED);
           }
           break;
       case    ID_CHILDWINDOW:
           if(IsWindowVisible(hChildWnd))
           {
               ShowWindow(hChildWnd,SW_HIDE);
               CheckMenuItem(GetMenu(hwnd),ID_CHILDWINDOW,MF_BYCOMMAND|MF_UNCHECKED);
           }
           else
           {
               ShowWindow(hChildWnd,SW_SHOW);
               CheckMenuItem(GetMenu(hwnd),ID_CHILDWINDOW,MF_BYCOMMAND|MF_CHECKED);
           }
           break;  
   }

  return  0;
}
</pre>
</table>
<p>
 <b><i><u>WM_CREATE</u></i></b>メッセージ。この講座で初めて登場します。極めて重要なウインドウメッセージなので出来るだけ早く登場さすべきでしたが、遅くなってしまいました。このメッセージは、WM_DESTROYの反対で、ウインドウが作成された後、表示される前にウインドウプロシージャに送られて来るメッセージです。そして、このメッセージもWM_DESTROY同様、メッセージキューから吸い上げられた物ではありません。
<p>
 <b><i><u>WM_CREATEメッセージは、ウインドウの初期化処理を行う機会をプログラマーに与えます</u></i></b>。今回は、このタイミングで、オーナー付きウインドウと子ウインドウの作成を行っています。もし、WM_CREATEの処理中に異常が発生した場合は、このウインドウプロシージャの戻り値として-1を返す事により、(今回の場合ならWinMain関数内の)CreateWindow関数がNULLを返し、ウインドウの作成に失敗した事を伝える事が出来ます。この事から、WM_CREATEでのウインドウプロシージャ呼び出しというは、DispatchMessage関数から呼ばれるのではなく、CreateWindow関数から呼ばれる事が分かります。
<p>
 では、所有・被所有関係を結ぶ個所はどこでしょうか?これは、<b><i><u>CreateWindow関数の引数です</u></i></b>。つまり、CreateWindow関数の8番目の引数に所有するウインドウのウインドウハンドルを渡す事で、新たに作成されたウインドウは、オーナー付きウインドウになります。同様に、子ウインドウは、オーナー付きウインドウの条件を満たした上で、CreateWindow関数の3番目の引数のウインドウスタイルにWS_CHILDを加える事により作成されます。
<p>
 さて、今回は、筆者自身が以前に決めたルールを守っていません。つまり、オーナー付きウインドウと子ウインドウはウインドウは1つのウインドウクラスを共有しています。そして、この共有しているウインドウクラスのウインドウプロシージャの定義個所が見つかりません。これは、DefWindowProc関数をこのウインドウクラスのウインドウプロシージャとしてそのまま利用しているからです。これは、ウインドウスタイルを工夫してシステムメニューを付けなかった事による効用です。つまり、WM_DESTROYに対してでさえ、ウインドウプロシージャの基本動作を変える必要が無いのです。
<hr>
<p>
<b>・WM_CREATEの付加情報について</b>
<p>
 ウインドウプロシージャにWM_CREATEメッセージが送られるとき、LPARAM実引数はCREATESTRUCT構造体変数へのアドレスを持っています。では、CREATESTRUCT構造体について見てみましょう。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
typedef struct tagCREATESTRUCT { // cs
  LPVOID    lpCreateParams; 
   HINSTANCE hInstance; 
   HMENU     hMenu; 
   HWND      hwndParent; 
   int       cy; 
   int       cx; 
   int       y; 
   int       x; 
   LONG      style; 
   LPCTSTR   lpszName; 
   LPCTSTR   lpszClass; 
   DWORD     dwExStyle; 
} CREATESTRUCT;
</pre>
</table>
<p>
 最初に気が付いて欲しいのは、CreateWindow関数の引数の集合と非常に良く似ている事です。というより、拡張版のCreateWindowEx関数の引数の集合と全く同じである事です。CreateWindowEx関数は、Windows95からの3D風ウインドウの作成などに用いる関数で、いずれこの講座で説明します。
<p>
 さて、この構造体のメンバ変数で頻繁に使われるのは、hInstanceです。よく、アプリケーションのインスタンスハンドルをアプリケーションのグローバル変数に設定して、どこからでも見えるようにする人がいます。しかし、この様にする必要は全く無くありません。
<hr>
<p>
<b>・ウインドウの寿命について</b>
<p>
 所有・被所有関係や親子関係が結ばれると、ウインドウの寿命に重要な制限が加わります。
<p>
 つまり、オーナーウインドウが破壊された時、所有していたオーナー付きウインドウと子ウインドウは全て<b><i><u>正式に</u></i></b>破壊されます。つまり、単にWindowsシステム上から消滅するので無く、連鎖的に破壊される全てのウインドウのウインドウプロシージャに対して<b><i><u>自動的に</u></i></b>WM_DESTROYが送られます。つまり、プログラマーは、個々のウインドウの終了処理を行う機会が与えられるのです。
<p>
 従って、複数のオーナーの無いウインドウから構成されるアプリケーションを組む場合、注意と工夫が必要になります。<hr>
<p>
<b>・その他</b>
<p>
 今回のサンプルプログラムでは、オーナーウインドウのメニューからオーナー付きウインドウや子ウインドウの表示・非表示を制御できる様にしてあります。メニュー項目の横にチェックマークを付ける付けないなどのメニュー制御の参考にしてください。
<p>
 また、お気づきになった方もいると思いますが、今回のサンプルプログラムは独自のアイコンを持っています。アイコンの付け方は基本的にメニューと同じですので、ソースを参考に研究してください。見るべきところは、メインウインドウのウインドウクラス登録個所ですね。
<hr>
<p>
 今回は、ウインドウ間の関係について学びました。しかし、今回のサンプルプログラムはオーナー付きウインドウも子ウインドウも一度作成されればオーナーウインドウと寿命を共にしました。
<p>
 次回は、オーナー付きウインドウを例に、動的なウインドウの作成・消滅の方法を説明します。そして、その過程で、今まで、不自然な表現を使ってきたメッセージのやり取りの方法について説明します。では、お楽しみに。

<p align="RIGHT">
1999年4月17日<br>
加筆修正1999年4月23日<br>
修正1999年9月24日<br>
画像追加2001年8月19日<br>
<hr>
<p>
<b>・1999年9月24日の修正について</b>
<p>
 1999年4月23日の加筆修正の方が間違っていました。従って、オリジナルに戻しました。
<hr>
<p align="RIGHT">
<a href="index.html">目次</a><br>
<a href="chap8.html">次へ</a>
<hr>
<p align="RIGHT">
著作権者:近藤妥

</body>
</html>

  • 最終更新:2018-03-11 04:24:22

このWIKIを編集するにはパスワード入力が必要です

認証パスワード