Question

I'm trying to implement my own combobox like a lot of folks before me. What I want to accomplish is a combobox that filters and highlights items in the dropdown list while the user is typing in the combo textbox. The behaviour of a regular combobox after you click the arrow button is that the dropdown pops up and the focus stays in the textbox. This way you can start typing right away.

In order to customize the dropdown control you have to implement something from scratch. Most of the implementations that I've come across use either a Form or a ToolStripDropDown to host the custom control. Both are toplevel controls which means that you have to somehow close it yourself if the user clicks somewhere outside the dropdown. ToolStripDropDown does this automatically if AutoClose is true, but also somehow steals the combo textbox the focus on show if it is activated. A Form must be shown using ShowWithoutActivation() in order to prevent it from stealing the focus.

The problem is that the dropdown does never close unless I click somewhere within the dropdown and therefore activate it.

Another twist is that the combobox control is supposted to be hosted in an MFC application instead of a pure WinForms app.

Was it helpful?

Solution

The dropdown never being activated (gaining focus) is the main complication here. Otherwise you could just use the forms Deactivate event to hide it. The way to go here is to add an IMessageFilter to the WinForms application and catch mouse click messages. The message filter then determines whether the click took place outisde of the dropdown and closes it. If you are creating a WinForms application you are done.

Some extra work is necessary if you are for example in a MFC application hosting the control in a MFC window. In that case your IMessageFilter is useless. The reason is that the WinForms Application is never being run and therefore the event pump is never being invoked. Instead the MFC message pump does all the message handling. To solve this issue I've come accross a neat trick to activate the Application message pump in MFC applications.

In MFC applications there is usually an equivalent to the WinForms Application which is CWinApp (or CWinAppEx). The trick is to tap into the PreTranslateMessage method and serve the WinForms Application message pump before (or after) the MFC message pump:

BOOL CWinApp::PreTranslateMessage(MSG* pMsg)
{
    if (FilterWindowsFormsMessages(pMsg))
    {
        return TRUE;
    }

    return CWinApp::PreTranslateMessage(pMsg);
}

BOOL CWinApp::FilterWindowsFormsMessages(MSG* pMsg)
{
    Message message = Message::Create(IntPtr(pMsg->hwnd), 
                                      int(pMsg->message), 
                                      IntPtr((void*)pMsg->wParam), 
                                      IntPtr((void*)pMsg->wParam));

    if (Application::FilterMessage(message))
    {
        return TRUE;
    }

    return FALSE;
}

This way the registered IMessageFilters are being served and everything works fine.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top