Search code examples
c++winapifocustabstop

Why WS_TABSTOP doesn't work on rich edit control?


Focus change using tab key doesn't work on rich edit control, but works fine if rich edit control is replaced by WC_EDIT control.

#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);
}

Solution

  • Modify WM_GETDLGCODE return value for the rich edit control. DLGC_WANTTAB flag and DLGC_WANTMESSAGE flag for VK_TAB key need to be removed from the return value.

    This solution is based on the following MSDN article by Raymond Chen: Those who do not understand the dialog manager are doomed to reimplement it, badly.

    #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);
    }**