【问题标题】:Text highlighting and UNDO in RicheditRichedit 中的文本突出显示和 UNDO
【发布时间】:2020-11-29 21:59:43
【问题描述】:

我正在尝试在 RichEdit 中实现文本突出显示和撤消。下面的代码标记了 5 和 6 个字符,但撤消不起作用(文本不会更改),尽管 Edit_CanUndo 返回 1。如果我关闭updateHighlighting 然后撤消工作,但它一次回滚太多(例如输入长文本,按Ctrl + V粘贴文本,输入另一个文本 - 只需要3次撤消即可全部回滚)。

我的代码有什么问题?也许我应该从updateHighlighting 返回一个特定的值?

#define UNICODE
#define _UNICODE

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

#define IDC_EDITOR 1000
#define IDC_BUTTON 1001

HWND hEditorWnd;

bool updateHighlighting(HWND hWnd) {
    int carriagePos = 0;
    SendMessage(hWnd, EM_GETSEL, (WPARAM)&carriagePos, (WPARAM)&carriagePos);

    CHARFORMAT cf = {0};
    cf.cbSize = sizeof(CHARFORMAT) ;
    cf.dwMask = CFM_COLOR | CFM_BOLD;
    cf.crTextColor = RGB(0, 0, 200);
    cf.dwEffects = CFM_BOLD;
    SendMessage(hWnd, EM_SETSEL, 0, -1);
    SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf);

    cf.dwMask = CFM_COLOR | CFM_BOLD;
    cf.dwEffects = cf.dwEffects & ~CFM_BOLD;
    cf.crTextColor = RGB(200, 0, 0);
    SendMessage(hWnd, EM_SETSEL, 4, 6);
    SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf);
    SendMessage(hWnd, EM_SETSEL, carriagePos, carriagePos);

    _tprintf(TEXT("End highlight\n"));
    return true;
}

LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_DESTROY:
            PostQuitMessage (0);
            break;

        case WM_COMMAND: {
            if (LOWORD(wParam) == IDC_EDITOR && HIWORD(wParam) == EN_CHANGE)
                return updateHighlighting(hEditorWnd);

            if (LOWORD(wParam) == IDC_BUTTON && HIWORD(wParam) == BN_CLICKED)
                _tprintf(TEXT("UNDO: can %i => result: %i\n"), Edit_CanUndo(hEditorWnd), Edit_Undo(hEditorWnd));
        }
        break;

        default:
            return DefWindowProc (hWnd, msg, wParam, lParam);
    }

    return 0;
}

