delphiwinapiwindows-messagesrichedittrichedit

Why doesn't EM_SETTEXTMODE work?


I'm trying to use EM_SETTEXTMODE on a RichEdit control in Delphi 7.

Just create a new project, add a TRichEdit control and a TButton control and add the following code to the button's click handler:

  SendMessage(RichEdit1.Handle, WM_SETTEXT, 0, LPARAM(PChar('')));
  Button1.Caption := IntToStr(SendMessage(RichEdit1.Handle, EM_GETTEXTMODE, 0, 0));
  Button1.Caption := Button1.Caption + ' ' + IntToStr(SendMessage(RichEdit1.Handle, EM_SETTEXTMODE, TM_PLAINTEXT, 0));
  Button1.Caption := Button1.Caption + ' ' + IntToStr(SendMessage(RichEdit1.Handle, EM_GETTEXTMODE, 0, 0));

The button's caption is set to 38 0 38 after clicking on the button, meaning the text mode didn't change at all - initially it was 38 (TM_RICHTEXT or TM_SINGLELEVELUNDO or TM_MULTICODEPAGE), then SETTEXTMODE was successful (0) but even after that it is still 38.

The RichEdit's text is cleared before using EM_SETTEXTMODE as suggested by the documentation.

I've tried setting different values with EM_SETTEXTMODE and it always stays 38.

I noticed that EM_SETTEXTMODE always returns 0 (success) even if the RichEdit control contains text before calling it.

I tried using RichEdit1.Perform instead of SendMessage - no difference.

I've found several threads in various forums on this issue, and it wasn't resolved in any of them.

Any idea why isn't this working?


Solution

  • Older Delphi versions load the RichEdit control which is located in RICHED32.DLL. Newer Delphi versions load RICHED20.DLL.

    RICHED32.DLL exposes many problems, like yours. The fix is modifying comctrls.pas so that it loads RICHED20.DLL. That will most likely not be enough as there may be compatibility issues with this DLL and the VCL code. Look for the TCustomRichEdit.CreateParams() procedure, it contains the LoadLibrary call.

    I tested your code in Delphi XE and there it works, so your best option is to upgrade to a more recent Delphi version.

    UPDATE

    I did some tests with Delphi 5 and it seems to be enough to change 2 functions. Copy Comctrls.Pas into your project and execute these modifications:

    1) in TCustomRichEdit.CreateParams(), change

    const
      RichEditModuleName = 'RICHED32.DLL';
    

    into

    const
      RichEditModuleName = 'RICHED20.DLL';
    

    and

    CreateSubClass(Params, 'RICHEDIT');
    

    into

    CreateSubClass(Params, 'RICHEDIT20A');
    

    2) in the procedure TRichEditStrings.Insert(), change

    if RichEdit.SelStart <> (Selection.cpMax + Length(Str)) then
          raise EOutOfResources.Create(sRichEditInsertError);
    

    into

    if RichEdit.SelStart <> (Selection.cpMax + Length(Str) - CountLineBreaks(Str)) then
          raise EOutOfResources.Create(sRichEditInsertError);
    

    code for CountLineBreaks/PosEx:

    function PosEx(const SubStr, S: string; Offset: Cardinal = 1): Integer;
    var
     Tmp: PChar;
    
    begin
     Result := 0;
     if (Offset > Cardinal(Length(S))) then exit;
     if Offset = 1 then
      Result := Pos(SubStr, S)
     else
      begin
       Tmp := StrPos(PChar(@S[Offset]), PChar(SubStr));
       if Tmp = nil then exit;
       Result := Cardinal(Tmp - PChar(@S[Offset])) + Offset;
      end;
    end;
    
    
    function CountLineBreaks(const S: string): Integer;
    const
      LB = #13#10;
    var
      P: Integer;
    begin
      Result := 0;
      P := Pos(LB, S);
      while P <> 0 do
      begin
        Inc(Result);
        P := PosEx(LB, S, P + 2);
      end;
    end;
    

    Doing this in Delphi5, I get the correct result:

    Delphi5

    One thing to keep in mind is that RichEdit 2.0 replaces CRLF pairs with CR, so other issues may creep up. Don't shoot me if you run into problems down the road...