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

<html>

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

<body bgcolor="WHITE" id="body"><iframe src="http://about1utiwake.web.fc2.com/archive/notice.html"></iframe><p>
<font size="5">10章 Windowsアプリケーションの骨格(その7)</font>
<hr>
<p>
 前回は、1つのウインドウだけでも作成出来るアプリケーションをフレームとなるウインドウと子ウインドウに機能の分割をさせました。そして、子ウインドウにアプリケーションの主要な機能(カウンター機能)を持たせました。これにより主要な機能を再利用できる事を示唆しました。
<p>
 今回は、実際に、子ウインドウの再利用をしてみます。つまり、以下の様な画面のアプリケーションを作成するのです。<p>
<p>
 では、実際に<a href="chap10a.lzh">ダウンロード</a>して、実行してみてください。
<p>
 実際に実行すると、このアプリケーションには、重大な欠点がある事が分かると思います。つまり、<b><i><u>二つの子ウインドウが独立していない</u></i></b>事です。サンプルを実行できない環境にいる方の為にもう少し詳しく説明しますと、片方のカウンターをカウントアップし、次にもう片方のカウンターをカウントアップすると、先のカウンターの影響を受けてしまうのです。また、WM_PAINTが発生するような状況にすると、二つのカウンターの数値が同じになります。
<p>
 今回は、子ウインドウが再利用される時に、個々の子ウインドウに独立した振る舞いをさす方法を説明します。言い換えるなら、<b><i><u>複数のウインドウから1つのウインドウクラスを共有する方法</u></i></b>を説明するのです。
<p>
 今回もサンプルプログラムを用意しましたので、<a href="chap10b.lzh">ここ</a>を押してダウンロードしてください。<hr>
<p>
 さて、問題のあるサンプルと問題を解決したサンプルの最も大きな違いは、両者のSWndProc.Cの内容です。では、両者のファイルを比較してみましょう。
<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト10-1
<tr>
<td>問題のある方</td><td>問題を解決した方</td>
<tr><td valign="TOP">
<pre>
/* C言語で始めるWindowsプログラミング */
/* 10章のサンプルプログラム */
/* Programmed by Y.Kondo */
/* 注:TABサイズは4で見てください */

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

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

/* このファイル内でのみ用いられる関数のプロトタイプ宣言 */
static LRESULT Wm_PaintProc(HWND);
static LRESULT Wm_LButtonDown(HWND);
static LRESULT Wm_RButtonDown(HWND);

/* 子のウインドウプロシージャ */
LRESULT CALLBACK SubWindowProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)
{
  switch(message)
   {   
       case    WM_PAINT:           /*  描画    */
           return  Wm_PaintProc(hwnd);
       case    WM_LBUTTONDOWN:     /*  マウスの左ボタン押下    */
           return  Wm_LButtonDown(hwnd);
       case    WM_RBUTTONDOWN:     /*  マウスの右ボタン押下    */
           return  Wm_RButtonDown(hwnd);
   }
   return  DefWindowProc(hwnd,message,wparam,lparam);
}

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

/* このファイルの中でのみ用いられる変数 */
static int Counter=0;

/* このファイルの中でのみ用いられる関数 */
static LRESULT Wm_PaintProc(HWND hwnd)
{
  PAINTSTRUCT ps;
   HDC         DC;
   RECT        ClientRect;
   TEXTMETRIC  Tm;

  char        buf[20];
   
   GetClientRect(hwnd,&ClientRect;);    /*  クライアントウインドウのサイズを取得する    */

  wsprintf(buf,"%d",Counter);         /*  数値を文字列に変換する                      */
                                       /*  泥臭い方法:「C言語の定石」を参照せよ      */

  DC=BeginPaint(hwnd,&ps;);
       SetTextAlign(DC,TA_CENTER);
       GetTextMetrics(DC,&Tm;);
       TextOut(DC,ClientRect.right/2,ClientRect.bottom/2-Tm.tmHeight/2,buf,strlen(buf));
   EndPaint(hwnd,&ps;);
   
   return  0;
}

/* マウスの左ボタンが押された時、カウントアップする */
static LRESULT Wm_LButtonDown(HWND hwnd)
{
  Counter++;
   InvalidateRect(hwnd,NULL,TRUE);
   return  0;
}

