【发布时间】:2018-04-02 00:25:11
【问题描述】:
我遇到了一个使用 WinAPI 绘制的 ComboBox 无效的问题。当您最小化应用程序并在 ComboBox 控件的选择未被隐藏后恢复时,它看起来像这样:
您可以看到 OK 按钮具有焦点,但 ComboBox 的选择仍未隐藏。当控件失去输入焦点时,ComboBox 的正常行为会隐藏所选内容。
代码:
#define WIN32_MEAN_AND_LEAN
#include <SDKDDKVer.h>
#include <Windows.h>
#include <Windowsx.h>
#include <CommCtrl.h>
#include <assert.h>
struct window_context {
HINSTANCE _instance;
HWND _window;
HWND _combo_box2;
HWND _ok_button;
window_context(HINSTANCE instance) noexcept : _instance{ instance }
{
}
};
static BOOL on_create(HWND hwnd, LPCREATESTRUCT lpCreateStruct) noexcept
{
auto context = reinterpret_cast<window_context*>(lpCreateStruct->lpCreateParams);
context->_combo_box2 = CreateWindowW(WC_COMBOBOXW,
L"",
CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_TABSTOP | WS_VSCROLL | WS_VISIBLE | WS_CHILD,
0, 0, 0, 0,
hwnd,
(HMENU)44,
nullptr,
nullptr);
ComboBox_AddString(context->_combo_box2, L"select me");
context->_ok_button = CreateWindowW(WC_BUTTONW,
L"OK",
BS_DEFPUSHBUTTON | BS_NOTIFY | WS_TABSTOP | WS_VISIBLE | WS_CHILD,
0, 0, 0, 0,
hwnd,
nullptr,
nullptr,
nullptr);
return true;
}
static void on_destroy(HWND hwnd) noexcept
{
PostQuitMessage(0);
}
static void on_size(HWND hwnd, UINT state, int cx, int cy) noexcept
{
auto context = reinterpret_cast<window_context*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
MoveWindow(context->_combo_box2,
10,
10,
100,
100,
true); // causes the weird behaviour to occur
// using SetWindowPos doesn't help
// using SetWindowPos with SWP_NOSIZE prevents the bug from occuring
// but also locks it into size which is not acceptable
// since I use this to deal with DPI scaling changes
MoveWindow(context->_ok_button,
10,
110,
100,
50,
true);
}
static void on_activate(HWND hwnd, UINT state, HWND hwndActDeact, BOOL fMinimized) noexcept
{
auto context = reinterpret_cast<window_context*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
if (state)
SetFocus(context->_ok_button);
}
static BOOL on_nc_create(HWND hwnd, LPCREATESTRUCT lpCreateStruct) noexcept
{
auto context = reinterpret_cast<window_context*>(lpCreateStruct->lpCreateParams);
context->_window = hwnd;
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(context));
return FORWARD_WM_NCCREATE(hwnd, lpCreateStruct, DefWindowProcW);
}
static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept
{
switch (uMsg) {
HANDLE_MSG(hwnd, WM_CREATE, on_create);
HANDLE_MSG(hwnd, WM_DESTROY, on_destroy);
HANDLE_MSG(hwnd, WM_SIZE, on_size);
HANDLE_MSG(hwnd, WM_ACTIVATE, on_activate);
HANDLE_MSG(hwnd, WM_NCCREATE, on_nc_create);
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
static bool register_class(HINSTANCE hInstance) noexcept
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(wcex);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = &wnd_proc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = nullptr;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = L"test";
wcex.hIconSm = nullptr;
return RegisterClassExW(&wcex) != 0;
}
static bool create_window(window_context& context) noexcept
{
return CreateWindowExW(0,
L"test",
L"Win32 Program",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
800,
600,
nullptr,
nullptr,
context._instance,
&context);
}
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
if (!register_class(hInstance))
return false;
window_context context{ hInstance };
if (!create_window(context))
return false;
ShowWindow(context._window, nCmdShow);
MSG msg;
BOOL got_message;
while ((got_message = GetMessageW(&msg, nullptr, 0, 0)) && got_message != -1) {
if (IsDialogMessageW(context._window, &msg))
continue;
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return static_cast<int>(msg.wParam);
}
重现问题的步骤:
- 从组合框中选择
select me - 最小化窗口
- 恢复窗口
ComboBox 现在应该显示为选中状态,即使它不是。
解决问题的步骤:
- 使 ComboBox 的区域无效无济于事,重绘组合框仍无法解决问题
- 仅在指定位置创建 ComboBox 并且从不移动其位置或更改其大小时,不会出现此问题
- 使用
SetWindowPos移动 ComboBox 的位置不会出现此问题 - 只要更改 ComboBox 的大小,就会出现这种奇怪的行为。
- 对话框以某种方式正确处理此问题
- WinForms 也能正确处理此问题,但我未能找到他们如何防止它发生
- 该错误仅在您从下拉列表中选择一个项目时出现,当您输入自定义文本时该错误消失
当然,这是一个非常奇特的问题,我非常感谢您的帮助。
【问题讨论】:
-
这不只是表明该按钮是默认按钮吗?如果您按下空格键,是否会调用按钮处理程序,还是必须按 Enter 键?
-
按钮完全没问题,它按照默认按钮样式绘制。是的,处理程序会被调用,尽管在这个例子中我省略了它们,因为它们对这一点没有贡献。 Enter 导致 IDOK 被发送到 WM_COMMAND 并且空间将按下聚焦按钮,但我的问题是组合框的绘画,即使它不是选中的:(当我遍历控件时它得到修复,但是的,从来没有第一个时间。
-
@Sid:是的,粗蓝色轮廓表示该按钮是默认按钮。但是 XOR 样式的焦点矩形表明它也是焦点控件(所以是的,空格键也会激活它)。
-
为什么你认为你需要覆盖
WM_ACTIVATE?让 Windows 负责恢复焦点。为此,您应该适当地继承对话窗口类(WC_DIALOG或“#32770”),其默认WM_ACTIVATE应该做正确的事情。最后,在对话框中使用SetFocus()几乎总是错误的,因为它不关心默认按钮状态。请改用WM_NEXTDLGCTL。
标签: c++ windows winapi combobox