マルチウィンドウ上(MDIではない)で、ひとつをTreeView、他のウィンドウをスクロール
対応にしています。
困ったことにTreeViewはどのウィンドウ上にマウスがあっても、マウスホイールで勝手
にスクロールしてしまいます。
スクロールを実装した別ウィンドウにマウスがある時は、そちらのウィンドウをスクロー
ルさせたいのですが、WM_MOUSEWHEELメッセージが親ウィンドウのプロシージャに届か
ず、うまく行きません。
やむなく
TranslateMassage()前にWM_MOUSEWHEELを捕まえて強制的に動かしたいウィンドウのスク
ロールに成功したのですが、今度はフォーカスをTreeView側に移してもTreeView側のスク
ロールが実行されません。
どうすれば意図するウィンドウのスクロールを有効にすることができるのでしょうか?
コモンコントロールのTreeViewではマウスホイール処理がどのように実装されているので
しょうか?
開発はVC++(VC2003)のSDKでプログラムしています。
ヘルプによると、以下のような記載がありますね。
WM_MOUSEWHEELは Windows によって、フォーカスのあるコントロールまたは
子ウィンドウに自動的に転送されます。Win32 の関数 DefWindowProc は、
このメッセージを処理するウィンドウに到達するまで、親チェインを
さかのぼってメッセージを転送します。
つまり、何らかのウィンドウでWM_MOUSEWHEELに対してTRUEを返さない限り
処理が終わらない、という意味だと思います。
> ひとつをTreeView、他のウィンドウをスクロール対応にしています。
TreeView以外のスクロール対応のウィンドウのウィンドウプロシージャでは
WM_MOUSEWHEELのイベントハンドラを実装していますか?
また、その処理から戻る時にTRUEを返していますか?
TRUEを返せば、親チェーンをさかのぼることもなくなるような気がします。
> どうすれば意図するウィンドウのスクロールを有効にすることができるのでしょう
か?
「意図するウィンドウ」を判定する条件がはっきりしているのであれば、
TreeViewをサブクラス化して条件外の場合にはWM_MOUSEWHEELをスルーする、
または意図するウィンドウにパスする、という方法もあるかもしれませんね。
今、これを書いていて思い出しました。
HTML ヘルプなどでも左側のTreeViewにフォーカスがある状態で、右側の
コンテンツペイン内でホイールを回転させると、TreeViewの方がスクロール
してしまいますよね。これと同じ現象でしょうか?
このような場合には、やはりTreeViewをサブクラス化してマウスポインタの
座標によって、目的のウィンドウにメッセージをパスするような方法が有効な気がしま
す。
もっと、スマートな方法があるかもしれませんが・・・
Windowsのホイールメッセージの実装は、ほんとにばかげてますよね(^^;)。
インターネットエクスプローラはちゃんと処理してるようですし、
自分でバンバンやっちゃいましょう(笑)。
自分の場合は次の要領でやってます。
1.メインフレームのPreTranslateMessage()をオーバーライドする
BOOL CMainFrame::PreTranslateMessage(MSG* pMsg){
// マウスホイールが処理したら戻る
if( TRUE == PreTranslate_MouseWheel( pMsg)){ return TRUE;}
return CMDIFrameWnd::PreTranslateMessage(pMsg);
}
2.PreTranslate_MouseWheel()を実装する
BOOL CMainFrame::PreTranslate_MouseWheel( MSG *msg){
// ホイールメッセージ以外は処理しない
if( msg->message != WM_MOUSEWHEEL) return FALSE;
// メッセージのHWNDが最上位フレームで無い場合は最上位フレームに変更す
// ■これは、マウスをキャプチャーしているコントロールがメッセージを
// 独占するのを防ぎ、現在のカーソル直下の「正しい」コントロールに
// メッセージを送付したいため。
HWND hwnd_root = ::GetAncestor( msg->hwnd, GA_ROOTOWNER);
msg->hwnd = hwnd_root; // 差し替える
// カーソル直下の子ウインドウを取得する
HWND hwnd_child = ::WindowFromPoint( msg->pt);
if( NULL == hwnd_child) return FALSE;// 子が見つからない場合は未処理で終
了
// コンボボックスには送らない(迷惑な場合が多いため)
char classname[ 128];
::GetClassNameA( hwnd_child, classname, 128);
if( 0 == ::strcmp( ComboBox, classname)) return FALSE;
// 見つかった子ウインドウにフォーカスを強制し、メッセージを送付する
::SetFocus( hwnd_child); // フォーカスを強制する。一般にこれでキャプチャ
ーが外れる
::SendMessage( hwnd_child, msg->message, msg->wParam, msg->lParam);
return TRUE;
}
さて、SDKの場合はメインメッセージループで当該の関数を呼べばおけのはず
// メイン メッセージ ループ:
MSG msg; // メッセージ
while( ::GetMessage( &msg, NULL, 0, 0)){
// マウスホイールが処理したら戻る
if( TRUE == PreTranslate_MouseWheel( msg)) continue;
// 通常のディスパッチ
::TranslateMessage( &msg);
::DispatchMessage( &msg);
}
注意:GetAncestor()でエラーになるようならプラットホームかエクスプローら
バージョンの指定を変えてくださいね。(どっちだったか失念しました)。
#define _WIN32_WINNT 0x0502
#define _WIN32_IE 0x0600
連投失礼m(__)m
先の方法だと、自分以外のWindowにも効果が出てしまいますね。
気に入らなかったら自分のHWNDに限定してください。
MistyGreenさん、仲澤@失業者さん、親切なご回答ありがとうございます。
MistyGreenさん
>今、これを書いていて思い出しました。
>HTML ヘルプなどでも左側のTreeViewにフォーカスがある状態で、右側の
>コンテンツペイン内でホイールを回転させると、TreeViewの方がスクロール
>してしまいますよね。これと同じ現象でしょうか?
⇒まさにその現象です。
>TreeView以外のスクロール対応のウィンドウのウィンドウプロシージャでは
>WM_MOUSEWHEELのイベントハンドラを実装していますか?
>また、その処理から戻る時にTRUEを返していますか?
⇒TRUEを返していますが、TreeView側には処理が戻りません。
仲澤@失業者さん
私はMFCは使っておりませんが、仲澤@失業者さんのサンプルのようにTranslateMessage
()手前でWM_MOUSEWHEELを捕まえて期待するウィンドウのスクロールを行っています。
でもTreeView側では新たなスクロール処理を実装せずに済ませたいと思っています。
TreeView側のスクロール処理も新たに実装する必要がありますか?
単純にTRUEを返せば、TreeView側に既に実装されている処理がなされるのでしょうか?
極力新たな実装をせずにスマートに行きたいのですが。。
すいません今、手元にソースがないので、週末にでももう一度トライしてみたいと
思います。
お二人さん
まずは、ありがとうございました。
>TreeView側のスクロール処理も新たに実装する必要がありますか?
まったく必要ありません。
「連投」の方にも書きましたが、ホイールメッセージを常識的な手順で
処理をしている限り、自分のコードを実装すれば、自分の配下、
また他のウインドウの配下の、マウスカーソル直下の、全てのコントロール
(コンボボックスを除く)にWM_MOUSEWHEELが届きます。
尚、マウス位置はいじっていないので、デスクトップ座標のままになっています。
これは、ほとんどのコントロールがデスクトップ座標のままで、正しく反応する
ことを確かめた結果の仕様です。
仲澤さん、フォローありがとうございました。
PreTranslateMessage()で処理した方が断然スマートですね。
私も仲澤さんのコードをストックさせて頂きます。(^_^;
今後ともよろしくお願い致します。
半信半疑でPreTranslate_MouseWheel( &msg )を実装してみました。
え!!
あっさり動きました^^/
こういう時は、
::GetAssensor()でルートウィンドウのハンドルを取得するのですね(フムフム)
仲澤@失業者さん、MistyGreenさん
ありがとうございましたm(_ _)m