Win32APIにおけるコントロールのクラス化

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
ぬっち

Win32APIにおけるコントロールのクラス化

#1

投稿記事 by ぬっち » 8年前

いつもお世話になっております。

Win32APIにおけるコントロールのクラス化についてお聞きしたいと思い、書き込みを行いました。
コントロールのクラス化については、
http://members3.jcom.home.ne.jp/progstu ... index.html
にある、「一般的なWindowsプログラミング」の「プロシージャのメンバ化」から「サブクラス化」までを参考に実現しようと考えていました。
ところが「サブクラス化」において、SetWindowLongの第3引数に指定したウィンドウプロシージャはどのような形で持たせておくべきなのかわからずに詰ってしまいました。
コントロールのクラス化というと、そのウィンドウプロシージャもメソッドとして持っておくべきなのではないかと思ったのですが、これはstaticでなくてはなりません。

具体的には、
class Control : public Window //←これは、「ウィンドウのクラス化」のCBaseWndとほぼ同じ
{
public:
    virtual LRESULT MainProc(...);
    virtual void Create();    //ここで、SetWindowLongを呼び出し、ウィンドウプロシージャを「MainProc」から「ControlProc」に変更。←もちろんこれはControlのインスタンスが作成されていないためエラーとなる。
    virtual LRESULT ControlProc(...);    //コントロール別のプロシージャ
};
のように行いたいです。
もちろんこのままではSetWindowLongでエラーが出るのは確実です。
しかし、このホームページでは、コントロールのクラス化について可能であるようなことが書いてあるので、どのように実現するのか知りたかったので質問しました。

このホームページの管理人さんに連絡が取れればよかったのですが、最終更新日が2005年で、BBSも消えてしまっている状態なので、場違いかと思ったのですがここで質問させていただきました。

よろしくお願いします。

ISLe

Re:Win32APIにおけるコントロールのクラス化

#2

投稿記事 by ISLe » 8年前

そのページに書いてあるとおりにすれば良いと思うのですが。
おっしゃるとおりWin32APIで指定するウインドウプロシージャは静的なメンバ関数である必要があります。
静的ではないメンバ関数では引数とともに暗黙にインスタンスへのポインタを渡すので関数の呼び出し方が違うからです。
そのため
1) 作成したウインドウが用意したユーザー用のメモリ領域にインスタンスへのポインタを記録しておく
2) ウインドウズが静的ではないメンバ関数をウインドウプロシージャとして呼び出す
3) ウインドウプロシージャとして呼び出された静的ではないメンバ関数でウインドウのユーザー用のメモリ領域に記録されたインスタンスへのポインタを取得する
4) 取得したインスターンスへのポインタを使って静的ではないメンバ関数で用意したウインドウプロシージャを呼び出す
という手順を踏む必要があります。

「サブクラス化」のページではウインドウプロシージャをフックする方法しか書かれていませんので、静的ではないメンバ関数で用意したウインドウプロシージャを呼び出すためには同時にウインドウのユーザー用メモリ領域にインスタンスへのポインタ(this)を記録する必要があります。
ただしサブクラス化する対象のウインドウによっては既にウインドウのユーザー用メモリ領域が既に別の目的に使われている可能性がありますので、元のウインドウプロシージャを呼び出す前にウインドウのユーザー用メモリ領域を元の値に戻し、元のウインドウプロシージャから戻ったらthisポインタを書き込む処理を追加する必要があります。

ぬっち

Re:Win32APIにおけるコントロールのクラス化

#3

投稿記事 by ぬっち » 8年前

ISLeさん返信ありがとうございます。

すいません、私の理解不足のためか、やはりよく流れがわかっていないようです。
1)のユーザー用メモリ領域にインスタンスへのポインタを記録するというのはわかるのですが、その後の流れがいまいちよくわかりません。
ウィンドウプロシージャをフックする方法というのは、基本的にはどこで呼ぶべきものなのでしょう?
前の節で作成した静的なメンバ関数に処理を追加するということでしょうか?
だとすると、コントロールとウィンドウのプロシージャをひとまとめにするってことでしょうか?

