views:

1512

answers:

3

In C# .NET 2.0, I have a WinForms ComboBox with ComboBoxStyle DropDownList. However, the drop down appears whenever the user clicks anywhere on the combo box. Instead, I'd like to have it only open when the user explicitly clicks on the drop down button. When the user clicks on the rest of the combo box, I'd like to just assign it the keyboard focus so he or she can use some keyboard commands on the selected item. What's the best way to do this?

A: 

You may be able to get the X,Y position of the mouse click, and from there you could force it collapsed if it isn't on the drop down "icon" (for lack of a better word).

MasterMax1313
+1  A: 

You have two issues to consider. The first is rather simple: determine whether the dropdown should be opened or closed. This code can do that:

    void comboBox1_MouseClick(object sender, MouseEventArgs e)
    {
        ComboBox combo = sender as ComboBox;
        int left = combo.Width - (SystemInformation.HorizontalScrollBarThumbWidth + SystemInformation.HorizontalResizeBorderThickness);
        if (e.X >= left)
        {
            // They did click the button, so let it happen.
        }
        else
        {
            // They didn't click the button, so prevent the dropdown.
        }
    }

The second issue is more significant -- actually preventing the dropdown from appearing. The simplest approach is:

comboBox1.DropDownStyle = ComboBoxStyle.DropDown;

But, that allows typing into the box, which you may not want.

I spent about 15 minutes looking at options, and it appears that to prevent the dropdown from appearing and simultaneously prevent the user from typing into the dropdown, you would need to subclass the control. That way, you can override OnMouseClick(), and only call the base.OnMouseClick() when they did click on button. It would look something like this (untested):

public class CustomComboBox : ComboBox
{
    protected override void OnMouseClick(MouseEventArgs e)
    {
        base.OnMouseClick(e);

        int left = this.Width - (SystemInformation.HorizontalScrollBarThumbWidth + SystemInformation.HorizontalResizeBorderThickness);
        if (e.X >= left)
        {
            // They did click the button, so let it happen.
            base.OnMouseClick(e);
        }
        else
        {
            // They didn't click the button, so prevent the dropdown.
            // Just do nothing.
        }
    }
}
John Fisher
The scrollbar width was helpful, but using the WinForms mouse events/functions is not useful. They are called too late.
Eric
The WinForms events and functions are called at the same times as the normal Windows messages. It's the handling of them that matters. Finding a way to prevent the default behavior is the challenge, since the timing is the same.
John Fisher
I was writing metaphorically. Internally, it seems that before WinForms mouse events are ever called, the drop down appears AND the drop down event is sent. So using the WinForms events is not enough to prevent the drop down from appearing.
Eric
+3  A: 

After some help from the other answers, I arrived at this quick solution:

public class MyComboBox : ComboBox
{
    public MyComboBox()
    {
        FlatStyle = FlatStyle.Popup;
        DropDownStyle = ComboBoxStyle.DropDownList;
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == 0x0201 /* WM_LBUTTONDOWN */ || m.Msg == 0x0203 /* WM_LBUTTONDBLCLK */)
        {
            int x = m.LParam.ToInt32() & 0xFFFF;
            if (x >= Width - SystemInformation.VerticalScrollBarWidth)
                base.WndProc(ref m);
            else
            {
                Focus();
                Invalidate();
            }
        }
        else
            base.WndProc(ref m);
    }
}
Eric