Reputation: 22678
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
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