Почему WS_TABSTOP не работает с расширенным контролем редактирования?

Изменение фокуса с помощью клавиши табуляции не работает с расширенным элементом управления редактированием, но работает нормально, если расширенное редактирование заменено элементом управления WC_EDIT.

#include <windows.h>
#include <commctrl.h>
#include <richedit.h>

#pragma comment(lib, "comctl32.lib")
#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) 
{
    LoadLibrary(TEXT("msftedit.dll"));

    WNDCLASSEX mainwcex;
    mainwcex.cbSize = sizeof(WNDCLASSEX);
    mainwcex.style = CS_HREDRAW | CS_VREDRAW;
    mainwcex.lpfnWndProc = WindowProc;
    mainwcex.cbClsExtra = 0;
    mainwcex.cbWndExtra = 0;
    mainwcex.hInstance = hInstance;
    mainwcex.hIcon = NULL;
    mainwcex.hCursor = (HICON) LoadCursor(NULL, IDC_ARROW);
    mainwcex.hbrBackground = NULL;
    mainwcex.lpszMenuName = NULL;
    mainwcex.lpszClassName = "mainwindow";
    mainwcex.hIconSm = NULL;

    RegisterClassEx(&mainwcex);

    HWND mainWindow = CreateWindowEx(
        NULL,
        "mainwindow",
        NULL,
        WS_OVERLAPPEDWINDOW,
        100,
        100,
        600,
        400,
        NULL,
        NULL,
        hInstance,
        NULL);

    HWND richEditControl = CreateWindowEx(
        NULL,
        "RICHEDIT50W",  // Works fine if replaced by WC_EDIT.
        "Rich Edit",
        WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP,
        50,
        50,
        100,
        25,
        mainWindow,
        NULL,
        hInstance,
        NULL);

    HWND button1 = CreateWindowEx(
        NULL,
        WC_BUTTON,
        "Button1",
        WS_CHILD | WS_VISIBLE | WS_TABSTOP,
        200,
        50,
        100,
        25,
        mainWindow,
        NULL,
        hInstance,
        NULL);

    HWND button2 = CreateWindowEx(
        NULL,
        WC_BUTTON,
        "Button2",
        WS_CHILD | WS_VISIBLE | WS_TABSTOP,
        350,
        50,
        100,
        25,
        mainWindow,
        NULL,
        hInstance,
        NULL);

    ShowWindow(mainWindow, nCmdShow);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        if (!IsDialogMessage(mainWindow, &msg)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return static_cast<int>(msg.wParam);
}

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg) {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
            break;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

person krsi    schedule 28.05.2018    source источник
comment
Не можете ли вы использовать вкладку вдали от элемента управления расширенным редактированием или не можете использовать вкладку к элементу управления? Если это первое, используйте Spy++, чтобы увидеть, как он реагирует на WM_GETDLGCODE.   -  person Paul Sanders    schedule 28.05.2018
comment
@PaulSanders Я не могу уйти от богатого элемента управления редактированием.   -  person krsi    schedule 28.05.2018
comment
Хорошо, тогда запустите Spy++. Он поставляется с Visual Studio, но, возможно, вы сможете получить его каким-то другим способом. WM_GETDLGCODE задокументировано здесь.   -  person Paul Sanders    schedule 28.05.2018
comment
Я решил эту проблему, подклассировав элемент управления расширенного редактирования для перехвата WM_GETDLGCODE, затем делегировав VK_TAB окну владельца и вызвав исходную процедуру окна расширенного редактирования для всех остальных ключей.   -  person krsi    schedule 28.05.2018
comment
Более чистое решение, вероятно, состоит в том, чтобы изменить значение, возвращаемое WM_GETDLGCODE, см. stackoverflow.com/questions/24117221/. Интересно, что я не могу найти документацию о том, как элементы управления RICHEDIT реагируют на это сообщение. Я, конечно, не знал об этом.   -  person Paul Sanders    schedule 29.05.2018
comment
Попробуйте сделать родительское окно настоящим диалогом (класс окна WC_DIALOG). Я считаю, что это может изменить поведение элемента управления richedit по умолчанию, чтобы игнорировать клавишу TAB (поэтому диалоговый менеджер будет обрабатывать ее) вместо того, чтобы есть ее. В остальном согласен с предыдущим комментарием. Обязательное чтение OldNewThing: Тем, кто не понимает диспетчер диалогов обречены на повторную реализацию, плохо   -  person zett42    schedule 29.05.2018
comment
@ zett42 Наверное, уже есть. Переключение между элементами управления вообще не будет работать в опубликованном коде, так как ни DialogBox, ни CreateDialog не вызывались, поэтому этот код, вероятно, просто для примера. Во всяком случае, я не верю в это. Зачем любому элементу управления проверять имя класса своего родителя? Диалоги можно создавать с любым именем класса, а не только с WC_DIALOG. Хорошая ссылка, хотя код там как раз то, что нужно ОП.   -  person Paul Sanders    schedule 29.05.2018
comment
Изменение возвращаемого значения WM_GETDLGCODE, как описано в статье Рэймонда Чена, работает отлично. ссылка   -  person krsi    schedule 29.05.2018
comment
@PaulSanders Вышеприведенный код вызывает IsDialogMessage(), который обычно включает табуляцию.   -  person zett42    schedule 29.05.2018
comment
@ zett42 Ах да, извините, пропустил это. Все эти WS_TABSTOP трюки действительно происходят там.   -  person Paul Sanders    schedule 29.05.2018
comment
@krsi Тогда я предлагаю вам написать реальный ответ.   -  person zett42    schedule 29.05.2018


Ответы (1)


Измените возвращаемое значение WM_GETDLGCODE для элемента управления Rich Edit. Флаг DLGC_WANTTAB и флаг DLGC_WANTMESSAGE для ключа VK_TAB необходимо удалить из возвращаемого значения.

Это решение основано на следующей статье MSDN Раймонда Чена: Те, кто не понимаю, диалоговый менеджер обречен на переделку, плохо.

#include <windows.h>
#include <commctrl.h>
#include <richedit.h>

#pragma comment(lib, "comctl32.lib")
#pragma comment(linker,"\"/manifestdependency:type='win32' \
    name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
    processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK RichEditProc(HWND, UINT, WPARAM, LPARAM);

WNDPROC richEditOrigProc;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    LoadLibrary(TEXT("msftedit.dll"));

    WNDCLASSEX mainwcex;
    mainwcex.cbSize = sizeof(WNDCLASSEX);
    mainwcex.style = CS_HREDRAW | CS_VREDRAW;
    mainwcex.lpfnWndProc = WindowProc;
    mainwcex.cbClsExtra = 0;
    mainwcex.cbWndExtra = 0;
    mainwcex.hInstance = hInstance;
    mainwcex.hIcon = NULL;
    mainwcex.hCursor = (HICON)LoadCursor(NULL, IDC_ARROW);
    mainwcex.hbrBackground = NULL;
    mainwcex.lpszMenuName = NULL;
    mainwcex.lpszClassName = "mainwindow";
    mainwcex.hIconSm = NULL;

    RegisterClassEx(&mainwcex);

    HWND mainWindow = CreateWindowEx(
        NULL,
        "mainwindow",
        NULL,
        WS_OVERLAPPEDWINDOW,
        100,
        100,
        600,
        400,
        NULL,
        NULL,
        hInstance,
        NULL);

    HWND richEditControl = CreateWindowEx(
        NULL,
        "RICHEDIT50W",
        "Rich Edit",
        WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP,
        50,
        50,
        100,
        25,
        mainWindow,
        NULL,
        hInstance,
        NULL);

    richEditOrigProc = (WNDPROC) SetWindowLongPtr(richEditControl, GWLP_WNDPROC, (LONG_PTR) RichEditProc);

    HWND button1 = CreateWindowEx(
        NULL,
        WC_BUTTON,
        "Button1",
        WS_CHILD | WS_VISIBLE | WS_TABSTOP,
        200,
        50,
        100,
        25,
        mainWindow,
        NULL,
        hInstance,
        NULL);

    HWND button2 = CreateWindowEx(
        NULL,
        WC_BUTTON,
        "Button2",
        WS_CHILD | WS_VISIBLE | WS_TABSTOP,
        350,
        50,
        100,
        25,
        mainWindow,
        NULL,
        hInstance,
        NULL);

    ShowWindow(mainWindow, nCmdShow);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        if (!IsDialogMessage(mainWindow, &msg)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return static_cast<int>(msg.wParam);
}

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg) {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        break;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK RichEditProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg) {
        case WM_GETDLGCODE:
        {
            // THIS IS THE IMPORTANT PART
            // ***********************************
            LRESULT lres = CallWindowProc(richEditOrigProc, hWnd, uMsg, wParam, lParam);
            lres &= ~DLGC_WANTTAB;
            if (lParam && ((MSG *)lParam)->message == WM_KEYDOWN && ((MSG *)lParam)->wParam == VK_TAB) {
                lres &= ~DLGC_WANTMESSAGE;
            }
            return lres;
            // ***********************************
        }
        break;
    }
    return CallWindowProc(richEditOrigProc, hWnd, uMsg, wParam, lParam);
}**
person krsi    schedule 29.05.2018