1.ウインドウを表示する

 

※ 修正版をこちらのページにアップしています。将来的にはこのページは削除される可能性があります。

 


1.「何もしない」ウインドウ

まずは、ウインドウを表示することから始めます。
実現しようとする機能をすべてコーディング(プログラミングすることです)してから、コンパイルしても問題はありませんが、どこかにバグが潜んでいた場合、特定が難しくなります。

このため、通常は、1番簡単なプログラムを作成して、徐々に機能を追加していきます。
つまり、「何もしない」ウインドウが現れるだけのプログラムを作り、そこにメニューやら、ツールバーやらがついて、さらにツールバーのボタンを押したときの動作がついてという感じで、一人前のプログラムにしていきます。

はじめの一歩である「何もしない」ウインドウが現れるだけのプログラムのソースを以下に記します。
そして入力後 「WinMain.cpp」 と名前を付けて保存してください。


/* ========================================================================== */
/*          FILE        WinMain.cpp                                           */
/*          CREATE      2006/10/01                                            */
/*          MODIFY      ----/--/--                                            */
/*          REMARK                                                            */
/*          COPYRIGHT   2000-2007, Latin. All Rights Reserved                 */
/* ========================================================================== */
#include <Windows.h>


/* -------------------------------------------------------------------------- */
/*          CALLBACK     WndProc                                              */
/*          PARAM        HWND       hWnd        : ウィンドウハンドル          */
/*          UINT                    uMsg        : メッセージ                  */
/*          WPARAM                  wParam      : メッセージの追加情報        */
/*          LPARAM                  lParam      : メッセージの追加情報        */
/*          RETURN       LRESULT                :                             */
/*                       メッセージ処理の結果                                 */
/*          REMARK       ウインドウプロシージャ                               */
/* -------------------------------------------------------------------------- */
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  /* 関数本体部 */
  switch (uMsg)
  {
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    default:
      return (DefWindowProc(hWnd, uMsg, wParam, lParam));
  }

  return 0L;
}

/* -------------------------------------------------------------------------- */
/*          WINAPI      WinMain                                               */
/*          PARAM       HINSTANCE   hInstance   : 現在のインスタンス          */
/*                      HINSTANCE   hPrevInst   : 直前のインスタンス          */
/*                      LPSTR       lpCmdLine   : コマンドライン引数          */
/*                      int         nCmdShow    : ウインドウの表示形式        */
/*          RETURN      int                     :                             */
/*                      メッセージの wParam パラメータ                        */
/*          REMARK      Win32 アプリケーションの初期エントリポイント          */
/*                      Windows システムが呼び出す。                          */
/* -------------------------------------------------------------------------- */
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow)
{
  /* 変数宣言部 */
  HWND              hWnd;
  WNDCLASSEX        WndClass;
  MSG               Msg;


  /* 関数本体部 */
  // ウインドウクラス作成
  WndClass.cbSize        = sizeof(WndClass);
  WndClass.style         = CS_HREDRAW | CS_VREDRAW;
  WndClass.lpfnWndProc   = WndProc;
  WndClass.cbClsExtra    = 0;
  WndClass.cbWndExtra    = 0;
  WndClass.hInstance     = hInstance;
  WndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
  WndClass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
  WndClass.lpszMenuName  = NULL;
  WndClass.lpszClassName = "FTPManager";

  // ウィンドウクラス登録
  if (! RegisterClassEx(&WndClass))
  {
    return false;
  }

  // ウィンドウ作成
  hWnd = CreateWindow(WndClass.lpszClassName, // 登録されているクラス名
                      "FTPManager",           // ウィンドウ名
                      WS_OVERLAPPEDWINDOW,    // ウィンドウスタイル
                      CW_USEDEFAULT,          // ウィンドウの横方向の位置
                      CW_USEDEFAULT,          // ウィンドウの縦方向の位置
                      CW_USEDEFAULT,          // ウィンドウの幅
                      CW_USEDEFAULT,          // ウィンドウの高さ
                      NULL,                   // 親ウィンドウまたはオーナーウィンドウのハンドル
                      NULL,                   // メニューハンドルまたは子ウィンドウ ID
                      hInstance,              // アプリケーションインスタンスのハンドル
                      NULL);                  // ウィンドウ作成データ

  if (! hWnd)
  {
    return false;
  }

  // ウィンドウの表示
  ShowWindow(hWnd, nCmdShow);
  UpdateWindow(hWnd);

  // メッセージループ
  while (GetMessage(&Msg, NULL, 0, 0))
  {
    TranslateMessage(&Msg);
    DispatchMessage(&Msg);
  }

  return (int) Msg.wParam;
}

ソースの入力が終わったら、実行してみてください。
次のような画面が表示されると思います。

「何もしない」ウインドウが表示されました。
「何もしない」ウインドウですが、ウィンドウとしての以下のような最低限の機能は備わっています。

