【问题标题】:WS_TABSTOP in winapi on edit controls of child windowswinapi中的WS_TABSTOP关于子窗口的编辑控件
【发布时间】:2021-04-03 17:20:23
【问题描述】:

在我的 WinAPI 应用程序中,我在子窗口中有一系列编辑控件。我希望用户能够通过按 tab 键前进和 shift-tab 后退来在它们之间移动,但我似乎无法弄清楚如何将WS_TABSTOP 与子窗口一起使用。我打算发生的是,当用户单击 tab 键时,会选择后续的编辑控件。但是,当我单击以下代码窗口中的选项卡时,光标就会消失。

这是一个最小的可重现示例:

    //libraries
#pragma comment ("lib", "Comctl32.lib")
#pragma comment ("lib", "d2d1.lib")


#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
#include <CommCtrl.h>
// C RunTime Header Files

#include <vector>
#include <string>


#define IDS_APP_TITLE           103
#define IDI_PRACTICE            107
#define IDI_SMALL               108
#define IDC_PRACTICE            109

#define MAX_LOADSTRING          100

// Global Variables:
HINSTANCE hInst;                                // current instance
WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK WndProcChild(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

ATOM MyRegisterClass(HINSTANCE hInstance);

HWND childHWND;

HWND InitInstance(HINSTANCE hInstance, int nCmdShow);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    // Initialize global strings
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_PRACTICE, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

    // Perform application initialization:
    HWND hWnd = InitInstance(hInstance, nCmdShow);
    if(!hWnd)
    {
        return FALSE;
    }
    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_PRACTICE));
    MSG msg;
    // Main message loop:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            if (!IsDialogMessage(hWnd, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
    }
    return (int)msg.wParam;
}


LRESULT CALLBACK WndProcChild(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE:
    {
        HWND edit1 = CreateWindow(WC_EDIT, L"", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP, 100, 100, 100, 100, hWnd, (HMENU)1, hInst, NULL);
        HWND edit2 = CreateWindow(WC_EDIT, L"", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP, 300, 100, 100, 100, hWnd, (HMENU)2, hInst, NULL);
        break;
    }
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;

}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PRACTICE));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(GetStockObject(WHITE_BRUSH));
    wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_PRACTICE);
    wcex.lpszClassName = L"Parent";
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));


    //Child wnd class
    WNDCLASSEXW wcexChild;
    wcexChild.cbSize = sizeof(WNDCLASSEX);
    wcexChild.style = CS_HREDRAW | CS_VREDRAW;
    wcexChild.lpfnWndProc = WndProcChild;
    wcexChild.cbClsExtra = 0;
    wcexChild.cbWndExtra = 0;
    wcexChild.hInstance = hInstance;
    wcexChild.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PRACTICE));
    wcexChild.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcexChild.hbrBackground = (HBRUSH)(GetStockObject(WHITE_BRUSH));
    wcexChild.lpszMenuName = MAKEINTRESOURCEW(IDC_PRACTICE);
    wcexChild.lpszClassName = L"Child";
    wcexChild.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    return RegisterClassExW(&wcexChild) && RegisterClassExW(&wcex);
}


HWND InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance; // Store instance handle in our global variable
    HWND hWnd = CreateWindowW(L"Parent", L"PARENT", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hInstance, nullptr);

    childHWND = CreateWindowW(L"Child", L"", WS_CHILD | WS_VISIBLE | WS_EX_CONTROLPARENT,
        0, 0, 700, 700, hWnd, nullptr, hInstance, nullptr);

    if (!hWnd)
    {
        return NULL;
    }
    ShowWindow(childHWND, nCmdShow);
    UpdateWindow(childHWND);

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    return hWnd;
}

【问题讨论】:

标签: c++ windows winapi tabstop


【解决方案1】:

这里的问题是 if (!IsDialogMessage(hWnd, &amp;msg)) 在错误的窗口上被调用。

if (!IsDialogMessage(childHWND, &amp;msg)) 替换该行可以使TAB 导航起作用。

来自IsDialogMessage 文档:

虽然 IsDialogMessage 函数适用于无模式对话框, 您可以将它用于任何包含控件的窗口, 启用窗口提供与对话框中使用的相同的键盘选择。

在贴出的代码中,“包含控件的窗口”是编辑控件的直接父级,即childHWND


[ EDIT ] cmets 中指出的另一个问题(感谢 @IInspectable)是扩展样式 WS_EX_CONTROLPARENT 错误地与样式标志一起传递,而不是 extended 样式标志。为了解决这个问题,对childHWND = CreateWindowW(L"Child", L"", WS_CHILD | WS_VISIBLE | WS_EX_CONTROLPARENT, ... 的调用应该改为childHWND = CreateWindowExW(WS_EX_CONTROLPARENT, L"Child", L"", WS_CHILD | WS_VISIBLE, ...

【讨论】:

  • 谢谢,如果我有多个子窗口,每个子窗口都包含我想使用的控件WS_TABSTOP,我是否只需要在每个子窗口上调用IsDialogMessage
  • @JuanitaLopez 您可以为每个人致电IsDialogMessage。但是只有 一个 子窗口可以在任何给定时间获得输入焦点,因此更经济的方法是跟踪哪个是活动的,然后只为那个调用IsDialogMessage。例如,请参阅 this page 上的“IsDialogMessage 和多个无模式对话框”和/或(现已停用)文章 How To Use One IsDialogMessage() Call for Many Modeless Dialogs
  • 其实这不是必须的。指定 WS_EX_CONTROLPARENT extended window style 就足够了。 OP 正在指定该样式,但参数错误(dwStyle 而不是dwExStyle)。正确使用CreateWindowEx 可以解决此问题。
  • @IInspectable 没错,当然。我添加了一个注释,然而,在这种情况下,只要 IsDialogMessage 被正确的 HWND 调用,即使没有 any WS_EX_CONTROLPARENT,TAB'ing 也可以工作。猜测扩展样式对于必须参与相同 TAB 序列的嵌套控件肯定是必需的,尽管验证该理论将不得不等待另一天。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-29
  • 1970-01-01
  • 2012-11-25
  • 1970-01-01
  • 1970-01-01
  • 2018-09-02
  • 1970-01-01
相关资源
最近更新 更多