int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow) {
    InitCommonControls();
    LoadLibrary(TEXT("msftedit.dll"));

    HWND hWnd;
    MSG msg;
    WNDCLASSEX wincl;

    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = TEXT("AppClass");
    wincl.lpfnWndProc = WindowProcedure;
    wincl.style = CS_DBLCLKS;
    wincl.cbSize = sizeof (WNDCLASSEX);
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;
    wincl.cbClsExtra = 0;
    wincl.cbWndExtra = 0;
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    if (!RegisterClassEx (&wincl))
        return 0;

    hWnd = CreateWindowEx (0, TEXT("AppClass"), TEXT("Test"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 300, 400, 310, 375, HWND_DESKTOP, NULL, hThisInstance, NULL);

    CreateWindowEx(0, WC_BUTTON , L"UNDO", WS_VISIBLE | WS_CHILD | WS_TABSTOP, 10, 310, 280, 30, hWnd, (HMENU)IDC_BUTTON, hThisInstance,  NULL);
    hEditorWnd = CreateWindowEx(0, TEXT("RICHEDIT50W"), NULL, WS_VISIBLE | WS_CHILD | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP | ES_NOHIDESEL, 0, 0, 300, 300, hWnd, (HMENU)IDC_EDITOR, hThisInstance,  NULL);
    SendMessage(hEditorWnd, EM_SETEVENTMASK, 0, ENM_CHANGE | ENM_UPDATE | ENM_KEYEVENTS);

    while (GetMessage (&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

附:我在 Win7x64 上使用 Code::Block 17.12 + gcc5.1(但构建了一个 32 位应用程序)。

【问题讨论】:

标签: c winapi undo richedit tom.h


【解决方案1】:

您对“不起作用”或何时“起作用”的确切含义尚不清楚,但是,更改颜色也是可以撤消的,因此如果您不想这样做,则必须关闭上色前撤消,上色后恢复。

我不知道是否有更简单的方法,但在 C 中我使用以下函数:

//#include <windows, richedit, ...>
#include <TOM.h>

extern HWND ghEditWnd;

static IUnknown *pUnk;
static ITextDocument *pDoc;

static const IID IID_ITextDocument = {
    0x8CC497C0, 0xA1DF, 0x11CE,
    {0x80,0x98,0x00,0xAA,0x00,0x47,0xBE,0x5D}
};

static int tomInit(void)
{
    if (!pUnk) SendMessage(ghEditWnd, EM_GETOLEINTERFACE, 0, (LPARAM)&pUnk);
    if (!pUnk || pUnk->lpVtbl->QueryInterface(pUnk,&IID_ITextDocument, &pDoc) != NOERROR)
        {pUnk= 0; return FALSE;}
    return TRUE;
}
void undoSuspend(void)
{
    if (!pUnk) if (!tomInit()) return;
    pDoc->lpVtbl->Undo(pDoc,tomSuspend,0); //Suspends Undo.
}
void undoResume(void)
{
    if (!pUnk) if (!tomInit()) return;
    pDoc->lpVtbl->Undo(pDoc,tomResume,0);   // resume Undo.
}
void releaseTOM(void)
{
    if (pUnk) {
        pUnk->lpVtbl->Release(pUnk); pUnk= 0; pDoc= 0;
    }
}

另见https://docs.microsoft.com/en-us/windows/win32/api/tom/

编辑:有问题How to disable Undo in Rich Text Box in WPF? 的建议是将 UndoLimit 设置为零。目前尚不清楚这是否也会清除现有的撤消堆栈。

微软表示,在 RichEdit 3.0 中可以暂停和恢复撤消操作。

【讨论】:

    【解决方案2】:

    这是基于 Paul Ogilvie 的回复和 codepilot answer 的工作示例(撤消按我的需要逐个字符地删除符号)。

    提示:使用freeze/unfreeze 来防止闪烁。这类似于SetWindowRedraw(hWnd, TRUE/FALSE),但最后一个操作会引起不必要的EN_SELCHANGE 通知消息。

    #define UNICODE
    #define _UNICODE
    
    #include <tchar.h>
    #include <windows.h>
    #include <windowsx.h>
    #include <commctrl.h>
    #include <richedit.h>
    
    // Code::Block doesn't have a tom.h :(
    // https://github.com/kinke/mingw-w64-crt/blob/master/mingw-w64-headers/include/tom.h
    #include "tom.h" 
    #include <richole.h>
    #include <unknwn.h>
    
    IUnknown *tr_code = NULL;
    ITextDocument *td_code;
    
    #define DEFINE_GUIDXXX(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID name = { l, w1, w2, { b1, b2,  b3,  b4,  b5,  b6,  b7,  b8 } }
    DEFINE_GUIDXXX(IID_ITextDocument,0x8CC497C0,0xA1DF,0x11CE,0x80,0x98, 0x00,0xAA,0x00,0x47,0xBE,0x5D);
    
    #define IDC_EDITOR 1000
    #define IDC_BUTTON 1001
    
    HWND hEditorWnd;
    
    bool updateHighlighting(HWND hWnd) {
        int carriagePos = 0;
        SendMessage(hWnd, EM_GETSEL, (WPARAM)&carriagePos, (WPARAM)&carriagePos);
    
        CHARFORMAT cf = {0};
        cf.cbSize = sizeof(CHARFORMAT) ;
        cf.dwMask = CFM_COLOR | CFM_BOLD;
        cf.crTextColor = RGB(0, 0, 200);
        cf.dwEffects = CFM_BOLD;
        SendMessage(hWnd, EM_SETSEL, 0, -1);
        SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf);
    
        cf.dwMask = CFM_COLOR | CFM_BOLD;
        cf.dwEffects = cf.dwEffects & ~CFM_BOLD;
        cf.crTextColor = RGB(200, 0, 0);
        SendMessage(hWnd, EM_SETSEL, 4, 6);
    
        SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf);
        SendMessage(hWnd, EM_SETSEL, carriagePos, carriagePos);
    
        _tprintf(TEXT("End highlight\n"));
        return true;
    }
    
    LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
        switch (msg) {
            case WM_DESTROY:
                PostQuitMessage (0);
                break;
    
            case WM_COMMAND: {
                if (LOWORD(wParam) == IDC_BUTTON && HIWORD(wParam) == BN_CLICKED)
                    _tprintf(TEXT("UNDO: can %i => result: %i\n"), Edit_CanUndo(hEditorWnd), Edit_Undo(hEditorWnd));
    
                if (LOWORD(wParam) == IDC_EDITOR && HIWORD(wParam) == EN_CHANGE) {
                    long cnt = 0;
                    td_code->Undo(tomSuspend, NULL); // <---
                    td_code->Freeze(&cnt);           // <---
                    updateHighlighting(hEditorWnd);
                    td_code->Unfreeze(&cnt);         // <---
                    td_code->Undo(tomResume, NULL);  // <---
                    return 1;
                 }
    
            }
            break;
    
            default:
                return DefWindowProc (hWnd, msg, wParam, lParam);
        }
    
        return 0;
    }
    
    int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow) {
        InitCommonControls();
        LoadLibrary(TEXT("msftedit.dll"));
    
        HWND hWnd;
        MSG msg;
        WNDCLASSEX wincl;
    
        wincl.hInstance = hThisInstance;
        wincl.lpszClassName = TEXT("AppClass");
        wincl.lpfnWndProc = WindowProcedure;
        wincl.style = CS_DBLCLKS;
        wincl.cbSize = sizeof (WNDCLASSEX);
        wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
        wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
        wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
        wincl.lpszMenuName = NULL;
        wincl.cbClsExtra = 0;
        wincl.cbWndExtra = 0;
        wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
    
        if (!RegisterClassEx (&wincl))
            return 0;
    
        hWnd = CreateWindowEx (0, TEXT("AppClass"), TEXT("Test"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 300, 400, 310, 375, HWND_DESKTOP, NULL, hThisInstance, NULL);
    
        CreateWindowEx(0, WC_BUTTON , L"UNDO", WS_VISIBLE | WS_CHILD | WS_TABSTOP, 10, 310, 280, 30, hWnd, (HMENU)IDC_BUTTON, hThisInstance,  NULL);
        hEditorWnd = CreateWindowEx(0, TEXT("RICHEDIT50W"), NULL, WS_VISIBLE | WS_CHILD | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP | ES_NOHIDESEL, 0, 0, 300, 300, hWnd, (HMENU)IDC_EDITOR, hThisInstance,  NULL);
        SendMessage(hEditorWnd, EM_SETEVENTMASK, 0, ENM_CHANGE | ENM_UPDATE | ENM_KEYEVENTS);
    
        // \/   
        SendMessage(hEditorWnd, EM_GETOLEINTERFACE, 0, (LPARAM)&tr_code);
        if(tr_code == (IRichEditOle*)NULL) {
            MessageBox(0, TEXT("Error when trying to get RichEdit OLE Object"), NULL, 0);
            return 0;
        }
        tr_code->QueryInterface(IID_ITextDocument,(void**)&td_code);
        // /\    
    
        while (GetMessage (&msg, NULL, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    
        return msg.wParam;
    }
    

    【讨论】:

    • 你对tom函数的调用比我的简单,不用lpVtbl。我会为我的应用程序研究它。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-31
    • 2015-05-29
    • 1970-01-01
    相关资源
    最近更新 更多