Question

The WinForms GroupBox control doesn't support MouseMove (or at least, not consistently), and I don't understand why.

Since it descends from Control, it does have a MouseMove event, but GroupBox explicitly reintroduces it with Browsable(false), so it's not shown in the Property Grid. You can hook the MouseMove event at runtime, and sometimes it works -- as long as FlatStyle is left at Standard. If the GroupBox's FlatStyle is set to System, then no MouseMove events are fired at all.

Reflector hasn't given me any clues. The GroupBox constructor doesn't seem to be setting any strange control styles, and GroupBox doesn't do anything silly like override MouseMove and fail to call base.

This also appears to be a WinForms-specific limitation, because Delphi group boxes support OnMouseMove just fine. Correction: the comparison to Delphi isn't valid. Delphi group boxes aren't actually standard BM_GROUPBOX controls; they're just painted to look like group boxes, without actually inheriting strange groupbox behaviors like this. So this may well be a limitation of the Windows groupbox control, though I haven't seen it documented anywhere.

Why does the WinForms GroupBox not support MouseMove?

Was it helpful?

Solution

According to this thread, a standard Windows groupbox (i.e., a BUTTON control with BS_GROUPBOX style) appears to return HTTRANSPARENT in response to WM_NCHITTEST. Since the control claims to be transparent, Windows sends the mouse-move events to its parent window instead.

The thread confirms that, if you handle WM_NCHITTEST yourself and return HTCLIENT, then the groupbox will get mouse-move events. They're using MFC but it probably works for WinForms as well.

What's not clear is why Windows returns HTTRANSPARENT by default, but at least the problem has been independently confirmed.

OTHER TIPS

You can see this with Reflector, the key property is CreateParams and the internal OwnerDraw property. GroupBox normally operates with OwnerDraw = true, except when you set FlatStyle = System. Then you get an old-fashioned Windows group box, a window whose class name is BUTTON with the BS_GROUPBOX style bit turned on.

If you take a look with Spy++, you'll see that the control doesn't get any mouse messages at all. Not sure why this is so, the SDK docs don't mention it. I'd guess this dates back to Windows 2.x where every cycle counted. But it does explain why the MouseMove event is hidden, it cannot fire when the System style is selected. Same for Click and Up/Down. The FlatStyle property setter really nails it down by also turning off the Control.UserMouse control style.

Anyhoo, if you want mouse messages, you'll need to avoid the System style.

A group box is a static control that houses other controls inside it. It is designed purely to "group" things together to make the User Interface intuitive if laid out correctly. Hence there is very little events that you can use on behalf of the GroupBox.

You may be able to create a new class that is inherited from GroupBox and subclass it to intercept mouse move event. There is a very useful class that I have used before and it is really easy to perform the subclassing and to trigger the event for MouseMove.

Have a look at this here to see how the subclassing would work...Ok, it is written in VB.NET, but it is really easy to translate it into C# if you so wish, the code I would imagine would look like this: Note: This code I have included is top of my head so there might be an error in this...but that's the gist of it.

Edit: In response to Joe White's comment, I have included the revised code and it does send WM_MOUSEMOVE...look at the steps below on how I reproduced this under VS 2008 Pro.

    public class MyGroupBox : System.Windows.Forms.GroupBox
    {
        private SubClass sc;
        private const int WM_MOUSEMOVE = 0x200;
        public delegate void MyMouseMoveEventHandler(object sender, System.EventArgs e);
        public event MyMouseMoveEventHandler MyMouseMove;
        public MyGroupBox()
            : base()
        {
            sc = new SubClass(this.Handle, true);
            sc.SubClassedWndProc += new SubClass.SubClassWndProcEventHandler(sc_SubClassedWndProc);
        }

        protected override void Dispose(bool disposing)
        {
            if (sc.SubClassed)
            {
                sc.SubClassedWndProc -= new SubClass.SubClassWndProcEventHandler(sc_SubClassedWndProc);
                sc.SubClassed = false;
            }
            base.Dispose(disposing);
        }
        private void OnMyMouseMove()
        {
            if (this.MyMouseMove != null) this.MyMouseMove(this, System.EventArgs.Empty);
        }
        void sc_SubClassedWndProc(ref Message m)
        {
            if (m.Msg == WM_MOUSEMOVE) this.OnMyMouseMove();
        }

    }

    #region SubClass Classing Handler Class
    public class SubClass : System.Windows.Forms.NativeWindow
    {
        public delegate void
          SubClassWndProcEventHandler(ref System.Windows.Forms.Message m);
        public event SubClassWndProcEventHandler SubClassedWndProc;
        private bool IsSubClassed = false;

        public SubClass(IntPtr Handle, bool _SubClass)
        {
            base.AssignHandle(Handle);
            this.IsSubClassed = _SubClass;
        }

        public bool SubClassed
        {
            get { return this.IsSubClassed; }
            set { this.IsSubClassed = value; }
        }

        protected override void WndProc(ref Message m)
        {
            if (this.IsSubClassed)
            {
                OnSubClassedWndProc(ref m);
            }
            base.WndProc(ref m);
        }

        #region HiWord Message Cracker
        public int HiWord(int Number)
        {
            return ((Number >> 16) & 0xffff);
        }
        #endregion

        #region LoWord Message Cracker
        public int LoWord(int Number)
        {
            return (Number & 0xffff);
        }
        #endregion

        #region MakeLong Message Cracker
        public int MakeLong(int LoWord, int HiWord)
        {
            return (HiWord << 16) | (LoWord & 0xffff);
        }
        #endregion

        #region MakeLParam Message Cracker
        public IntPtr MakeLParam(int LoWord, int HiWord)
        {
            return (IntPtr)((HiWord << 16) | (LoWord & 0xffff));
        }
        #endregion

        private void OnSubClassedWndProc(ref Message m)
        {
            if (SubClassedWndProc != null)
            {
                this.SubClassedWndProc(ref m);
            }
        }
    }
    #endregion

  1. Create a simple blank form.
  2. Drag a group box from the tools palette and drop it into the form, default name would be groupBox1
  3. In your Form's designer code, change the code reference by doing this:
    System.Windows.Forms.GroupBox groupBox1;
    to
    WindowsApplication.MyGroupBox groupBox1;
  4. Within the InitializeComponent() method, change the instantiation of the GroupBox to this:
    this.groupBox1 = new WindowsApplication.MyGroupBox();
  5. Save and just compile it.
  6. Go back into your designer window and click on the group box, look for the MyMouseMove event within the properties toolbox, and wire it up.
  7. Your event handler should look something like this:
        private void groupBox1_MyMouseMove(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("MyMouseMove!");
        }

Run the application and everytime you move the mouse inside the groupbox you will see the output 'MyMouseMove!'.

Hope this gives you the hint, Best regards, Tom.

I've noticed that a lot of the events for certain controls aren't accessible via the events tab (under Properties) in VS. You can just assign an event handler manually in the parent form's Designer under InitializeComponents():

this.groupBox1.MouseMove += new MouseEventHandler(this.groupBox1_MouseMove);

This will trigger the following method:

private void groupBox1_MouseMove(object sender, MouseEventArgs e)
{
    //doodle the stuff you want to happen after the trigger here
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top