3.標準コントロール(1) 〜 コンボボックス(1) 〜

 

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

 


1.コンボボックス

ウインドウとメニューができましたので、なんかそれらしい形になってきましたが、クライアント領域にはまだ何もありませんし、ただ終了する機能しかありません。
今回からしばらくは、これに、エクスプローラのような機能を持たせてドライブとファイルの表示を行っていこうと思います。

ウィンドウズでは、みんながよく使う、ボタンやコンボボックス、ラジオボタンなど部品があらかじめ用意されています。
これを 標準コントロール といいます。
それ以外に、わりとよく使うけど標準ではない コモンコントロール というものがあります。
また、完全に自作の カスタムコントロール というものもあります。
漢字でいうと、第1・2水準漢字が標準コントロール、第3・4水準漢字がコモンコントロール、カスタムコントロールは、外字といったところでしょうか。

では、標準コントロールは、どうやって使用するのでしょうか?
ウインドウズでは、コントロールは、ウインドウのひとつとして扱われます。
つまり、ウインドウとコントロールは同じ部品として扱われるということです。

ウインドウと同じということは、 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() で登録した ウィンドウクラス名 ではなく、定義済みの システムクラス名 になります。
今回はコンボボックスを作成したいので、システムクラスには、"COMBOBOX" を指定します。

コントロールを作成する上で、ひとつ重要なことがあります。
それは、ウインドウの子供としてコントロールを作成する必要があるということです。
このため、第3引数の ウィンドウスタイル には必ず、WS_CHILD を指定しなければなりません。
また、第8引数には、親であるウインドウの ウインドウハンドル を指定します。
さらに、第10引数には、親であるウインドウの インスタンスハンドル を指定します。

ウインドウハンドル は、WndProc() の第1引数にありますが、親であるウインドウの インスタンスハンドル はどのように取得したらいいのでしょうか?
インスタンスハンドル を取得するには、 GetWindowLong() を使用します。

LONG GetWindowLong(
  HWND hWnd,  // ウィンドウのハンドル
  int nIndex  // 取得する値のオフセット
);

第1引数には、インスタンスを取得する ウインドウハンドル
第2引数には、インスタンスハンドル を取得するために GWL_HINSTANCE を指定します。

作成するタイミングはどうでしょう?親ウィンドウが作られた時に作るのがよさそうです。
ですから、ウインドウプロシージャの中で、WM_CREATE メッセージを処理するようにします。

WM_CREATE
  0    = wParam;                   // 未使用
  lpcs = (LPCREATESTRUCT) lParam;  // 作成データを格納する構造体

今回は、wParam、lParam ともに使用しません。

くどくど説明するより、ソースを見たほうが早いでしょう。


LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  /* 変数宣言部 */
  HINSTANCE         hInstance;


  /* 関数本体部 */
  switch (uMsg)
  {
    case WM_COMMAND:
      switch (LOWORD(wParam))
      {
        case IDM_EXIT:
          SendMessage(hWnd, WM_CLOSE, 0, 0L);
          break;
        default:
          return (DefWindowProc(hWnd, uMsg, wParam, lParam));
      }
      break;
    case WM_CREATE:
      hInstance = (HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE);
      CreateWindow("COMBOBOX",
                   NULL,
                   WS_CHILD | WS_VISIBLE | CBS_SORT | CBS_DROPDOWN | CBS_AUTOHSCROLL,
                   10,
                   5,
                   400,
                   100,
                   hWnd,
                   0,
                   hInstance,
                   NULL);
      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    default:
      return (DefWindowProc(hWnd, uMsg, wParam, lParam));
  }

  return 0L;
}

そうして、実行してみてください。次のようなコンボボックスがついた画面が表示されると思います。


2.カレントディレクトリの取得

さて、コンボボックスはできましたが、何も表示されていません。
そこで、このコンボボックスに カレントディレクトリ を表示させようと思います。
カレントディレクトリとは、ユーザが現在作業を行なっているディレクトリのことで、通常は、実行しているアプリケーションがあるディレクトリ(フォルダ)がカレントディレクトリになります。
カレントディレクトリを取得するには、 GetCurrentDirectory() を使用します。

DWORD GetCurrentDirectory(
  DWORD nBufferLength,  // ディレクトリバッファのサイズ
  LPTSTR lpBuffer       // ディレクトリバッファ
);