コンソールアプリケーションでない限り、ウィンドウを表示させる必要があります。
このとき、上のソースは基本中の基本になりますのでそこで、このソースを雛形として保存しておくと、それを毎回使いまわすことができます。楽をできるところは、楽をしましょう。
また、実際に動いているソースですから、デバッグの手間が省けます。


2.ウインドウの作成

Windows プログラミングにおいて、よく言われることですが、「全てを理解しようとしてもだめです。」
全てを理解しようとすることは、挫折への一番の近道なのです。
プログラマとは、「もやもや」を抱えたまま進んでいくしかないのです。常に「未知との遭遇」です。
かといって何も解説しないのもどうかと思いますので、ウィンドウを表示するための流れを簡単に説明すると、

  1. ウィンドウクラスの作成
  2. ウィンドウクラスの登録
  3. ウィンドウの作成
  4. ウィンドウの表示

の順になります。あれれ、WinMain() のコメントの部分ですね。

ウィンドウクラス とは何なのかというと、ウインドウの設計図です。

のような感じで、ウインドウの設計図を作成するのです。
設計図は、WNDCLASSEX構造体 を使って表しますから

このWNDCLASSEX構造体 の変数を作り、そのメンバ(.よりも右の部分です)に必要な情報を格納します。

次に、ウィンドウクラスの登録ですが、 RegisterClassEx() で行います。

ATOM RegisterClassEx(
  CONST WNDCLASSEX *lpwcx  // クラスデータ
);

WNDCLASSEX * つまり、WNDCLASSEX のポインタを引数にする必要があるので、&(アドレス演算子)を使用して、WndClass を指すポインタを渡しています。
ウィンドウクラスの登録をしないと、そのウィンドウクラスの情報を元に、ウィンドウを作成することができません。
ところで、どこに登録するのでしょうか?それは、システム、この場合は、OS(この場合はWindows)に登録すると思ってください。

ウィンドウの作成は、 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         // ウィンドウ作成データ
);

第1引数に指定するウィンドウクラス名は、RegisterClassEx() で登録した ウィンドウクラス名です。
このウィンドウクラス名から、先に登録された設計図を OS(この場合はWindows)が呼び出してきてウィンドウが作成されるわけです。

CreateWindow() は、戻り値として作成したウィンドウの ウィンドウハンドル とを返します。
このウィンドウに対して何か処理をしようとするときは、するときはこのウィンドウハンドルが必要になります。
ウィンドウハンドルとは、何かしら仰々しい感じを与えますが、ただの数値(ポインタ)です。
ただの数値(ポインタ)なので表示させてみましょう。

今回のウインドウハンドルは、0x001505ec (1377772)を指しているようです。
失敗した場合には、戻り値で NULL を返します。

if (! hWnd) という書き方は、if (hWnd = 0) もしくは if (hWnd = NULL) と同じ意味です。
どうしてそうなるかというと、C言語では、次のような決まりがあります。
    ≠0 : 真
    =0 : 偽
このため if (a == 0) を if (!a) と 記述できるのです。逆に、if (a != 0) は if (a) と記述できます。
この場合、 NULL は、0 なので if (! hWnd) は、if (hWnd == 0) ⇒ if (!hWnd) となるのです。

ウィンドウの表示には、 ShowWindow() を使います。

BOOL ShowWindow(
  HWND hWnd,     // ウィンドウのハンドル
  int nCmdShow   // 表示状態
);

第1引数に指定するウィンドウハンドルは、先ほど作成した、ウインドウハンドルです。
第2引数で指定する値は決まっています。 WinMain 関数が受け取った nCmdShowです。
なぜなら、ShowWindow()の解説の項に、「アプリケーションで初めて ShowWindow 関数を呼び出すときは、nCmdShow パラメータには、WinMain 関数が受け取った nCmdShow パラメータを指定してください。」
と書いてあるからです。
さらに、ウインドウの内容を再描画するため UpdateWindow() も呼び出しています。


3.メッセージループ

さて、このソースの最後のほうにメッセージループなるコメントが書いてあるところがあります。

メッセージループって何でしょうか?
これは、ウィンドウズプログラムでは肝になるところです。
ウインドウに対して何か処理(ボタンを押す・マウスを動かす・文字を入力する…)を行うと、その情報は、一旦OS(この場合はWindows)が受け取ります。そして、システムからメッセージとして、各プログラムに送られ、 メッセージキュー と呼ばれるものに順番に蓄えられます。

そこで、 GetMessage() を使って、このメッセージキューから古いものから順番に取得、MSG構造体へと格納します。

BOOL GetMessage(
  LPMSG lpMsg,         // メッセージ情報
  HWND hWnd,           // ウィンドウのハンドル
  UINT wMsgFilterMin,  // 最初のメッセージ
  UINT wMsgFilterMax   // 最後のメッセージ
);

第2引数にウィンドウのハンドルを指定することで、そのウィンドウに関したメッセージのみ取得します。
そしてMSG構造体へと格納した後、キューからそのメッセージを削除します。

