none
マウスのホイールをコロコロした時にマウスオーバー(エンター?)しているコントロールのスクロールバーを動かしたい

    質問

  • こんにちは

    C#&Winformsでアプリを作っています。

    フォーム上にTreeView、DataGridView、TextBox(複数行)があります。

    各コントロールは表示されるコンテンツ量によってスクロールバーが表示されます。

    このフォーム上でマウスのホイールをコロコロした時に、フォーカスがあるコントロールではなくマウスオーバー(エンター?)しているコントロールのスクロールが動作するようにしたいと思っていますが良い方法が見つかりません。

    もしこのようなニーズに対応するための一般的な方法などあれば教えて下さい。

    各コントロールのMouseEnterでフォーカスを自分自身に設定する方法で対応を考えましたが、フォーカス自体が移動してしまうことに不都合がありこの方法は却下となりました。

    というのは、フォーム上には上記コントロール以外にもう一つTextBox(一行)が存在しています。

    このTextBox(一行)にフォーカスがある状態(カーソルがある状態)でTreeView、DataGridView、TextBox(複数行)にマウスオーバーするとそれらのコントロールにフォーカスが移動してしまいTextBox(一行)からフォーカスが外れ(カーソルが消え)てしまいます。

    その結果キーボードで文字入力を行ってもTextBox(一行)に文字が入力されなくなってしまうのです。

    このTextBox(一行)に文字を入力するためにはマウスでクリックした後、TreeView、DataGridView、TextBox(複数行)にマウスオーバーしないように注意しなければいけないというストレスをユーザーに与えることになってしまうため対応方法としては適切ではないと判断しました。

    またWin32 APIのSetScrollInfo・GetScrollInfoの利用も検討したのですが、TreeViewに対してSetScrollInfoでスクロールを移動してあげるとスクロールバーのみが移動してTreeView内のコンテンツがそれにあわせてスクロールしないという現象が起きています。

    DataGridViewに関してはSetScrollInfoが効きませんでした。

    そのためSetScrollInfo・GetScrollInfoについてもまだ方法が確立できていない状況です。

    もし同じ対応をされたことがある方がいらっしゃいましたら是非具体的な方法を教えていただけたらと思います。

    よろしくお願いします。

    2012年5月7日 6:44

回答

すべての返信

  • 試していませんが、以下のページにあるNativeWindowを継承したバージョンのクラスで、スマートに実現できるのではないかと思います。

    How to forward messages (eg. mouse wheel) to another Control without stealing focus and without P/Invoke?
    http://stackoverflow.com/questions/6036918/how-to-forward-messages-eg-mouse-wheel-to-another-control-without-stealing-fo


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2012年5月7日 7:51
    モデレータ
  • trapemiyaさん

    情報をありがとうございました!

    教えていただいたURLを参考にやりたいことが実現できました。

    参考までに対応方法を載せておきます。

    下記MessageForwarderクラスを作成

    using System;
    using System.Collections.Generic;
    using System.Windows.Forms;
    
    namespace hogehoge
    {
        public class MessageForwarder : NativeWindow, IMessageFilter
        {
            private Control _Control;
            private Control _PreviousParent;
            private HashSet<int> _Messages;
            private bool _IsMouseOverControl;
    
            public MessageForwarder(Control control, int message) 
                : this(control, new int[] { message })
            { 
            }
            public MessageForwarder(Control control, IEnumerable<int> messages)
            {
                _Control = control;
                AssignHandle(control.Handle);
                _Messages = new HashSet<int>(messages);
                _PreviousParent = control.Parent;
                _IsMouseOverControl = false;
    
                control.ParentChanged += new EventHandler(control_ParentChanged);
                control.MouseEnter += new EventHandler(control_MouseEnter);
                control.MouseLeave += new EventHandler(control_MouseLeave);
                control.Leave += new EventHandler(control_Leave);
    
                if (control.Parent != null)
                {
                    Application.AddMessageFilter(this);
                }
            }
    
            public bool PreFilterMessage(ref Message m)
            {
                if (_Messages.Contains(m.Msg) && 
                    _Control.CanFocus && 
                    !_Control.Focused && 
                    _IsMouseOverControl)
                {
                    m.HWnd = _Control.Handle;
                    WndProc(ref m);
                    return true;
                }
    
                return false;
            }
    
            void control_ParentChanged(object sender, EventArgs e)
            {
                if (_Control.Parent == null)
                {
                    Application.RemoveMessageFilter(this);
                }
                else
                {
                    if (_PreviousParent == null)
                    {
                        Application.AddMessageFilter(this);
                    }
                }
                _PreviousParent = _Control.Parent;
            }
    
            void control_MouseEnter(object sender, EventArgs e)
            {
                _IsMouseOverControl = true;
            }
    
            void control_MouseLeave(object sender, EventArgs e)
            {
                _IsMouseOverControl = false;
            }
    
            void control_Leave(object sender, EventArgs e)
            {
                _IsMouseOverControl = false;
            }
        }
    }
    

    後はコンストラクタ内などで下記のように対象のコントロールを渡すようにMessageForwarderをnewしてあげればOKです。

            /// <summary>
            /// コンストラクタ
            /// </summary>
            public form1()
            {
                InitializeComponent();
    
                new MessageForwarder(treeview1, 0x20A);
                new MessageForwarder(datagridview1, 0x20A);
                new MessageForwarder(textbox1, 0x20A);
            }
    

    上記のようにした場合、「treeview1」「datagridview1」「textbox1」にマウスオーバーしてホイールをコロコロするとそれぞれのコントロールがスクロールするようになります。

    大変勉強になりました。

    ありがとうございました。

    2012年5月8日 1:22