TreeViewのスクロールのマウスホイール有効、無効化

[戻る]
マコ [E-Mail] 2010/08/04(水) 18:00:50
マルチウィンドウ上(MDIではない)で、ひとつをTreeView、他のウィンドウをスクロール
対応にしています。
困ったことにTreeViewはどのウィンドウ上にマウスがあっても、マウスホイールで勝手
にスクロールしてしまいます。
スクロールを実装した別ウィンドウにマウスがある時は、そちらのウィンドウをスクロー
ルさせたいのですが、WM_MOUSEWHEELメッセージが親ウィンドウのプロシージャに届か
ず、うまく行きません。
やむなく
TranslateMassage()前にWM_MOUSEWHEELを捕まえて強制的に動かしたいウィンドウのスク
ロールに成功したのですが、今度はフォーカスをTreeView側に移してもTreeView側のスク
ロールが実行されません。

どうすれば意図するウィンドウのスクロールを有効にすることができるのでしょうか?
コモンコントロールのTreeViewではマウスホイール処理がどのように実装されているので
しょうか?

開発はVC++(VC2003)のSDKでプログラムしています。

MistyGreen 2010/08/05(木) 08:17:11
ヘルプによると、以下のような記載がありますね。

WM_MOUSEWHEELは Windows によって、フォーカスのあるコントロールまたは
子ウィンドウに自動的に転送されます。Win32 の関数 DefWindowProc は、
このメッセージを処理するウィンドウに到達するまで、親チェインを
さかのぼってメッセージを転送します。

つまり、何らかのウィンドウでWM_MOUSEWHEELに対してTRUEを返さない限り
処理が終わらない、という意味だと思います。

> ひとつをTreeView、他のウィンドウをスクロール対応にしています。

TreeView以外のスクロール対応のウィンドウのウィンドウプロシージャでは
WM_MOUSEWHEELのイベントハンドラを実装していますか?
また、その処理から戻る時にTRUEを返していますか?

TRUEを返せば、親チェーンをさかのぼることもなくなるような気がします。

> どうすれば意図するウィンドウのスクロールを有効にすることができるのでしょう
か?

「意図するウィンドウ」を判定する条件がはっきりしているのであれば、
TreeViewをサブクラス化して条件外の場合にはWM_MOUSEWHEELをスルーする、
または意図するウィンドウにパスする、という方法もあるかもしれませんね。

今、これを書いていて思い出しました。
HTML ヘルプなどでも左側のTreeViewにフォーカスがある状態で、右側の
コンテンツペイン内でホイールを回転させると、TreeViewの方がスクロール
してしまいますよね。これと同じ現象でしょうか?

このような場合には、やはりTreeViewをサブクラス化してマウスポインタの
座標によって、目的のウィンドウにメッセージをパスするような方法が有効な気がしま
す。

もっと、スマートな方法があるかもしれませんが・・・

仲澤@失業者 2010/08/05(木) 11:44:04
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

仲澤@失業者 2010/08/05(木) 11:55:08
連投失礼m(__)m
先の方法だと、自分以外のWindowにも効果が出てしまいますね。
気に入らなかったら自分のHWNDに限定してください。

マコ [E-Mail] 2010/08/05(木) 12:13:41
MistyGreenさん、仲澤@失業者さん、親切なご回答ありがとうございます。

MistyGreenさん
>今、これを書いていて思い出しました。
>HTML ヘルプなどでも左側のTreeViewにフォーカスがある状態で、右側の
>コンテンツペイン内でホイールを回転させると、TreeViewの方がスクロール
>してしまいますよね。これと同じ現象でしょうか?
⇒まさにその現象です。

>TreeView以外のスクロール対応のウィンドウのウィンドウプロシージャでは
>WM_MOUSEWHEELのイベントハンドラを実装していますか?
>また、その処理から戻る時にTRUEを返していますか?
⇒TRUEを返していますが、TreeView側には処理が戻りません。

仲澤@失業者さん
私はMFCは使っておりませんが、仲澤@失業者さんのサンプルのようにTranslateMessage
()手前でWM_MOUSEWHEELを捕まえて期待するウィンドウのスクロールを行っています。
でもTreeView側では新たなスクロール処理を実装せずに済ませたいと思っています。
TreeView側のスクロール処理も新たに実装する必要がありますか?
単純にTRUEを返せば、TreeView側に既に実装されている処理がなされるのでしょうか?
極力新たな実装をせずにスマートに行きたいのですが。。

すいません今、手元にソースがないので、週末にでももう一度トライしてみたいと
思います。

お二人さん
まずは、ありがとうございました。

仲澤@失業者 2010/08/05(木) 13:00:39
>TreeView側のスクロール処理も新たに実装する必要がありますか?

まったく必要ありません。

「連投」の方にも書きましたが、ホイールメッセージを常識的な手順で
処理をしている限り、自分のコードを実装すれば、自分の配下、
また他のウインドウの配下の、マウスカーソル直下の、全てのコントロール
(コンボボックスを除く)にWM_MOUSEWHEELが届きます。

尚、マウス位置はいじっていないので、デスクトップ座標のままになっています。
これは、ほとんどのコントロールがデスクトップ座標のままで、正しく反応する
ことを確かめた結果の仕様です。

MistyGreen 2010/08/05(木) 15:27:02
仲澤さん、フォローありがとうございました。

PreTranslateMessage()で処理した方が断然スマートですね。
私も仲澤さんのコードをストックさせて頂きます。(^_^;

今後ともよろしくお願い致します。

マコ 2010/08/05(木) 23:16:20
[[解決]]
半信半疑でPreTranslate_MouseWheel( &msg )を実装してみました。
え!!
あっさり動きました^^/
こういう時は、
::GetAssensor()でルートウィンドウのハンドルを取得するのですね(フムフム)

仲澤@失業者さん、MistyGreenさん
ありがとうございましたm(_ _)m

古いスレッドには追加発言できません。