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モジュールでは、このCreateWindowEx関数を呼び出すモジュール命令_makewndを定義しています。これを使うと、やや簡単に作成できます。
配列変数v1には、あらかじめ初期化情報を格納しておきます。
この命令でコントロールの作成に成功すると、statに0が格納され、v1.0にウィンドウハンドルが格納されます。コントロールID(子ウィンドウID)は、モジュール命令側で決定されます。
作成したコモンコントロールを操作するためには、専用のAPI関数を使うよりもむしろ、そのコントロールにメッセージを送るということが行なわれます。アプリケーションがSendMessage関数でコントロールにある特定のメッセージを送ると、そのメッセージを受け取ったコントロールは、そのメッセージに応じた処理を行なって、必要ならば戻り値も返します。メッセージの送信によって、まるでAPI関数を使っているかのようにコントロールを操作することができます。このとき、引数を同じ役割を果たすのは、メッセージとともに送られるwParamパラメータとlParamパラメータです。HSP標準命令のobjsend命令でHSPオブジェクトにメッセージを送るといろいろな操作ができますが、あれとまったく同じです。
どんなメッセージコードを送ればどんな操作ができるのかはコントロールの種類によって異なります。
llmodモジュールを使ったメッセージの送信には、モジュール定義命令のsendmsg命令を使うことができます。このページでも、メッセージの送信にはこの命令を頻繁に使用していきます。
ウィンドウメッセージの送信およびsendmsg命令については、『メッセージの送信とポスト』の節を参照してください。
ウィンドウメッセージを発生させるにはSendMessage関数によるメッセージ送信と、PostMessage関数によるメッセージのポストがありましたが、コントロールに対してのメッセージはすべてSendMessage 関数を使います。
コモンコントロールは、何らかのイベントが起こったとき、例えばコントロール内にユーザーからの入力があったときに、たいていの場合そのことを通知メッセージ という形で親ウィンドウに知らせます。アプリケーションは、この通知メッセージに応じて、どんな処理をするかを決定していくことができます。ツールバーやステータスバーなどを使う場合にはあまり必要のないことなのですが、ツリービューやリストビューなどでは使うことがしばしばあるので、ここで述べておくことにします。
コモンコントロールからの通知メッセージは
メッセージの形で送られてきます。idControl(wParamパラメータ)はコモンコントロールのIDを、lpnmh(lParamパラメータ)はNMHDR構造体のアドレス、もしくはNMHDR構造体を第1メンバに持つ構造体のアドレスを示しています。この構造体には、通知メッセージを含む、コモンコントロールで発生したイベントの情報が含まれています。
NMHDR構造体は以下のように定義されています。
typedef struct tagNMHDR { HWND hwndFrom; // コントロールのハンドル UINT idFrom; // コントロールID UINT code; // 通知コード } NMHDR;
この構造体を変数 nmhdr にとってみたとき、
というようになります。
ところで、「NMHDR構造体を第1メンバに持つ構造体」というのがどういった構造体であるのかは、やや分かりにくいかもしれませんね。簡単に説明すると、先頭にNMHDR構造体のデータが丸ごと入っていると考えます。例えば、多くのコモンコントロールは、コントロールにキーボードフォーカスがあるときにキーが押されると、通知メッセージNM_KEYDOWN (コード-15)を送るのですが、この場合は メッセージのlParamパラメータはNMKEYという構造体のアドレスとなっています。
typedef struct tagNMKEY { NMHDR hdr; // NMHDR構造体 UINT nVKey; // 仮想キーコード UINT uFlags; // キー状態を示すフラグ } NMKEY, FAR *LPNMKEY;
このNMKEY構造体は、その第1メンバにNMHDR構造体を持ちます。ここで、この構造体を変数nmkeyにとってみたとき、
というようになります。こうして考えるとだいたい仕組みが分かると思います。どの構造体が送られてくるかは、コモンコントロールの種類と、そのコントロールから送られてくる通知メッセージの種類によって異なります。
hsgetmsg.dllでは、この
メッセージを取得することはできますが、このメッセージに限り、特別に他のメッセージとは違った設定方法をとっています。これにより、送られてくるすべての メッセージを取得するのではなく、通知コードに応じて、取得するメッセージを選択することができるようになっています。hsgetmsg.dllによってset_notifyもしくはDLL拡張命令のgm_setnotify命令を使います。
メッセージの取得設定を行なうには、hsgetmsgモジュール定義命令の描画中のウィンドウが親ウィンドウとなる場合の設定を行ないます。通常はこちらを使用することになると思います。
第1パラメータには通知コードを指定します。送られてきた
メッセージの通知コードがここで指定した値と等しい場合に、メッセージが取得されます。第2パラメータの追加取得する情報量とは、NMHDR構造体を第1メンバに持つ構造体」となるような場合に、その構造体の最初のNMHDR構造体を除いた部分のデータを変数に入れたときの4バイト配列の大きさ(通常はデータサイズを4で割った値)を指定します。
メッセージのlParamパラメータが「例えば、先ほどの、通知コードNM_KEYDOWNの時の メッセージを取得したい場合、NMKEY構造体は、通知コードが-15であり、最初のNMHDR構造体以外に仮想キーコードを表すnVKeyメンバとキー状態を示すフラグを表すuFlagsメンバのデータ(両方合わせて8バイト分)があるので、追加取得する情報量として2を指定することになります。すなわち、
set_subclass ; サブクラス化 set_notify -15, 2 ; メッセージ取得設定
とします。ここで示しているように、あらかじめ親ウィンドウをサブクラス化することが必要になります。
こちらは、親ウィンドウのハンドルを指定したい場合に使用します。モジュール命令set_notifyは、内部でこの命令を実行しています。
こちらを使うことはあまりないと思うので、説明を省略します。
以上の方法で設定したら、他のメッセージを待つ場合と同様にループ中でget_message命令(またはgm_getmessage命令)を実行します。もしもメッセージが送られていれば、指定された変数に以下のように格納されます。メッセージ情報を受け取る変数をmsgvalとすると、
となります。また、通知コード設定時に追加取得する情報量を設定した場合には、そのデータがmsgval.7以降の領域に格納されます。
ループに入る前にあらかじめ
dup nmhdr, msgval.4
としておくと、メッセージ取得時に
と格納されることになるので、スクリプトが分かりやすくなるかもしれません。
コモンコントロールは、コントロール内で起こったイベントを親ウィンドウに知らせるのに、
メッセージを使うことがあります。例えば、ツールバーは、ツールボタンが押されたときにそのボタンのアイテムIDを メッセージのパラメータに含ませて親ウィンドウに送るのです。親ウィンドウはそのメッセージを取得することで、ツールボタンが押されたことを知ることができます。このとき、 メッセージのlParamパラメータには、コントロールのハンドルが指定されます。これについてはここでは詳しく述べません。必要になったときにそれぞれ述べていくことにします。
今回はかなり長くなってしまいましたが、コモンコントロールを扱う上での前置きとしてはこのぐらいでしょう。次回からは何か具体的なコモンコントロールを使っていきたいと思います。