完全に理解不足なため、うまく言葉に出来なくて申し訳ないですが、よろしくお願いします。

ISLe

Re:Win32APIにおけるコントロールのクラス化

#4

投稿記事 by ISLe » 8年前

> ウィンドウプロシージャをフックする方法というのは、基本的にはどこで呼ぶべきものなのでしょう?
ウインドウハンドルを使うのでウインドウハンドルが取得できるタイミングならいつでも良いです。

質問にあるコントロールとは自作のコントロールなのでしょうか?
「サブクラス化」は既存のコントールでソースコードから手を加えられない(加えたくない)ときにしか必要ないです。
自作のコントロールならそのままのコードを流用できるはずです。
MainProcをControlProcに変えたいだけなら「ウィンドウのクラス化」ページのCBaseWnd::CallProcにMainProcを呼び出しているところがあります。
thisポインタはCreateWindow APIの最後の引数を経由して受け取っていますが、WM_CREATEメッセージを処理するコードの中でインスタンスを作成しても構いません。
その場合インスタンスの作成に失敗したときはウインドウプロシージャの戻り値に-1を返せばCreateWindow APIの呼び出しが失敗したことになります。

> だとすると、コントロールとウィンドウのプロシージャをひとまとめにするってことでしょうか?
この意味が分かりません。
コントロールとウインドウで何が違うのでしょう?
もしかしてサンプルどおりだとMainProcしか呼ばれないがControlProcも呼び出すようにしたいということでしょうか。
もしそうならMainProcの最初にControlProcを呼び出すようにオーバーライドするだけです。

ぬっち

Re:Win32APIにおけるコントロールのクラス化

#5

投稿記事 by ぬっち » 8年前

返信ありがとうございます。

> 質問にあるコントロールとは自作のコントロールなのでしょうか?

はい、もちろん自作です。

> コントロールとウインドウで何が違うのでしょう?

コントロールとウィンドウは基本的には同じものとして考えています。
うーん、なんて説明したらよいかわからないのですが・・・。
メッセージを横取りする方法という点がコントロールと違うのかなと思ったのですが・・・。


なんとなく、ISLeさんのおっしゃっていることがわかってきた気がします。
確認ですが、コントロールについてもウィンドウと同じように、それぞれのコントロール特有の動作をMainProcで記述すればよくて、その場合は、例としてボタンが押されたと判定する時にWM_RBUTTONUPなどで判定すればよいということになるのでしょうか?
そうすると、SetWindowLongやCallWindowLongが必要ない気がするのですがこれでもいいのでしょうか?

ISLe

Re:Win32APIにおけるコントロールのクラス化

#6

投稿記事 by ISLe » 8年前

> はい、もちろん自作です。

既存のコントロールに似たものでなく標準のコントロールの振る舞いをサブクラス化して継承する必要もないのであればふつうのウインドウと同じように実装すれば良いです。

> 例としてボタンが押されたと判定する時にWM_RBUTTONUPなどで判定すればよいということになるのでしょうか?
そもそも標準のコントロールをサブクラス化しない限りBM_~といったコントロール独自のメッセージは飛んできませんのでいわゆるふつうのウインドウメッセージだけで対応する必要があります。
コントロール独自のメッセージは標準のコントロール用にあらかじめ用意されているウインドウプロシージャによってコントロール自身が発行しているものです。
例えばCreateWindow APIの引数でウインドウクラスに"BUTTON"という文字列を指定するとウインドウズにあらかじめ用意されているボタンコントロール用のウインドウプロシージャが呼ばれるといった仕組みになっています。
"BUTTON"というウインドウクラスを使うとき自前のウインドウプロシージャを指定することはできないのでサブクラス化が必要になるわけです。

> そうすると、SetWindowLongやCallWindowLongが必要ない気がするのですがこれでもいいのでしょうか?
サブクラス化しなければCallWindowProcは必要ないですが、コントロールのプログラムコードをC++のクラスとして記述するならSetWindowLongは必要ではないでしょうか。

ぬっち

Re:Win32APIにおけるコントロールのクラス化

