コモンコントロールの基礎

Windowsの提供する機能の中で非常に便利なものの1つに、ツールバーやステータスバー、ツリービューなどといったものがあります。これらは、総称してコモンコントロールと呼ばれています。今回は、コモンコントロールについてを簡単に紹介しておきます。また、今後コモンコントロールを使っていく上で、必要となることも、あわせて述べていくことにしましょう。

コモンコントロール

コモンコントロールは、Windowsのコモンコントロールライブラリによって提供されるウィンドウクラスのセットです。簡単に言ってしまえば、Windowsより提供されている、独自の機能を持ったウィンドウといったところでしょう。

以下にコモンコントロールの例を示します。

これらのコントロールは、コモンコントロールライブラリと呼ばれるものによって提供されていると述べましたが、このライブラリの正体はcomctl32.dllです。このDLLによって提供されている関数を呼び出していくことで、コモンコントロールを作成することができるのです。

コモンコントロールライブラリは、コモンコントロール以外にも、それらをサポートするための機能として、イメージリストと呼ばれる機能も提供しています。イメージリストは、主にアイコンなどのイメージを使用するコモンコントロールで使われます。

コモンコントロールのバージョン依存性

コモンコントロールを提供しているcomctl32.dllのバージョンアップに伴い、いろいろな機能が追加されてきています。こういった、あとから追加された機能を使用する場合、古いバージョンのDLLがインストールされているマシンでは、そのソフトウェアが正常に動かなくなってしまう可能性があります。新しい機能を使用したソフトウェアを公開する場合には、その動作環境をはっきりと明記しておきましょう。

comctl32.dllのバージョンとその動作環境の対応は、以下のようになります。

バージョン 動作環境
4.00 Windows 95/NT 4.0 がインストールされた環境
4.70 Internet Explorer 3.x がインストールされた環境
4.71 Internet Explorer 4.0 がインストールされた環境
4.72 Internet Explorer 4.01 / Windows 98 がインストールされた環境
5.80 Internet Explorer 5 がインストールされた環境
5.81 Windows 2000 がインストールされた環境

コモンコントロール作成の準備

コモンコントロールを使うためには、まずcomctl32.dllを初期化するために、InitCommonControls関数を呼び出さなければなりません。

BOOL InitCommonControls(VOID);

この関数は、コモンコントロールを使いますよー、ということをcomctl32.dllに伝えて、準備をするためのものです。この関数は、複数のコモンコントロールを使う場合でも、プログラム中で一度だけ呼び出せばOKです。


実は、コモンコントロール初期化用の関数にはInitCommonControls関数の他にもう1つあります。それは、comctl32.dllのバージョンアップに伴って新しく追加されたInitCommonControlsEx関数です。

BOOL InitCommonControlsEx(
    LPINITCOMMONCONTROLSEX  pInitCtrls
);

この関数の引数(pInitCtrlsパラメータ)には、INITCOMMONCONTROLSEX構造体のアドレスを指定します。この構造体は以下のように定義されているものです。

typedef struct tagINITCOMMONCONTROLSEX {
    DWORD  dwSize;
    DWORD  dwICC;
} INITCOMMONCONTROLSEX, *LPINITCOMMONCONTROLSEX;

この構造体のdwSizeメンバには、構造体サイズである8を、またdwICCメンバには、使用するコントロールの種類を表す値を格納しておきます。

InitCommonControls関数の代わりにInitCommonControlsEx関数を使うことで、すべてのコモンコントロールのウィンドウクラスをロードするのではなく、必要なもののみをロードすることができるので、使用されるメモリの節約や初期化の時間短縮につながるというわけです。

ただし、先ほども述べた通り、InitCommonControlsEx関数は新しく追加された関数であるため、バージョン依存性があるのです。この関数はcomctl32.dllのバージョン4.70以降で使用することができるもので、このDLLはInternet Explorer 3.0以降がインストールされている環境に存在します。また、Windows 98/MeやWindows 2000/XPにはすでにインストールされています。逆に、それより前のバージョンのDLLを持つマシンでは、この関数を使うことはできません。したがって、すべての環境で動作するようにするにはInitCommonControls関数を使うことになります。

コモンコントロールの作成

先ほども言いましたが、コモンコントロールの実体は、Windowsから提供されているウィンドウです。したがって、すべてのコモンコントロールは、ウィンドウ作成用のAPI関数であるCreateWindowEx関数を使って作成することができます。

