How to correctly handle Windows messages from an MSFTEDIT_CLASS (RichEdit) control?

2.9k views Asked by At

UPDATE: As requested I have added all of the code I am using to create the Window and its RichEdit control.

I'm trying to handle windows messages for a RichEdit control used as a child of another window.

Now I did have the RichEdit control working with the exception of my own WndProc. The issue is that, when I set wc.lpszClassName = MSFTEDIT_CLASS; so that it matches lpClassName used in CreateWindowEx(), the content of the RichEdit control no longer appears to draw (ie text, etc), however, its WndProc function can then handle messages.

The creation of the window:

First the constructor:

SubWindow::SubWindow(const wchar_t *szAppNameImport)
{
    szAppName = szAppNameImport;

    cfmt = CHARFORMATW();
    hwnd = HWND();
    windowRect = RECT();
    editControlHwnd = HWND();
    wc = WNDCLASSEX();

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_CLASSDC;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = GetModuleHandle(NULL);
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szAppName;
    wc.hIconSm = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
}

Then the Create() function:

VOID SubWindow::Create(unsigned int window_startX, unsigned int window_startY, unsigned int windowWidthInput, unsigned int windowHeightInput, HWND parent)
{   
    windowRect.left = window_startX;
    windowRect.top = window_startY;

    windowRect.right = windowWidthInput;
    windowRect.bottom = windowHeightInput;

    if(!RegisterClassEx(&wc))
    {
        throw std::exception();
    }

    if((hwnd = CreateWindowEx
        (
        WS_EX_CLIENTEDGE,
        szAppName,
        TEXT("Our classy sub window!"),
        WS_OVERLAPPEDWINDOW| WS_VISIBLE,

        windowRect.left, windowRect.top,
        windowRect.right, windowRect.bottom,
        parent,
        NULL,       
        wc.hInstance,
        NULL))==NULL)
    {
        throw std::exception();
    }

    SetWindowLongPtr(hwnd, GWL_USERDATA, (LONG_PTR)this);

    ShowWindow(hwnd, SW_SHOWDEFAULT);
    UpdateWindow(hwnd);
}

WndProc:

LRESULT CALLBACK SubWindow::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    SubWindow *childWindowPointer = (SubWindow*)GetWindowLongPtr(hwnd, GWLP_USERDATA);

    if(childWindowPointer != NULL)
    {
        if(childWindowPointer->GetEditControl() == hwnd)
            OutputDebugString(L"I SHOULD NOT BE CALLED");

        return childWindowPointer->MsgProc(hwnd, uMsg, wParam, lParam);
    }
    else
    {
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

MsgProc:

LRESULT SubWindow::MsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 
{
    PAINTSTRUCT ps;
    HDC hdc;

    switch(uMsg)
    {
    case WM_WINDOWPOSCHANGED:
        {
            GetClientRect(hwnd, &windowRect);
            SetWindowPos(editControlHwnd, NULL, windowRect.left, windowRect.top, windowRect.right, windowRect.bottom, SWP_NOZORDER | SWP_NOACTIVATE);
            return 0;
        }
    case WM_DESTROY:
        {
            OutputDebugString(TEXT("DESTROYING A SUB WINDOW!\n"));
            return 0;
        }

    case WM_PAINT:
        {
            InvalidateRect (hwnd, NULL, FALSE);
            hdc = BeginPaint(hwnd, &ps);
            EndPaint(hwnd, &ps);
            return 0;
        }

    case EM_EXSETSEL:
        {
            if(hwnd == editControlHwnd)
            {
                OutputDebugString(L"Text selection changed");
                return 0;
            }
        }
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
} 

The RichEdit control draws and functions perfectly, apparently without issue, with the exception of it not using the WndProc I have defined.

I'm not sure what I'm doing wrong here or how I can correctly resolve this.

EDIT: Based on the answers and comments, I have restored my code to use only a Window class which contains a RichEdit control, created thusly:

void SubWindow::CreateEditControl()
{
    std::wstring initialText = TEXT("TestWindow\r\n");

    LoadLibrary(L"Msftedit.dll");

    GetClientRect(hwnd, &windowRect);
    editControlHwnd = CreateWindowEx(0, MSFTEDIT_CLASS, initialText.data(),
        WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_READONLY | WS_VSCROLL | ES_NOHIDESEL,
        windowRect.left, windowRect.top,windowRect.right,windowRect.bottom,
        hwnd,
        NULL, NULL, NULL);

    cfmt.cbSize = sizeof(CHARFORMAT);
    cfmt.dwMask = CFM_COLOR | CFM_FACE | CFM_SIZE;
    cfmt.dwEffects = 0;
    cfmt.yHeight = 160;
    cfmt.crTextColor = RGB(0,0,0);
    wcscpy_s(cfmt.szFaceName, TEXT("Tahoma"));

    SendMessage(editControlHwnd, EM_SETCHARFORMAT, SCF_DEFAULT, (LPARAM)&cfmt);
}

How do I process the messages from this control in the Window's MsgProc?

3

There are 3 answers

6
Cody Gray - on strike On BEST ANSWER

When you create a rich edit control window using the default class name (MSFTEDIT_CLASS), all messages are going to be sent to its parent window. Since you are not that parent window, you are not able to handle those messages.

So you will need to subclass the control, substituting your own window procedure that will be called directly, instead of allowing the messages to be passed on to the parent. That is simple to do; I've discussed it before in this answer for a regular edit control. The altered example code looks like this:

// Stores the old original window procedure for the rich edit control.
WNDPROC wpOldRichEditProc;

// The new custom window procedure for the rich edit control.
LRESULT CALLBACK CustomRichEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        ...
    }

    // Pass the messages you don't process on to the original window procedure.
    CallWindowProc(wpOldRichEditProc, hWnd, msg, wParam, lParam);
}

And when you create the control:

// Create the rich edit control
HWND hWnd = CreateWindowEx(...)

// Subclass it.
wpOldRichEditProc= (WNDPROC)SetWindowLongPtr(hWnd,
                                             GWLP_WNDPROC,
                                             (WNDPROC)CustomRichEditProc);

You will also need to make sure to unsubclass the control whenever it is destroyed. The other example demonstrates doing that in response to messages received by the parent window, but that won't work in your case, since you're not getting messages for the parent window. Instead, you'll need to remove the subclass from the control in response to its own WM_NCDESTROY message:

SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)wpOldRichEditProc);

Or, version 6 of the common controls library introduced a new, less error-prone method of subclassing using a set of utility functions. (The critical functionality was actually there in earlier versions, but it was undocumented.) Considering that you do not have control over the process that actually owns the window, this is arguably the preferred approach.

There is a demo of both approaches here on MSDN.

And of course, you don't have to subclass only individual controls. You can also register a custom window class that behaves the same way as the built-in rich edit control, but still gives you first crack at the messages received by windows of that class. I can't tell from the question whether that's necessary or not; it sounds like you only have a single control you care about.

1
Mike Weir On

Can you show your code involving the wc structure and the creation of the window? I'm fairly sure you don't want the main window to have the same class as the rich edit control - and that's what I'm reading so far.

I don't even know why you have a WNDCLASSEX apply to a rich edit control.

My suggestion is that you simplify things and "subclass" the rich edit control after it has been created, using SetWindowLong() with GWL_WNDPROC to your EditControl::WndProc.

0
Stuart On

You say that the original problem was that your parent window was not getting the notification messages from the RichEdit control. Did you send a EM_SETEVENTMASK message to the RichEdit control? If you don't, the RichEdit control will not send certain notification messages to its parent window. See EM_SETEVENTMASK message.