VZ.
VZ.

Reputation: 22678

How to put an EDIT control in a custom popup window using Win32 API?

I'm trying to add an EDIT control to a window used as a dropdown for a custom combobox-like control. Initially this dropdown window was implemented as a child (WS_CHILD) window of the desktop, which is similar to the "ComboLbox" window used by the real combobox. This worked just fine, however an EDIT window seems to just refuse to accept focus when it is put into such dropdown window. I.e. it is enabled and reacts to right mouse clicks, for example, but clicking on it or calling SetFocus() fails (the latter sets last error to ERROR_INVALID_PARAMETER).

Because of this, and also because of the way custom popup windows are implemented in many examples including Raymond Chen's fakemenu sample, I've changed the dropdown implementation to use WS_POPUP, with the main application window as owner. This has a known problem with stealing activation from the owner window when the popup is shown, however this can be addressed by returning MA_NOACTIVATE from WM_MOUSEACTIVATE handler for the popup window and it indeed works well initially, i.e. the owner window keeps activation when the popup shows up. But as soon as I click the EDIT control inside the popup, it calls, from its default window proc, SetFocus() to set the focus to itself, which deactivates the parent window.

My question is how can I prevent this from happening? I know that it can be done because WinForms ToolStripManager manages to allow editing text in a dropdown without deactivating the parent window and it also uses WS_POPUP style for the popup window. But how does it do it?

Upvotes: 3

Views: 1150

Answers (1)

Barmak Shemirani
Barmak Shemirani

Reputation: 31599

A solution was suggested in comments "prevent the host window from visibly appearing inactive by handling WM_NCACTIVATE" This should work as shown in the example below.

When menu window is opened, the host window (HostProc) will receive WM_NCACTIVATE message. Host will look for "menuclass", if menu class is found then host will return DefWindowProc(hwnd, WM_NCACTIVATE, TRUE, lparam); to prevent the title bar for host window get painted inactive.

You also need to handle WM_NCACTIVATE in fake menu window. When menu window goes out of focus, WM_NCACTIVATE is received by MenuProc, at this point the menu can close itself.

#include <windows.h>

const wchar_t* menuclass = L"menuclass";

LRESULT CALLBACK MenuProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch(msg)
    {
    case WM_CREATE:
        CreateWindow(L"Edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 10, 160, 30,
            hwnd, NULL, NULL, NULL);
        break;

    case WM_NCACTIVATE:
    {
        if(!wparam)
        {
            //close the menu if its losing focus
            PostMessage(hwnd, WM_CLOSE, 0, 0);

            //tell parent to paint inactive, if user clicked on a different program
            POINT pt;
            GetCursorPos(&pt);
            HWND hit = WindowFromPoint(pt);
            HWND hparent = GetParent(hwnd);
            if(hit != hparent && !IsChild(hparent, hit))
                DefWindowProc(hparent, WM_NCACTIVATE, FALSE, 0);
        }
        break;
    }

    case WM_LBUTTONDOWN:
        PostMessage(hwnd, WM_CLOSE, 0, 0);
        break;
    //also handles other mouse/key messages associated with a menu...
    }

    return DefWindowProc(hwnd, msg, wparam, lparam);
}

LRESULT CALLBACK HostProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch(msg)
    {
    case WM_NCACTIVATE:
        //paint the window as active when custom menu starts
        if(!wparam && FindWindow(menuclass, NULL))
            return DefWindowProc(hwnd, WM_NCACTIVATE, TRUE, lparam);
        break;
    case WM_RBUTTONUP:
    {
        //show the custom menu
        POINT pt;
        GetCursorPos(&pt);
        CreateWindow(menuclass, NULL, WS_VISIBLE | WS_POPUP | WS_BORDER,
            pt.x, pt.y, 200, 400, hwnd, 0, 0, 0);
        return 0;
    }
    case WM_DESTROY: 
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, msg, wparam, lparam);
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int)
{
    WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
    wcex.hInstance = hInstance;
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.lpfnWndProc = HostProc;
    wcex.lpszClassName = L"hostwnd";
    RegisterClassEx(&wcex);

    wcex.lpfnWndProc = MenuProc;
    wcex.lpszClassName = menuclass;
    RegisterClassEx(&wcex);

    CreateWindow(L"hostwnd", L"Right click for menu ...", 
        WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0, 0, 600, 400, 0, 0, hInstance, 0);

    MSG msg;
    while(GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

Upvotes: 2

Related Questions