HWND CreateWindowExA(
    DWORD  dwExStyle,     // 拡張ウィンドウスタイル
    PCTSTR pszClassName,  // ウィンドウクラス名
    PCTSTR pszWindowName, // ウィンドウ名
    DWORD  dwStyle,       // ウィンドウスタイル
    int    x,             // x座標
    int    y,             // y座標
    int    nWidth,        // xサイズ
    int    nHeight,       // yサイズ
    HWND   hWndParent,    // 親ウィンドウのハンドル
    HMENU  hMenu,         // メニューハンドルまたは子ウィンドウID
    HINSTANCE hInstance,  // インスタンスハンドル
    PVOID  pParam         // ウィンドウ作成データ
);

ウィンドウスタイルとかウィンドウクラス名とか、聞き慣れないものがいくつかありますね。とりあえず、それらについて説明しましょう。

まずは第2引数(pszClassNameパラメータ)のウィンドウクラス名からです。ウィンドウクラスとは、ウィンドウに関するデータと、ウィンドウの動作を決定付ける関数(ウィンドウプロシージャ)を定義した情報と考えることができます。これから作成されるウィンドウがどのようなスタイルを持っていて、ウィンドウメッセージにどのように反応するウィンドウプロシージャを持っているのか、といった情報をウィンドウクラスという形で保持してあるのです。ウィンドウクラスに関する情報は、Windowsの管理するメモリ上のデータベースに格納されます。Windowsは、この情報を元にして、ウィンドウの作成や表示、メッセージの送信などを行なうのです。

通常、アプリケーション独自のウィンドウを作成する際には、ウィンドウクラスを自分で登録する必要があります。HSPウィンドウも例外ではなく、HSP内部ではウィンドウクラスを登録してウィンドウを作成しているのです。しかし、エディットボックスやボタンなどのWindows標準コントロールは、Windowsの提供するウィンドウとしてはじめからウィンドウクラスが登録されていて、アプリケーションはそれを自由に使うことができます。また、ツールバーやステータスバーなどのコモンコントロールは、コモンコントロールライブラリ初期化時にウィンドウクラスが登録され、それ以降でアプリケーションはコモンコントロールを作成して使うことができます。

ウィンドウクラスはそれぞれ名前が付けられていて、その名前によって識別されます。Windowsの提供するコントロールのウィンドウクラスにもそれぞれ名前がつけられており、ウィンドウ作成時にその名前を指定するだけでコントロールを作成することができます。

コモンコントロールのウィンドウクラス名は以下のものがあります。この名前を指定することで、それぞれのコントロールを作成できます。ここでは、クラス名に大文字と小文字が混ざって書かれていますが、実際には大文字・小文字は区別されません。

ウィンドウクラス名 作成されるコモンコントロール
"SysAnimate32" アニメーションコントロール
"msctls_hotkey32" ホットキーコントロール
"msctls_progress32" プログレスバー
"msctls_statusbar32" ステータスバー
"ToolbarWindow32" ツールバー
"tooltips_class32" ツールチップコントロール
"msctls_trackbar32" トラックバー
"msctls_updown32" アップダウンコントロール
"ComboBoxEx32" 拡張コンボボックス
"SysHeader32" ヘッダーコントロール
"SysListView32" リストビュー
"SysTabControl32" タブコントロール
"SysTreeView32" ツリービュー
"SysDateTimePick32" バージョン4.70以降: DTPコントロール
"SysMonthCal32" バージョン4.70以降: 月間カレンダーコントロール
"ReBarWindow32" バージョン4.70以降: レバーコントロール
"SysIPAddress32" バージョン4.71以降: IPアドレスコントロール
"SysPager" バージョン4.71以降: ページャーコントロール

CreateWindowEx関数の第4引数(dwStyleパラメータ)および第1引数(dwExStyleパラメータ)は、それぞれウィンドウスタイルおよび拡張ウィンドウスタイルを指定します。これらは、すべてのウィンドウに共通なスタイルもありますが、作成するコントロールによって指定できる値が変わってくるスタイルもあります。ただし、どのコントロールを作成する場合も、あるウィンドウの子ウィンドウにならなければならないので、0x40000000 (WS_CHILD) を指定しておかなければなりません。また、表示された状態にするには0x10000000 (WS_VISIBLE) も組み合わせて指定されていなければなりません。

この関数の第3引数(pszWindowNameパラメータ)にはウィンドウのキャプションバー(タイトルバー)に表示される文字列を指定するのですが、コモンコントロールはキャプションバーを持たないので、特に指定する必要はありません。

第10引数(hMenuパラメータ)には、コモンコントロールID(子ウィンドウID)を指定します。第12引数(lpParamパラメータ)は、0 (NULL) を指定しておきます。

第11引数(hInstanceパラメータ)には、インスタンスハンドルというものを指定します。インスタンスとは、メモリ上にロードされたプログラムの実体のことを指します。このインスタンスはハンドルによって識別されます。インスタンスハンドルは、HSPウィンドウのBMSCR構造体に含まれているので、mref命令によって取得することができます