#7

投稿記事 by ぬっち » 8年前

返信ありがとうございます。

> コントロール独自のメッセージは標準のコントロール用にあらかじめ用意されているウインドウプロシージャによってコントロール自身が発行しているものです。
> 例えばCreateWindow APIの引数でウインドウクラスに"BUTTON"という文字列を指定するとウインドウズにあらかじめ用意されているボタンコントロール用のウインドウプロシージャが呼ばれるといった仕組みになっています。
> "BUTTON"というウインドウクラスを使うとき自前のウインドウプロシージャを指定することはできないのでサブクラス化が必要になるわけです。

もしかしたら自作のコントロールっていうのは"BUTTON"を指定しない場合に作った時のことですか?
それだったら、今回の場合は"BUTTON"などを指定しているため自作ではないということになってしまうのでしょうか?
勘違いしていたようで申し訳ありません。
やはり"BUTTON"のように指定すると、サブクラス化しないとダメですかね?

以下はサブクラス化の実装手段についての質問です。
今のところ、MainProcをコントロールが持っている状態で、サブクラス化したときに作るべきウィンドウプロシージャをどのようにしたらよいのかが、さっぱりつかめません。
MainProc用の静的メソッド(ホームページで言うCallProc)と、ControlProc用の静的メソッドが両方必要ということでしょうか?

> サブクラス化しなければCallWindowProcは必要ないですが、コントロールのプログラムコードをC++のクラスとして記述するならSetWindowLongは必要ではないでしょうか。

あ、確かにコントロールでもSetWindowLongは必要でしたね。
すいません、間違えました。 画像

ISLe

Re:Win32APIにおけるコントロールのクラス化

#8

投稿記事 by ISLe » 8年前

> やはり"BUTTON"のように指定すると、サブクラス化しないとダメですかね?
標準のボタンコントロールの振る舞いを継承するなら作成されたウインドウハンドルを使ってサブクラス化する必要がありますね。

> 以下はサブクラス化の実装手段についての質問です。
> 今のところ、MainProcをコントロールが持っている状態で、サブクラス化したときに作るべきウィンドウプロシージャをどのようにしたらよいのかが、さっぱりつかめません。
「ウィンドウのクラス化」ページで言うところのCBaseWnd::CallProcをセットすれば良いです。

> MainProc用の静的メソッド(ホームページで言うCallProc)と、ControlProc用の静的メソッドが両方必要ということでしょうか?

MainProcのオーバーライドだけで目的を達成することは可能だと思います。
MainProcは仮想関数なのでControlクラスでオーバーライドした関数が呼ばれます。
ControlクラスにControlProcを呼び出す静的なメンバ関数を定義するかどうかは設計の問題なので必要・不必要のどちらとも言えません。
画像

ぬっち

Re:Win32APIにおけるコントロールのクラス化

#9

投稿記事 by ぬっち » 8年前

返信ありがとうございます。

考えていただけでは、進まないので試行錯誤してやってみました。
ISLeさんの提案による、MainProcのオーバーライドの方法で行ってみました。
コントロール上でクリックすると、メッセージボックスが表示されるというものです。
ですが、やはり反応していないようです。
そもそも、デバッグを掛けてみると、オーバーライドしたもののウィンドウプロシージャが呼ばれていないみたいです。
staticなウィンドウプロシージャ入れ替え用のメソッドを作ること以外で、CBaseWndクラスを継承してサブクラス化を行うというのはやはり出来ないのでしょうか、うーん。。。

一応、参考までに必要な部分のソースコードを載せてみますので、間違い名部分があるようでしたら指摘していただければ助かります。

ISLe

Re:Win32APIにおけるコントロールのクラス化

#10

投稿記事 by ISLe » 8年前

ソースコードはコンパイルできませんでしたが眺めてみました。
とりあえず
Controlクラスのメンバに
WNDPROC defWndProc;
を追加。
Control::Create関数でコントロールのCreateWindowのあとに
defWndProc = SetWindowLong(hWnd, GWL_WNDPROC, (LONG)(Window::MsgProc));
SetPointer(hWnd);
の二行を追加。
Control::WndProc関数とMyControl::WndProc関数の最後は
return CallWindowProc( defWndProc, hWnd, message, wParam, lParam );
に変更。
これでMyControl::WndProcが呼ばれるのではないでしょうか。