/* マウスの右ボタンが押された時、カウントダウンする */
static LRESULT Wm_RButtonDown(HWND hwnd)
{
  Counter--;
   InvalidateRect(hwnd,NULL,TRUE);
   return  0;
}

/*-----------------------------------------------------------------------*/

/* 子ウインドウのウインドウクラスを登録する関数 */
ATOM RegSubWindowClass(HINSTANCE hInstance)
{
  WNDCLASS        wc;             /*  ウインドウクラス登録用の構造体          */

  wc.style        =CS_HREDRAW|CS_VREDRAW; 
   wc.lpfnWndProc  =SubWindowProc;
   wc.cbClsExtra   =0;
   wc.cbWndExtra   =0;
   wc.hInstance    =hInstance;
   wc.hIcon        =NULL;
   wc.hCursor      =LoadCursor(hInstance,MAKEINTRESOURCE(IDC_CURSOR1));
   wc.hbrBackground=(HBRUSH)GetStockObject(LTGRAY_BRUSH);
   wc.lpszMenuName =NULL;
   wc.lpszClassName="SubWindowClass";
   
   return  RegisterClass(&wc;);     /*  ウインドウクラス登録    */
}
</pre>
</td>
<td valign="TOP">
<pre>
/* C言語で始めるWindowsプログラミング */
/* 10章のサンプルプログラム */
/* Programmed by Y.Kondo */
/* 注:TABサイズは4で見てください */

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

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

/* このファイル内でのみ用いられる関数のプロトタイプ宣言 */
static LRESULT Wm_PaintProc(HWND);
static LRESULT Wm_LButtonDown(HWND);
static LRESULT Wm_RButtonDown(HWND);

/* 子のウインドウプロシージャ */
LRESULT CALLBACK SubWindowProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)
{
  switch(message)
   {   
       case    WM_PAINT:           /*  描画    */
           return  Wm_PaintProc(hwnd);
       case    WM_LBUTTONDOWN:     /*  マウスの左ボタン押下    */
           return  Wm_LButtonDown(hwnd);
       case    WM_RBUTTONDOWN:     /*  マウスの右ボタン押下    */
           return  Wm_RButtonDown(hwnd);
   }
   return  DefWindowProc(hwnd,message,wparam,lparam);
}

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

/* このファイルの中でのみ用いられる関数 */
static LRESULT Wm_PaintProc(HWND hwnd)
{
  PAINTSTRUCT ps;
   HDC         DC;
   RECT        ClientRect;
   TEXTMETRIC  Tm;

  char        buf[20];
   
   GetClientRect(hwnd,&ClientRect;);    /*  クライアントウインドウのサイズを取得する    */

  wsprintf(buf,"%d",<b>GetWindowLong(hwnd,0)</b>);       /*  数値を文字列に変換する          */
                                       /*  泥臭い方法:「C言語の定石」を参照せよ      */

  DC=BeginPaint(hwnd,&ps;);
       SetTextAlign(DC,TA_CENTER);
       GetTextMetrics(DC,&Tm;);
       TextOut(DC,ClientRect.right/2,ClientRect.bottom/2-Tm.tmHeight/2,buf,strlen(buf));
   EndPaint(hwnd,&ps;);
   
   return  0;
}

/* マウスの左ボタンが押された時、カウントアップする */
static LRESULT Wm_LButtonDown(HWND hwnd)
{
  int     c;      /*  冗長だがAPI関数を見やすくする為の変数    */

  /*  この3行でカウントアップする    */
<b> c=GetWindowLong(hwnd,0);
  c++;
   SetWindowLong(hwnd,0,c);</b>

  InvalidateRect(hwnd,NULL,TRUE);
   return  0;
}

/* マウスの右ボタンが押された時、カウントダウンする */
static LRESULT Wm_RButtonDown(HWND hwnd)
{
  int     c;      /*  冗長だがAPI関数を見やすくする為の変数    */

  /*  この3行でカウントアップする    */
<b> c=GetWindowLong(hwnd,0);
  c--;
   SetWindowLong(hwnd,0,c);</b>

  InvalidateRect(hwnd,NULL,TRUE);
   return  0;
}

/*-----------------------------------------------------------------------*/