mref bmscr, 67           ; BMSCR構造体を変数に割り当てる
hinst = bmscr.14         ; インスタンスハンドル

もしくは、llmodモジュールで定義されている_get_instance命令でも取得できます。

_get_instance hinst      ; インスタンスハンドル取得

コントロールが作成されると、CreateWindowEx関数は戻り値として、作成されたコントロールのウィンドウハンドルを返します。このウィンドウハンドルは、単に、コントロールのハンドルとも呼ばれます。

非表示のコントロール

ところで、初期状態で非表示のコントロールを作成するために、CreateWindowEx関数でウィンドウスタイルにWS_VISIBLEスタイルを指定しないでコントロールを作成することができます。この場合は、コントロールを後から表示させる必要があります。非表示状態のコントロールを表示させるには、ShowWindow関数を呼び出します。

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

この関数は、ウィンドウの表示状態を変更するためのものです。コントロールはすべてウィンドウであるので、この関数で表示状態を変更することができます。

hWndパラメータには、表示状態を変更するウィンドウのハンドルを指定します。非表示状態のコントロールを表示させるためには、このパラメータにコントロールのハンドルを指定する必要があります。

nCmdShowパラメータには、ウィンドウの表示状態を表す値を指定します。非表示状態のコントロールを表示させるには、このパラメータに1 (SW_SHOWNORMAL) を指定します。逆に、表示されていたコントロールを非表示状態にしたい場合には0 (SW_HIDE) を指定します。

llmodモジュール定義命令 _makewnd

さて、llmodモジュールでは、このCreateWindowEx関数を呼び出すモジュール命令_makewndを定義しています。これを使うと、やや簡単に作成できます。

_makewnd v1, s2
v1 : 作成するウィンドウの情報を格納した数値型配列変数
s2 : ウィンドウクラス名

配列変数v1には、あらかじめ初期化情報を格納しておきます。

v1.0:x座標
v1.1:y座標
v1.2:幅(0のときobjsize命令で指定された幅)
v1.3:高さ(0のときobjsize命令で指定された高さ)
v1.4:スタイル
v1.5:親ウィンドウハンドル(0のとき描画中ウィンドウ)
v1.6:拡張ウィンドウスタイル

この命令でコントロールの作成に成功すると、stat0が格納され、v1.0にウィンドウハンドルが格納されます。コントロールID(子ウィンドウID)は、モジュール命令側で決定されます。

コモンコントロールに送るメッセージ

作成したコモンコントロールを操作するためには、専用のAPI関数を使うよりもむしろ、そのコントロールにメッセージを送るということが行なわれます。アプリケーションがSendMessage関数でコントロールにある特定のメッセージを送ると、そのメッセージを受け取ったコントロールは、そのメッセージに応じた処理を行なって、必要ならば戻り値も返します。メッセージの送信によって、まるでAPI関数を使っているかのようにコントロールを操作することができます。このとき、引数を同じ役割を果たすのは、メッセージとともに送られるwParamパラメータとlParamパラメータです。HSP標準命令のobjsend命令でHSPオブジェクトにメッセージを送るといろいろな操作ができますが、あれとまったく同じです。

どんなメッセージコードを送ればどんな操作ができるのかはコントロールの種類によって異なります。

llmodモジュールを使ったメッセージの送信には、モジュール定義命令のsendmsg命令を使うことができます。このページでも、メッセージの送信にはこの命令を頻繁に使用していきます。

ウィンドウメッセージの送信およびsendmsg命令については、『メッセージの送信とポスト』の節を参照してください。

ウィンドウメッセージを発生させるにはSendMessage関数によるメッセージ送信と、PostMessage関数によるメッセージのポストがありましたが、コントロールに対してのメッセージはすべてSendMessage 関数を使います。

コモンコントロールから送られてくるWM_NOTIFYメッセージと通知コード

通知メッセージ

コモンコントロールは、何らかのイベントが起こったとき、例えばコントロール内にユーザーからの入力があったときに、たいていの場合そのことを通知メッセージ という形で親ウィンドウに知らせます。アプリケーションは、この通知メッセージに応じて、どんな処理をするかを決定していくことができます。ツールバーやステータスバーなどを使う場合にはあまり必要のないことなのですが、ツリービューやリストビューなどでは使うことがしばしばあるので、ここで述べておくことにします。

コモンコントロールからの通知メッセージはWM_NOTIFYメッセージの形で送られてきます。

#define  WM_NOTIFY    0x004E

WM_NOTIFY
    idControl = wParam;   // コントロールID
    lpnmh     = lParam;   // NMHDR構造体のアドレス

