如何使用Win32 API在自定义弹出窗口中放置EDIT控件?

时间:2018-10-13 22:29:46

标签: winapi

我试图将EDIT控件添加到用作自定义组合框控件的下拉菜单的窗口。最初,此下拉窗口是作为桌面的子(WS_CHILD)窗口实现的,类似于真实组合框使用的“ ComboLbox”窗口。这工作得很好,但是当将EDIT窗口放到这样的下拉窗口中时,它似乎只是拒绝接受焦点。即例如,它已启用并响应鼠标右键单击,但是单击它或调用SetFocus()失败(后者将最后一个错误设置为ERROR_INVALID_PARAMETER)。

因此,并且由于在许多示例(包括Raymond Chen的fakemenu sample)中实现了自定义弹出窗口的方式,我将下拉实现更改为使用WS_POPUP,并且具有主应用程序窗口作为所有者。这是一个已知的问题,当显示弹出窗口时会从所有者窗口中窃取激活,但是可以通过从MA_NOACTIVATE处理程序中返回WM_MOUSEACTIVATE来弹出窗口来解决此问题,并且在开始时确实工作良好,即弹出窗口显示时,所有者窗口保持激活状态。但是,一旦我在弹出窗口中单击EDIT控件,它就会从其默认窗口proc调用SetFocus()来将焦点设置为自身,这将停用父窗口。

我的问题是如何防止这种情况发生?我知道可以做到这一点,因为WinForms ToolStripManager设法允许在下拉列表中编辑文本而无需停用父窗口,并且它对弹出窗口也使用WS_POPUP样式。但是它是怎么做到的?

1 个答案:

答案 0 :(得分:2)

评论中提出了一种解决方案“通过处理WM_NCACTIVATE防止主机窗口明显不活动” 此操作应如以下示例所示。

打开菜单窗口时,主窗口(HostProc)将收到WM_NCACTIVATE消息。主机将寻找"menuclass",如果找到菜单类,则主机将返回DefWindowProc(hwnd, WM_NCACTIVATE, TRUE, lparam);以防止主机窗口的标题栏被绘制为非活动状态。

您还需要在伪菜单窗口中处理WM_NCACTIVATE。当菜单窗口失去焦点时,WM_NCACTIVATE收到MenuProc,此时菜单可以自行关闭。

#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;
}