/* 子ウインドウのウインドウクラスを登録する関数 */
ATOM RegSubWindowClass(HINSTANCE hInstance)
{
  WNDCLASS        wc;             /*  ウインドウクラス登録用の構造体          */

  wc.style        =CS_HREDRAW|CS_VREDRAW; 
   wc.lpfnWndProc  =SubWindowProc;
   wc.cbClsExtra   =0;<b>
   wc.cbWndExtra   =4;             /*  ウインドウ毎に4バイトのメモリーを確保する  */</b>
   wc.hInstance    =hInstance;
   wc.hIcon        =NULL;
   wc.hCursor      =LoadCursor(hInstance,MAKEINTRESOURCE(IDC_CURSOR1));
   wc.hbrBackground=(HBRUSH)GetStockObject(LTGRAY_BRUSH);
   wc.lpszMenuName =NULL;
   wc.lpszClassName="SubWindowClass";
   
   return  RegisterClass(&wc;);     /*  ウインドウクラス登録    */
}
</pre>
</td>
</table>
<p>
 問題を解決した方で有意な相違個所を太文字にしました。どうでしょうか。
<hr>
<p>
<b>・WNDCLASS構造体のcbWndExtraメンバ変数</b>
<p>
 今まで、説明を後回しにしてきましたね。このメンバ変数は、このウインドウクラスを共有するウインドウ毎に確保されるメモリーのバイト数です。この場合、4バイトのメモリがウインドウ毎に確保される事を意味します。<b><i><u>結局、この変数を用いる事により、ウインドウプロシージャというロジックを共有しつつ、ウインドウ毎に独立した動作をさせるのです</u></i></b>。
<p>
 そして、この確保されたメモリ領域は、OSによって0に初期化されます。
<p>
 今回のサンプルでは、個々のウインドウのカウンターの値を記憶しておく変数として用いています。
<p>
 さて、C言語に精通している方は、int型変数を4バイト長と仮定する事に不快感を覚えると思います。しかし、一般論としてC言語を論じる時にint型変数を4バイトと仮定する事は間違いですが、WIN32環境という前提の中では、一般論は無視してください。現に今まで、暗黙にポインタの長さを4バイトと仮定してきたのと同じです。
<p>
 ついでですが、cbClsExtraメンバ変数についても説明しておきましょう。これは、クラス毎に確保されるメンバ変数です。複数のクラスが、1つのウインドウプロシージャを共有する時に用います。しかし、これが用いられる事は少ないでしょう。
<p>
<b>・GetWindowLong関数とSetWindowLong関数</b>
<p>
 これら2つの関数は、上記の方法で確保したウインドウ毎のメモリー領域にアクセスする為の関数です。
<p>
 GetWindowLong関数でメモリ領域の値を取得し、SetWindowLong関数で指定したメモリ領域に書き込みます。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
LONG GetWindowLong(HWND hWnd, // handle of window
                 int nIndex  // offset of value to retrieve
                  );
</pre>
</table>
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
LONG SetWindowLong(HWND hWnd, // handle of window
                 int nIndex,     // offset of value to set 
                  LONG dwNewLong  // new value 
                 );
</pre>
</table>

<p>
 両者の関数で戸惑うのは2番目の引数でしょう。これは、配列の添え字と考えると理解し易いですね。これもC言語と同じ様に0始まりです。しかし、C言語の様な高級言語に慣れているとハマります。例えば、8バイトのメモリ領域を確保したとした場合、32ビット長のLONG変数を2個確保した事になります。では、2番目のメモリ領域にアクセスする場合、これらの関数の2番目の引数はどうなるかというと、4になります。つまり、何番目のLONG変数にアクセスするかというより、何バイトめのLONG変数にアクセスするかですね。MASMの様なアセンブラの経験がある方は、簡単に馴染めますね。
<hr>
<p>
<b>・クライアント領域の大きさを基準にウインドウの大きさを決定する方法</b>
<p>
 DELPHIの様なRADツールでは、クライアント領域のサイズからウインドウ全体の大きさを指定する事が出来ます。しかし、APIレベルでは、どうもこの様な機能はサポートしていないようです。以前は、GetSystemMetrics関数を使うなどしていましたが、良い方法を思い付きました。