第1引数のディレクトリバッファのサイズには、カレントディレクトリのフルパス名の文字数を指定すればいいのですが、そもそも、カレントディレクトリ がわからないのに、文字数もへったくりもありません。
そこで、カレントディレクトリ がとりうる最大の文字数を指定しておきます。
通常は、MAX_PATH という定数を使用しますが、終端のNULL文字分も含めなければなりませんので + 1 しています。

関数が成功した場合の戻り値は、NULL 文字を除いたバッファに格納した文字数ですが、バッファのサイズが足りなかった場合は NULL 文字を含む必要な文字数が返ります。
また、関数が失敗した場合は、0 が返ります。

ウインドウプロシージャを以下のように修正します。


LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  /* 変数宣言部 */
  HINSTANCE         hInstance;
  HWND              hCtlCombo;
  char              szCurDir[MAX_PATH + 1];


  /* 関数本体部 */
  switch (uMsg)
  {
    case WM_COMMAND:
      switch (LOWORD(wParam))
      {
        case IDM_EXIT:
          SendMessage(hWnd, WM_CLOSE, 0, 0L);
          break;
        default:
          return (DefWindowProc(hWnd, uMsg, wParam, lParam));
      }
      break;
    case WM_CREATE:
      hInstance = (HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE);
      hCtlCombo = CreateWindow("COMBOBOX",
                               NULL,
                               WS_CHILD | WS_VISIBLE | CBS_SORT | CBS_DROPDOWN | CBS_AUTOHSCROLL,
                               10,
                               5,
                               450,
                               100,
                               hWnd,
                               0,
                               hInstance,
                               NULL);

      GetCurrentDirectory(MAX_PATH + 1, szCurDir);
      SetWindowText(hCtlCombo, szCurDir);

      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    default:
      return (DefWindowProc(hWnd, uMsg, wParam, lParam));
  }

  return 0L;
}

GetCurrentDirectory の第2引数には、charの配列を指定しています。変数宣言において [ ] がつくと配列になります。
配列を引数として渡す場合は、無条件に配列の先頭要素へのポインタになりますので、LPTSTR と型は一致します。

そうして、実行してみてください。
コンボボックスにカレントディレクトリが表示されました。
このカレントディレクトリは、私の環境ではこうなりますが、プロジェクトがあるフォルダによって変わります。

テキストをコンボボックスに表示するには、 SetWindowText() を使用します。

BOOL SetWindowText(
  HWND hWnd,         // ウィンドウまたはコントロールのハンドル
  LPCTSTR lpString   // タイトルまたはテキスト
);

第1引数には、コントロールのハンドル、第2引数には、テキスト文字列を指定します。
ところで、第1引数のコントロールのハンドルは、どうやったらわかるのでしょうか。
実は、CreateWindow の戻り値が作成されたコントロールのハンドルになります。

また、コンボボックスの場合に ComboBox_SetText() マクロ でもテキスト表示が可能です。
この場合も、第1引数と第2引数は、SetWindowText() と変わりませんが、windowsx.h をインクルードする必要があります。
どちらでもかまいませんが、私は ComboBox_SetText() マクロ の方をよく使用します。


3.文字がデカくない?

ところで、カレントディレクトリが表示されているウインドウで何か気づくことはありませんか?
そうです! コンボボックスの文字がやたらにデカイ気がしませんか?
エクスプローラのアドレスバーのコンボボックスの文字の大きさはこんな感じなのに。

明らかにデカイですね…。
そこで、コンボボックスの文字の大きさを変更してみたいと思います。
ユーザーインターフェイス用のデフォルトフォントを設定するにはどうすればよいのでしょうか?

ウィンドウに、フォント変更のメッセージを送出してやる必要があります。
フォント変更のメッセージが WM_SETFONT メッセージ で、送出するには、 SendMessage() を使います。

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

SendMessage() は以前に出てきましたが、今回は、コンボボックスのハンドルが、送信先ウィンドウになります。
第2引数はメッセージですので、WM_SETFONT を指定します。

WM_SETFONT
  wParam = (WPARAM) hfont          // フォントのハンドル
  lParam = MAKELPARAM(fRedraw, 0)  // 再描画フラグ

第3引数には、設定するフォントのハンドル、第4引数には、再描画フラグをそれぞれ指定します。

