views:

280

answers:

2

I have a form in VB.Net with Autoscroll enabled and several ComboBoxes within it. When I click on a ComboBox, I can use the mouse wheel to scroll through options, but I cannot deselect the ComboBox (by clicking off it on a blank portion of the parent form) in order to return to scrolling the parent form. This makes navigation in the form annoying, as I instinctively click off the control and flip the scroll wheel, causing the ComboBox to change selections instead of moving the parent form.

Is there a way to make this work in the intuitive way I expect it to, or do I have some conceptual confusion that makes this the wrong question to ask?

+1  A: 

Windows sends the WM_MOUSESCROLL message to the control with the focus. If the control doesn't use it then it gets sent to the parent of the control. Eventually that will be your form which then uses it to scroll itself.

ComboBox is however quite happy to use the message itself. When the dropdown is shown, it scrolls the dropdown list. It also captures the mouse so that it can see you clicking outside of the list. Which closes the list and keeps the focus on the ComboBox. Which then uses the message to scroll the items through the text box portion. In other words, when the CB has the focus, you'll never get the scroll the form.

You can mess with this by using IMessageFilter. My code in this thread shows an example. You'll have to doctor it, the code was made to do something else. You can replace the m.HWnd field with the form's Handle property value for example so all mouse scroll messages go to the form instead. Or you can use the code as-is, it is perhaps more intuitive.

Be careful with this, your user might well be fatally confused by this non-standard behavior. Maybe she really wants to scroll the combo.

Here is the code to make it work. Put it in your app's main form, it is active for all other forms that your app shows:

public partial class Form1 : Form, IMessageFilter {
    public Form1() {
        InitializeComponent();
        Application.AddMessageFilter(this);
        this.FormClosed += (o, e) => Application.RemoveMessageFilter(this);
    }

    public bool PreFilterMessage(ref Message m) {
        if (m.Msg == 0x20a) {  // Trap WM_MOUSEWHEEL
            var ctl = Control.FromHandle(m.HWnd);
            if (ctl == null || !(ctl is ComboBox)) ctl = Control.FromHandle(GetParent(m.HWnd));
            // Redirect when message destined for text box of a ComboBox
            if (ctl != null && ctl is ComboBox && !(ctl as ComboBox).DroppedDown) {
                PostMessage(ctl.Parent.Handle, m.Msg, m.WParam, m.LParam);
                return true;
            }
        }
        return false;
    }

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern IntPtr PostMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern IntPtr GetParent(IntPtr hWnd);
}
Hans Passant
It isn't that the wheel scrolls the combobox that I have a problem with; it's the apparent difficulty of getting it to *stop* scrolling the combobox once the user has chosen what she wants in the combobox.
Orborde
A: 

One solution is to capture the selectedIndexChanged event, and switch focus to the form ( form1.Focus() I believe it is).

This will half-solve your problem by switching focus back to the form and thus scroll wheel operation too, when the user selects a value. However if the user clicks the combo-box and then decides not to change the value, then it will not be de-selected.

You could put the same Focus code into your form Click event too, but it won't work if they click a control such as a label.

SLC