せっかく継承しているのですからMyControl::WndProcはフックしたメッセージ以外はControl::WndProcを呼び出して投げてしまうほうが良いと思います。
あとは適当に整理してください。

ぬっち

Re:Win32APIにおけるコントロールのクラス化

#11

投稿記事 by ぬっち » 8年前

おぉ、出来ました!!
ソースコードを早めにお見せしたほうがよかったですね^^;
正直まだやっていることがきちんと理解できていないのですが、やっていることは、
・コントロールのウィンドウを作成すると、コントロールのウィンドウプロシージャが自動的に作成してしまう。
・自動的に作成されたウィンドウプロシージャをWindow::MsgProcという静的メンバ関数に置き換える。
・こうすることで、コントロールが自動生成したウィンドウプロシージャに対するメッセージをWindow::MsgProcが横取りする。
・Window::MsgProcでは、それぞれのウィンドウのクラスで独自に作成した静的でないウィンドウプロシージャを呼び出すことになる。
・結果として、Control::WndProcが呼び出される。
・Control::WndProcが呼び出された後、場合によりCallWindowProcによりコントロールが自動的に作成したウィンドウプロシージャを実行する。
・SetPointerをわざわざ呼び出しているのは、Window::MsgProcを呼び出したときにウィンドウプロシージャが受け取るHWNDは、親ウィンドウのものであるため、Window::MsgProc内のSetPointerの部分が呼び出されないため。
ということでしょうか。

長々と質問に答えていただきありがとうございました。

ISLe

Re:Win32APIにおけるコントロールのクラス化

#12

投稿記事 by ISLe » 8年前

> ・結果として、Control::WndProcが呼び出される。
「結果として」というところに、質問に書かれたリンク先のページで言うところの「プロシージャのメンバ化」「ウインドウのクラス化」で説明されている仕組みがあるわけですがずいぶん省略してしまいましたね。

> ・SetPointerをわざわざ呼び出しているのは、Window::MsgProcを呼び出したときにウィンドウプロシージャが受け取るHWNDは、親ウィンドウのものであるため、Window::MsgProc内のSetPointerの部分が呼び出されないため。
No:65540では間違えてますがControl::Create関数に追加したコードにm_HWndを使ったと思います。
m_HWndはボタンコントールのウインドウハンドルなので親ウインドウではないです。
SetPointerの中身を見てもらえればウインドウのユーザー用メモリ領域にthisポインタを記録しているのが分かると思いますが、これはWindow::MsgProcで静的ではないメンバ関数を呼び出すためです。
「プロシージャのメンバ化」「ウインドウのクラス化」で説明されているものと同じです。

ウインドウのユーザー用メモリ領域にインスタンスへのポインタを記録したらいったんこちらの手を離れて、ウインドウズがウインドウプロシージャとして呼び出した静的なメンバ関数でウインドウのユーザー用メモリ領域からインスタンスへのポインタを取得してメンバ関数を呼び出すという一連の動作を理解されていないように見受けられます。
静的なメンバ関数がウインドウプロシージャとして呼び出されるところは時間的な隔たりがあります。
インスタンスやポインタについて知識や経験を深めた上で実行時の様子を想像しながらコードを追いかけてください。
コードを順番に読み進めるだけでは分からないと思います。
クラスを有効に活用できるようMyControlを複数配置したときのプログラムの動きを追いかけられるように頑張ってみてください。

ぬっち

Re:Win32APIにおけるコントロールのクラス化

#13

投稿記事 by ぬっち » 8年前

ISLeさん、最後まで長々と私の質問に付き合っていただきありがとうございます。
まだまだプログラミングの経験が浅いですが、これからも続けて経験を深めていきたいと思います。
どうもありがとうございましたm(--)m

閉鎖

“C言語何でも質問掲示板” へ戻る