Reputation: 5225
I'm trying to implement a text highlighting and undo in RichEdit. The code below marks 5 and 6 chars but undo doesn't work (the text doesn't change) though Edit_CanUndo
returns 1
. If I turn off a updateHighlighting
then undo works but it rolls back too much at once (e.g. input a long text, paste a text by Ctrl+V, input another text - it's required only 3 undo to rollback all).
What is wrong with my code? Maybe I should return a specific value from 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;
}
P.S. I use Code::Block 17.12 + gcc5.1 on Win7x64 (but build a 32bit app).
Upvotes: 0
Views: 383
Reputation: 5225
Here is the working example (undo removes symbols char-by-char as I want) based on Paul Ogilvie's reply and codepilot answer.
Hint: use freeze/unfreeze
to prevent flickering. That is similar to SetWindowRedraw(hWnd, TRUE/FALSE)
but the last operation arouse unnecessary EN_SELCHANGE
notification messages.
#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;
}
Upvotes: 1
Reputation: 25286
What you mean exactly with "doesn't work" or with when it "does work" is unclear, however, changing color is something that can be undone too so if you do not want that, then you have to turn off undo before coloring and resume it after coloring.
I don't know if there are easier ways to do it, but in C I use the following functions:
//#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;
}
}
See also https://learn.microsoft.com/en-us/windows/win32/api/tom/
EDIT: in question How to disable Undo in Rich Text Box in WPF? the suggestion was to set UndoLimit to zero. It is not clear if that would clear the existing undo stack too.
Undo can be suspended and resumed in RichEdit 3.0, Microsoft says.
Upvotes: 1