メッセージキューに何か溜まっている間取得し続けないといけないので、永久ループ処理で行います。
じゃー、いつ終わるの?と思われる方がいるでしょうが、GetMessage()の戻り値の項に、「WM_QUIT メッセージを取得した場合、0 が返ります。」とあります。
while (式) は、式が真 ⇒ 式 ≠ 0 の間有効になりますので、0 が帰ってきた時点で、ループ終了です。

なお、GetMessage()の警告の項に、「GetMessage 関数は、0 以外の値、0、-1 のいずれかを返します。
したがって、次のようなコードは避けてください。」として、先ほどのコードが書かれています。
が、-1 を返すのは現実的にほとんど起こりえないので、もっとも記述されている形式にしました。
気になる方は、メッセージループの部分を以下のように書き換えてみてください。


  // メッセージループ
  while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
  {
    if (bRet == -1)
    {
      return bRet;
    }

    TranslateMessage(&Msg);
    DispatchMessage(&Msg);
  }

さて、受け取ったメッセージは、処理する必要があります。
受け取ったメッセージを処理するのが、 WndProc() です。

LRESULT CALLBACK WindowProc(
  HWND hwnd,      // ウィンドウのハンドル
  UINT uMsg,      // メッセージの識別子
  WPARAM wParam,  // メッセージの最初のパラメータ
  LPARAM lParam   // メッセージの 2 番目のパラメータ
);

定義上は、WindowProc() なっていますが、「WindowProc はアプリケーション定義のコールバック関数のプレースホルダであり、実際にこの関数名を使う必要はありません。」とありますので、関数の名前は何でもいいです。大事なのはこれが、システムから呼ばれるということです。
(このソース上、どこにも WndProc() を呼び出す記述がないことに注目してください。)

システムから呼ばれるということは、システムはこのプロシージャを、事前に把握しておかなければなりませんね。
そのため、設計図である WNDCLASSEX構造体lpfnWndProcウィンドウプロシージャ を定義しておきます。

じゃあ、どうやったらシステムから呼び出されるのでしょうか?
ウィンドウプロシージャへのメッセージの送出は、 DispatchMessage() で行います。
送出するとは、システムがプロシージャをコールすることを意味します。

LRESULT DispatchMessage(
  CONST MSG *lpmsg   // メッセージ情報
);

この DispatchMessage() でメッセージが送出されると、ウィンドウプロシージャの引数にそれぞれの値がセットされて呼び出されます。


4.ウインドウプロシージャ

さて、送られてきたメッセージを実際に処理するのは、WndProc() と書きましたが、
ここがウインドウズプログラムの中枢部で、ここの記述によってプログラムの挙動が決まります。

第1引数は、ウインドウハンドルで、作成したウインドウのハンドル(自分自身)です。
第2引数は、メッセージで「WM〜」という形ですが、これまた実際は単なる数値でしかありません。
そういうわけで、またまた表示させて見ましょう。

これは、WM_DESTROY のときの数値です。

ところで、ウインドウプロシージャには、必要・不必要を問わず、ウインドウ上に何か変化があるたびにメッセージが送られてきます。つまり、どのようなメッセージがウインドウプロシージャに送出されるかは未知数です。
そのすべてのパターンを網羅し、そのすべてにあった挙動を記述できるのでしょうか?
… 無理です。

そこで、用意されているのが、 DefWindowProc() です。

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

この関数に、ウインドウプロシージャが受け取ったデータを、そのまま渡してやります。
そうすると、後は万事取り計らってくれます。
これで、自分が処理したいメッセージにのみ専念できます。よかった、よかった。

最後に、メッセージループは、WM_QUIT メッセージを取得するまでと書きましたが、どうやったら、WM_QUIT メッセージが送られてくるんでしょうか?
残念ながら、どんなに待っていても送られて来ることはありません。そこで、送られるように仕向ける必要があります。
WM_QUIT のメッセージを発生させるには、 PostQuitMessage() を使います。

VOID PostQuitMessage(
  int nExitCode   // 終了コード
);

PostQuitMessage() は、WM_DESTROY メッセージの処理中に記述します。

このため、ウインドウプロシージャには、自分が扱わないメッセージを処理する DefWindowProc() と終了時のメッセージ作成を処理するPostQuitMessage() は、最低限記述しておく必要があります。

本日のソース: FTPManager101(VC8).zip   FTPManager101(BCC).zip


※小言

いやー、簡単に説明するつもりが、結構盛りだくさんになりました。
なんとなくしかわからなかった人、もしくは、まったくわからなかった人、気にしないでください。
ましてや、「自分は理解が悪いのであろうか、いやおつむが弱いに違いない。」なんて思ったりしないでください。

こういうものは、ある時フッと飲み込めるものです。ですが、なにもせずに飲み込めるものではありません。
このサイトでは、利便性を考えて、ソースを配布していますが、できれは、自分で入力してみてください。
コピー&ペーストもはじめのうちはしないほうがいいでしょう。

「習うより慣れろ」、何回もプログラムを書くこと以外に上達の近道はありません。


Last Update 2014/01/30 22:30
[Index] [Next]