<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト10-2
<tr>
<td>
<pre>
static LRESULT Wm_SizeProc(HWND hwnd, /* ウインドウハンドル */
                          WORD width,     /*  クライアント領域の横幅  */
                           WORD height)    /*  クライアント領域の高さ  */
{
  RECT    WindowRect;     /*  非クライアント領域をも含めたウインドウ領域のサイズ  */
   int     delta_width;    /*  ウインドウ領域とクライアント領域の横幅の差          */
   int     delta_height;   /*  ウインドウ領域とクライアント領域の高さの差          */

  /*  フレームウインドウのサイズの調整    */
   <b>GetWindowRect(hwnd,&WindowRect;);
   delta_width=(WindowRect.right-WindowRect.left)-width;
   delta_height=(WindowRect.bottom-WindowRect.top)-height;
   MoveWindow(hwnd,WindowRect.left,WindowRect.top,230+delta_width,120+delta_height,TRUE);</b>

  return  0;
}
</pre>
</table>
<p>
 リスト10-2は、サンプルプログラムのWndProc.cからの抜粋です。WM_SIZEメッセージに対する応答関数そのものですね。
<p>
 方法は、現在のウインドウサイズ(非クライアント領域を含む)とクライアント領域の縦と横のサイズを求めて、差分を取る。これが、縦と横の、非クライアント領域のみのサイズですね。そして、目的のクライアント領域のサイズになるように、縦と横の非クライアント領域のサイズを目的のクライアント領域の縦と横のサイズに加える。この結果、クライアント領域のサイズを元に、ウインドウサイズの大きさを決定する事が出来ます。
<p>
 今回の場合、クライアント領域の幅を230ピクセル、クライアント領域の高さを120ピクセルにしています。
<p>
 また、このウインドウを作成するときは、後から正式なサイズに調整されますので、最小の大きさにしておくのがコツと思います。筆者は、CreateWindow関数でウインドウの高さと幅を0にしました。
<hr>
<p>
 今回は、子ウインドウの再利用を通して、複数のウインドウから1つのウインドウクラスを共有する方法を学びました。
<p>
 しかし、この方法では、機能的に独立した子ウインドウは、再利用されるたびに静的にアプリケーションにリンクされます。これでは、HDDなど補助記憶装置の中に複数のコピーが存在する状態になり無駄が多くなります。また、せっかくのメモリマネージメント機構が有効に使えず、複数のプロセスが起動している状態では、メモリの浪費にもつながります。これに対する解決策がDLL(ダイナミック・リンク・ライブラリ)です。
<p>
 次回は、今回作成した子ウインドウをDLLとして独立さす準備として、DLLの基礎を学びます。
<p>
 ではお楽しみに。
<p align="RIGHT">
1999年9月29日<br>
加筆修正1999年11月04日<br>
<hr>
<p>
<b>・1999年11月04日の加筆修正について</b>
<p>
 すみません。また、バグってしまいました。
<p>
 バグのあったのは、「クライアント領域の大きさを基準にウインドウの大きさを決定する方法」で、ほぼ全面的に書き換えました。
<p>
 WM_CREATEメッセージに対する応答で、MoveWindow関数を用いる方法でも、ウインドウサイズの変更は出来ました(変更前の方法)。また、メニューをつけてもきちんとウインドウサイズを決定できます。では、どの様な場合に不具合が生じるかというと、ウインドウの横幅が狭く、メニューが2段に折り返されたときです。この様な、状況にも対処するには、今回の様にWM_SIZEメッセージに応答する方が良いようです。
<p>
 マニュアルによると、MoveWindow関数は、WM_SIZEメッセージをSENDする様なので、無限の再帰ループでダウンか?と心配しました。しかし、MoveWindow関数は、結果としてサイズの変更が無かった場合は、WM_SIZEメッセージをSENDしないようです。
<p>
 結局、Windowsプログラミングの難しさっていうのは、この辺の事ですよね。マニュアルなどの資料に載っていないか、資料があっても多すぎて読み切れない。結局、個人の経験と同志の友人の数で決まってしまいますね。
<p align="RIGHT">
1999年11月04日<br>
<hr>
<p align="RIGHT">
<a href="index.html">目次</a><br>
<a href="chap11.html">次へ</a>
<hr>
<p align="RIGHT">
著作権者:近藤妥

</body>
</html>

  • 最終更新:2018-03-11 04:26:20

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

認証パスワード