2008年12月27日

ウインドウ(コントロール)のサブクラス化

Windowsの標準コントロールから少しだけ動作を変更したいときは、サブクラス化というテクニックを使います。

サブクラス化は、コントロールが持っている本来のウインドウプロシージャを自前の関数と差し替えて行います。変更したい動作に関係のあるメッセージだけ自前のプロシージャで処理し、関係のない残りのメッセージを元のプロシージャに渡すことで、コントロールを1から作らなくても標準コントロールから少しだけ動作を変えたものを作ることができます。メッセージフックでも同じことができますが、フックよりも簡単に使うことができます。

以前はこのサブクラス化は、SetWindowLongでウインドウのプロシージャへのポインタを直接書き換えることで行いました。しかし、この方法には問題があります。

同じウインドウに対して複数回のサブクラス化が行われた場合、それぞれのルーチンが1つ前のプロシージャのポインタを保持しているので、解除する際はサブクラス化したときと完全に逆の順で行う必要があります。また、途中で自分のプロシージャを差し替えたくなっても、自分のプロシージャへのポインタは後からサブクラス化したルーチンが握っているので、それを変更することはできません。よって、一度サブクラス化すると、実質的に変更も解除もできなくなります。(よく考えずに解除してシステムをクラッシュさせるプログラムをたまに見る)

Windows XP以降ではこの問題に対処するために、SetWindowSubclassというサブクラス化専用のAPIが用意されています。このAPIを使うとサブクラスチェーンの管理をOS側でやってくれて、SetWindowLongを使った方法が持つ欠点が解消されています。

BOOL SetWindowSubclass(
    HWND hWnd,
    SUBCLASSPROC pfnSubclass,
    UINT_PTR uIdSubclass,
    DWORD_PTR dwRefData
);

SetWindowSubclassを使うには、commctrl.hをincludeする前に、_WIN32_WINNTマクロに0x0501以上の値をセットしておく必要があります。

#define _WIN32_WINNT 0x0501
#include <commctrl.h>

SetWindowSubclassの引数には、サブクラス化するウインドウのハンドル、新しいプロシージャのポインタ、サブクラスを一意に識別するためのID、ユーザ定義の任意の整数値を指定します。最後の整数値はポインタと同じ大きさの型(32bit環境では32bit、64bit環境では64bit)なので、ポインタ値を入れることができます。この整数値はプロシージャが呼ばれるときに引数で渡されます。

ウインドウ関係の処理をC++のクラスでラップしようとすると、OSからコールバックされるウインドウプロシージャはインスタンスに含めることができないので、thisポインタをウインドウのUSERDATA領域に格納するのがセオリーです。しかし、USERDATA領域は1つのポインタしか格納できないので、サブクラスで使うのは面倒です。SetPropで独自のプロパティ値をウインドウに追加することも可能ですが、全てのサブクラスで名前が被らないように管理する必要があります。

SetWindowSubclassを使ったサブクラス化では、各プロシージャに対応するポインタ値をOSが管理してくれるので便利です。USERDATAのように他のルーチンが書き換えてしまうこともありませんので、安心して使えます。サブクラス化しない素のウインドウクラスでも、USERDATAではなくSetWindowSubclassでthisポインタを管理した方が安全かもしれませんね。

色々と便利なAPIですが、引数が冗長な気がします。IDが一意になるように保証しようとするとthisポインタあたりをIDにせざるを得ないので、結局dwRefDataと同じ値を使うことになると思います。それとも、私が気づいていない便利な使い道があるんでしょうか?

タグ:Win32API
2008年12月27日 【プログラミング】 | コメント(0) |

この記事へのトラックバック
この記事へのコメント

コメントを書く
お名前: [必須入力]

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。