設定するフォントのハンドルは、ユーザーインターフェイス用のデフォルトフォントです。
つまり、DEFAULT_GUI_FONT を指定します。
一時期、このフォントは、Win95までのフォントだとヘルプに書いてあったようですが、どうやらガセのようです。
DEFAULT_GUI_FONT はあくまでフォントを表す数値ですから、このハンドル(ポインタ)を取得する必要がありますね。

定義済み(ストック)のペン、ブラシ、フォント、パレットのハンドルを取得するには、 GetStockObject() を使用します。

HGDIOBJ GetStockObject(
  int fnObject   // ストックオブジェクトのタイプ
);

そして、ウインドウプロシージャを以下のように修正します。


LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  /* 変数宣言部 */
  HINSTANCE         hInstance;
  HWND              hCtlCombo;
  char              szCurDir[MAX_PATH + 1];


  /* 関数本体部 */
  switch (uMsg)
  {
    case WM_COMMAND:
      switch (LOWORD(wParam))
      {
        case IDM_EXIT:
          SendMessage(hWnd, WM_CLOSE, 0, 0L);
          break;
        default:
          return (DefWindowProc(hWnd, uMsg, wParam, lParam));
      }
      break;
    case WM_CREATE:
      hInstance = (HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE);
      hCtlCombo = CreateWindow("COMBOBOX",
                               NULL,
                               WS_CHILD | WS_VISIBLE | CBS_SORT | CBS_DROPDOWN | CBS_AUTOHSCROLL,
                               10,
                               5,
                               450,
                               30,
                               hWnd,
                               0,
                               hInstance,
                               NULL);
      SendMessage(hCtlCombo, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT), MAKELPARAM(true , 0));

      GetCurrentDirectory(MAX_PATH + 1, szCurDir);
      SetWindowText(hCtlCombo, szCurDir);

      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    default:
      return (DefWindowProc(hWnd, uMsg, wParam, lParam));
  }

  return 0L;
}

そうして、実行してみてください。
コンボボックスにカレントディレクトリがエクスプローラの文字と同じように表示されました。

最後に今回の「WinMain.cpp」の全文を載せておきます。


/* ========================================================================== */
/*          FILE        WinMain.cpp                                           */
/*          CREATE      2006/10/01                                            */
/*          MODIFY      2006/11/11                                            */
/*          REMARK                                                            */
/*          COPYRIGHT   2000-2007, Latin. All Rights Reserved                 */
/* ========================================================================== */
#include <Windows.h>
#include "Resource.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)
{
  /* 変数宣言部 */
  HINSTANCE         hInstance;
  HWND              hCtlCombo;
  char              szCurDir[MAX_PATH +1];


  /* 関数本体部 */
  switch (uMsg)
  {
    case WM_COMMAND:
      switch (LOWORD(wParam))
      {
        case IDM_EXIT:
          SendMessage(hWnd, WM_CLOSE, 0, 0L);
          break;
        default:
          return (DefWindowProc(hWnd, uMsg, wParam, lParam));
      }
      break;
    case WM_CREATE:
      hInstance = (HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE);
      hCtlCombo = CreateWindow("COMBOBOX",
                               NULL,
                               WS_CHILD | WS_VISIBLE | CBS_SORT | CBS_DROPDOWN | CBS_AUTOHSCROLL,
                               10,
                               5,
                               450,
                               100,
                               hWnd,
                               0,
                               hInstance,
                               NULL);
      SendMessage(hCtlCombo, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT), MAKELPARAM(true , 0));

      GetCurrentDirectory(MAX_PATH + 1, szCurDir);
      SetWindowText(hCtlCombo, szCurDir);

      break;
    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  = MAKEINTRESOURCE(IDM_MENUMAIN);
  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;
}

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


※小言

ところで、初心者の方には、WM_SETFONT を使えばいいっていうのがわからないんだよな〜と思う方がいらっしゃると思います。
実際、私も始めは試行錯誤しながら調べました。
幸いインターネットというツールがあるので調べることはできますが、どうやって調べたらよいのでしょうか?

Google で調べるときにも、「コンボボックス フォント」で調べるとあまりほしい答えが出てきませんが、「コントロール フォント」で調べると、WM_SETFONT の記述がすぐ出てきます。
ここら辺は、勘と経験なのでしょうか。
すぐには、答えに結びつかないかもしれませんが、調べるということは大事ですので面倒くさがらずに、ググッてください。


Last Update 2014/01/31 21:23
[Index] [Prev] [Next]