3

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);
}
krsi
  • 1,045
  • 2
  • 14
  • 24
  • 1
    Can you not tab _away_ from the rich edit control or can you not tab _to_ the control? If it's the former, use Spy++ to see how it's responding to `WM_GETDLGCODE`. – Paul Sanders May 28 '18 at 19:24
  • @PaulSanders I cannot tab away from the rich edit control. – krsi May 28 '18 at 19:31
  • OK, fire up [Spy++](https://msdn.microsoft.com/en-us/library/dd460756.aspx) then. It comes with Visual Studio, but maybe you can get hold of it some other way. `WM_GETDLGCODE` is documented [here](https://msdn.microsoft.com/en-us/library/windows/desktop/ms645425(v=vs.85).aspx). – Paul Sanders May 28 '18 at 19:33
  • I solved it by subclassing the rich edit control to intercept WM_GETDLGCODE, then delegate VK_TAB to the owner window and call the original rich edit window proc for all other keys. – krsi May 28 '18 at 20:07
  • 1
    A cleaner solution is probably to modify the value returned by `WM_GETDLGCODE`, see https://stackoverflow.com/questions/24117221/how-to-make-a-multi-line-textboxs-tab-stop-working-in-win32api-application. It's interesting that I can find no documentation of how RICHEDIT controls respond to this message. I was certainly unaware of it. – Paul Sanders May 29 '18 at 05:13
  • 1
    Try to make the parent window an actual dialog (window class WC_DIALOG). I believe this could change the default behaviour of the richedit control to ignore the TAB key (so dialog manager will handle it) instead of eating it. Otherwise, I agree to previous comment. Obligatory OldNewThing read: "_[Those who do not understand the dialog manager are doomed to reimplement it, badly](https://blogs.msdn.microsoft.com/oldnewthing/20070627-00/?p=26243)_" – zett42 May 29 '18 at 06:05
  • @zett42 It probably already is. Tabbing between controls would not work at all in the posted code as neither `DialogBox` nor `CreateDialog` has been invoked so that code is probably just by way of example. Anyway, I don't believe this. Why would any control check the class name of its parent? Dialogs can be created with any class name, not just `WC_DIALOG`. Good link though, the code there is just what the OP needs. – Paul Sanders May 29 '18 at 08:54
  • 1
    Modifying `WM_GETDLGCODE` return value, as described by Raymond Chen article works perfectly. [link](https://blogs.msdn.microsoft.com/oldnewthing/20070627-00/?p=26243) – krsi May 29 '18 at 09:21
  • @PaulSanders The above code calls `IsDialogMessage()`, which normally enables tabbing. – zett42 May 29 '18 at 11:15
  • @zett42 Ah yes, sorry missed that. The `WS_TABSTOP` trickery does indeed all happen in there. – Paul Sanders May 29 '18 at 11:17
  • @krsi I suggest that you write an actual answer then. – zett42 May 29 '18 at 11:18

1 Answers1

4

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);
}**
krsi
  • 1,045
  • 2
  • 14
  • 24