【问题标题】:How to deal with invalidly painted ComboBox control in win32 winapi?win32 winapi中如何处理无效绘制的ComboBox控件?
【发布时间】: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


【解决方案1】:

您可以通过调用ComboBox_SetEditSel(context-&gt;_combo_box2, -1, -1);(参见CB_SETEDITSEL)来解决此问题,或者在调整大小之前在组合框的编辑控件中找到选定的字符,然后在调整大小后恢复这些值。

static void on_size(HWND hwnd, UINT, int, int) noexcept
{
    auto context=reinterpret_cast<window_context*>(GetWindowLongPtr(hwnd,GWLP_USERDATA));

    DWORD range = ComboBox_GetEditSel(context->_combo_box2);
    DWORD start = LOWORD(range);
    DWORD end = HIWORD(range);

    MoveWindow(context->_combo_box2, 10, 10, 150, 100, true);
    MoveWindow(context->_ok_button, 10, 110, 100, 50, true);

    ComboBox_SetEditSel(context->_combo_box2, start, end);
}

但是,您可能会发现在使用其他控件时会遇到类似问题,但很难找到解决所有问题的方法。考虑改用DialogBox

【讨论】:

  • 哦,非常感谢!这样做。您是否知道您是否可以在没有这些资源模板的情况下创建对话框?
  • 没有资源模板的对话非常困难。有一个例子here。你有什么理由不能使用资源模板?
  • 遗憾的是,应用程序需要支持 DPI 并与 gcc 兼容,对于我不知道如何实现的对话框。此外,我发现创建内存模板很乏味,但我想如果出现更多类似此弹出窗口的问题,我将别无选择。
  • 对话框是 DPI 感知的。您可以将资源对话框与 gcc/MinGW 一起使用。
猜你喜欢
  • 2010-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-29
  • 2020-12-14
  • 2012-11-25
  • 2013-01-08
  • 1970-01-01
相关资源
最近更新 更多