コントロール描画方法を考える
コントロールを具体的に描画する方法を考えている。頭の中にあるイメージはこんな感じである。
アプリのスレッドをフックすると親ウィンドウ・子ウィンドウのメッセージはすべて捕まえることができるのでそのうちのWM_PAINTメッセージを捕まえて自前で描画する。何のコントロールなのかはウィンドウ・クラス名で識別することができる。ウィンドウ中のコントロールはすべて子ウィンドウなのですべてHWNDを持っているのでインスタンスごとの識別はHWNDでできる。HWNDからは描画に必要な属性を取得することができる。
でも描画の都度HWNDを使用してOSから描画に必要な情報を取得するのもなんか遅そうな感じもする。アプリ側でキャッシュしておきたい気もする。
参考にしているSkinXというフレームワークのコードを読むと、WM_CREATEメッセージを捕まえて個々のコントロールに対応したラッパークラスを生成している。なので描画に必要な属性をキャッシュすることができる。さらにSkinXではラッパークラス側でコントロールをサブクラス化して、WM_PAINTメッセージを捕まえて個々のコントロールを描画している。この方法でいくほうがよさそうである。
SkinXはプラグ・イン可能なスキン・フレームワークをCOMで実装するものでそこに関してもアイデアを拝借したいのだけれども、ちょっと面倒なのでそこは時間があればにする。やろうとするとATLが使用できないのでWRLを使ってCOMを実装することになると思う。
フック
フックとはメッセージをウィンドウプロシージャに送られる前に捕まえる仕組みである。フックすること自体は簡単でSetWindowsHookExを呼び出すだけである。呼び出すとHHOOKというハンドルが帰る。これを終了時までに保存しておいて、終了時UnhookWindowsHookExを呼び出すときの引数にする。HHOOKを管理しないといけないのでこれはコンストラクタでSetWindowsHookExを呼び出し、デスクトラクタでUnhookWindowsHookExを呼び出すラッパーを作成しておいたほうがよさそうである。
SetWindowsHookExは以下の引数を取る。
HHOOK SetWindowsHookEx( int idHook, // フックタイプ HOOKPROC lpfn, // フックプロシージャ HINSTANCE hMod, // アプリケーションインスタンスのハンドル DWORD dwThreadId // スレッドの識別子 );
フックタイプはどのようなメッセージを捕まえるのかである。いろいろな種類がある。今回はWH_CALLWNDPROCでいいかなと思う。フックプロシージャはフックしたメッセージを処理する関数ポインタを指定する。あとはインスタンスハンドルとスレッド識別子を指定する。
WH_CALLWNDPROCの場合HOOKPROCは以下の引数を取る。
LRESULT CALLBACK CallWndProc( In int nCode,// メッセージをどのように扱うか In WPARAM wParam,// カレントスレッドからメッセージが送信されたかどうか In LPARAM lParam// CWPSTRUCT構造体へのポインタ );
nCodeは送られてきたメッセージをどのように扱うかである。HC_ACTIONであればこの関数内でメッセージを処理できる。0未満であれば処理せずにCallNextHookExを呼び出して戻り値をOSに返さなくてはならない。/p>
wParamはカレントスレッドからメッセージが送られてきた場合は0以外の値が、他のスレッドであれば0が入っている。そういうわけでマルチスレッドを考慮しなければならないかもしれない。
lParamはCWPSTRUCT構造体へのポインタであり、メッセージに関する情報はここに格納されている。CWPSTRUCT構造体の定義は以下の通りである
typedef struct tagCWPSTRUCT { LPARAM lParam; WPARAM wParam; UINT message; HWND hwnd; } CWPSTRUCT, PCWPSTRUCT, LPCWPSTRUCT;
戻り値であるがLRESULT型であり、基本はCallNextHookExの戻り値を返すことになっている。CallNextHookExは次のフックを呼び出す関数である。LRESULTに0をセットして返すことでそれ以降にメッセージを流れないようにすることができる。MSDNにも書いてあるがフック内ではメッセージの流れを止めることは推奨されていない。なぜならフックはチェーンできるので止めてしまうと後続のフックにメッセージが流れなくなるためである。
今回WM_PAINTを横取りしようとしているがフックしたとしてもそれを後続に流さないとその後OSがどのような処理をしているかわからないので不具合がでる可能性がある。そういうことも考えるとやっぱりコントロールをサブクラス化してそこでWM_PAINTメッセージを捕まえるほうがよさそうである。
サブクラス化
サブクラス化とはウィンドウ・プロシージャを挿げ替えることである。コントロールのデフォルト・ウィンドウ・プロシージャを自前ウィンドウ・プロシージャに置き換えることでコントロールの挙動をカスタマイズすることができる。
私はSetWindowLongPtr関数を使う方法しか知らなかったのだが、XPからSetWindowSubclass/RemoveWindowSubclass が使えるようになっている。これを使えばデフォルト・ウィンドウ・プロシージャを保存したりもとに戻したりする必要がない。
SetWindowSubclassのパラメータは以下である。
BOOL SetWindowSubclass( In HWND hWnd,// ウィンドウ・ハンドル In SUBCLASSPROC pfnSubclass,// SUBCLASSPROCへのポインタ In UINT_PTR uIdSubclass,// サブクラスID In DWORD_PTR dwRefData// SUBCLASSPROCに渡すパラメータ );
SetWindowLongPtrを使う方法だとまさにウィンドウ・プロシージャを挿げ替えるのだが、この関数を使用するとウィンドウ・プロシージャのかわりにSUBCLASSPROCを使う。SUBCLASSPROC自体はウィンドウ・プロシージャと大差ないが、SUBCLASSPROCごとにことなるdwRefDataを渡すことが可能となっている。私の場合このSUBCLASSPROCにラッパークラスのインスタンスとHWNDを結びつけるサンク関数をセットする。ATLの場合はこのあたりはライブラリに隠ぺいされているが、ATLを持たない私は自前で実装しなくてはならない。このコードを書くのに私はXBYAKを使用している。おかげでATLと違ってサンクコードをバイナリで書く必要がなくて助かっている。
とりあえずこのあたりの知識をおさえておけばまあ作れると思うけれど、でもコントロールの属性っていっぱいあるから描画カスタマイズってとても面倒なんだよね。条件判断をいっぱいしなければならないので。ああ、面倒くさい。。