Search code examples
winapirichedit

Adding a clipboard type to the copy types of a Rich Edit control


I am writing a plugin for a host app, and my plugin uses a Rich Edit control. I would like to intercept when the user copies or cuts text to the clipboard from the control and add a custom format version of the text to the clipboard that my host app understands better than the standard RTF or plain text formats.

After consulting the LLMs, this code seems like it might have worked, or at least gotten me started:

static LRESULT CALLBACK richEditSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    switch (uMsg)
    {
        case WM_COPY:
        case WM_CUT:
        {
            // capture the custom format of the selection here.
            LRESULT retval = DefSubclassProc(hWnd, uMsg, wParam, lParam);
            // add the custom format of the selection to the clipboard here.
            return retval;
        }
    }
    return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}

However, for some reason, my breakpoint on the WM_COPY/WM_CUT block never hits when I hit ctrl-X or ctrl-C. (These are the primary way to cut or copy, since my dialog box has no menu.) This is an extract of my subclass routine for the Rich Edit. It is correctly hooked up and working for other messages.

Does anyone have any other ideas? Worst case, I could intercept the ctrl-C and ctrl-X keys, but there must be a less hacky way than that. For grins, I added this code to the main window proc, but those didn't hit either.


Solution

  • It seems (as a commenter posted) that the Rich Edit control does not guarantee that WM_CUT/WM_COPY/WM_PASTE will be sent when it responds internally to keyboard events ctrl-X/C/V. The solution that seems safest (and also works) is to intercept the keyboard events and send the relevant window messages back to the control. This has the advantage that

    • if a future version of the control does start guaranteeing them, the code continues to work and does not perform them twice.
    • it responds correctly to any internal or external agent that sends the messages.
    static LRESULT CALLBACK richEditSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
    {
        switch (uMsg)
        {
            case WM_COPY:
            case WM_CUT:
            {
                // capture the custom format of the selection here.
                LRESULT retval = DefSubclassProc(hWnd, uMsg, wParam, lParam);
                // add the custom format of the selection to the clipboard here.
                return retval;
            }
    
            case WM_PASTE:
                if (OpenClipboard(nullptr))
                {
                    bool handled = false;
                    UINT myFormat = RegisterClipboardFormatW(MY_CUSTOM_FORMAT);
                    HANDLE hData = GetClipboardData(myFormat);
                    if (hData != NULL)
                    {
                        // insert custom format into control here
                        handled = true
                    }
                    CloseClipboard();
                    if (handled) return 0;
                }
                break;
    
            case WM_KEYDOWN:
                if (GetKeyState(VK_CONTROL) & 0x8000)
                {
                    switch (wParam)
                    {
                        case 'C':
                            SendMessage(hWnd, WM_COPY, 0, 0);
                            return 0;
                        case 'X':
                            SendMessage(hWnd, WM_CUT, 0, 0);
                            return 0;
                        case 'V':
                            SendMessage(hWnd, WM_PASTE, 0, 0);
                            return 0;
                    }
                }
                break;
        }
        return DefSubclassProc(hWnd, uMsg, wParam, lParam);
    }