idControlwParamパラメータ)はコモンコントロールのIDを、lpnmhlParamパラメータ)はNMHDR構造体のアドレス、もしくはNMHDR構造体を第1メンバに持つ構造体のアドレスを示しています。この構造体には、通知メッセージを含む、コモンコントロールで発生したイベントの情報が含まれています。

NMHDR構造体は以下のように定義されています。

typedef struct tagNMHDR {
    HWND hwndFrom;   // コントロールのハンドル
    UINT idFrom;     // コントロールID
    UINT code;       // 通知コード
} NMHDR;

この構造体を変数 nmhdr にとってみたとき、

nmhdr.0 = (コントロールのハンドル)
nmhdr.1 = (コントロールID)
nmhdr.2 = (通知コード)

というようになります。

ところで、「NMHDR構造体を第1メンバに持つ構造体」というのがどういった構造体であるのかは、やや分かりにくいかもしれませんね。簡単に説明すると、先頭にNMHDR構造体のデータが丸ごと入っていると考えます。例えば、多くのコモンコントロールは、コントロールにキーボードフォーカスがあるときにキーが押されると、通知メッセージNM_KEYDOWN (コード-15)を送るのですが、この場合はWM_NOTIFY メッセージのlParamパラメータはNMKEYという構造体のアドレスとなっています。

typedef struct tagNMKEY {
    NMHDR hdr;          // NMHDR構造体
    UINT  nVKey;        // 仮想キーコード
    UINT  uFlags;       // キー状態を示すフラグ
} NMKEY, FAR *LPNMKEY;

このNMKEY構造体は、その第1メンバにNMHDR構造体を持ちます。ここで、この構造体を変数nmkeyにとってみたとき、

nmkey.0 = (コントロールのハンドル)
nmkey.1 = (コントロールID)
nmkey.2 = -15 ;(通知コード NM_KEYDOWN
nmkey.3 = (仮想キーコード)
nmkey.4 = (キー状態を表すフラグ)

というようになります。こうして考えるとだいたい仕組みが分かると思います。どの構造体が送られてくるかは、コモンコントロールの種類と、そのコントロールから送られてくる通知メッセージの種類によって異なります。

hsgetmsg.dllによる通知メッセージの取得

hsgetmsg.dllでは、このWM_NOTIFYメッセージを取得することはできますが、このメッセージに限り、特別に他のメッセージとは違った設定方法をとっています。これにより、送られてくるすべてのWM_NOTIFYメッセージを取得するのではなく、通知コードに応じて、取得するメッセージを選択することができるようになっています。

hsgetmsg.dllによってWM_NOTIFYメッセージの取得設定を行なうには、hsgetmsgモジュール定義命令のset_notifyもしくはDLL拡張命令のgm_setnotify命令を使います。


以上の方法で設定したら、他のメッセージを待つ場合と同様にループ中でget_message命令(またはgm_getmessage命令)を実行します。もしもメッセージが送られていれば、指定された変数に以下のように格納されます。メッセージ情報を受け取る変数をmsgvalとすると、

msgval:受け取ったメッセージ情報が格納される配列変数
msgval.0:ウィンドウハンドル
msgval.10x004E (WM_NOTIFY)
msgval.2:コントロールID (wParam)
msgval.3NMHDR構造体のアドレス (lParam)
msgval.4:コントロールのウィンドウハンドル
msgval.5:コントロールID
msgval.6:通知コード

となります。また、通知コード設定時に追加取得する情報量を設定した場合には、そのデータがmsgval.7以降の領域に格納されます。

ループに入る前にあらかじめ

dup nmhdr, msgval.4

としておくと、メッセージ取得時に

nmhdr.0:コントロールのウィンドウハンドル
nmhdr.1:コントロールID
nmhdr.2:通知コード
nmhdr.3〜:追加取得する情報

と格納されることになるので、スクリプトが分かりやすくなるかもしれません。

コモンコントロールから送られてくるWM_COMMANDメッセージと通知コード

コモンコントロールは、コントロール内で起こったイベントを親ウィンドウに知らせるのに、WM_COMMANDメッセージを使うことがあります。例えば、ツールバーは、ツールボタンが押されたときにそのボタンのアイテムIDをWM_COMMANDメッセージのパラメータに含ませて親ウィンドウに送るのです。親ウィンドウはそのメッセージを取得することで、ツールボタンが押されたことを知ることができます。このとき、WM_COMMANDメッセージのlParamパラメータには、コントロールのハンドルが指定されます。

これについてはここでは詳しく述べません。必要になったときにそれぞれ述べていくことにします。


今回はかなり長くなってしまいましたが、コモンコントロールを扱う上での前置きとしてはこのぐらいでしょう。次回からは何か具体的なコモンコントロールを使っていきたいと思います。