How to make TRichEdit behave like WordPad on Windows 7 when changing font for certain non-text characters?

With the help of Sertac Akyuz, it seems the direct reason is related to the charset of \bullet: In my localized Windows, the \bullet input by typing Alt(0149) always gets \fcharset134, and attempts to change its font through EM_SETCHARFORMAT always fail (well, the color, size, style can indeed be changed but not font family name).

The simplest workaround is therefore to first reset the charset and then change the font.

Note: RichEdit (version >= 4.1) should be used!

Note: RichEdit versions can be found at MSDN's About Rich Edit Controls, Murray Sargent's MSDN Blog RichEdit versions and RichEdit Versions Update to 7.0. The latter pages mentioned RichEdit versions higher than 4.1. As a test, I copied the RICHED20.DLL shiped with Office 2010 together with the application to Windows 2000, and everything works like a charm!

procedure TMainForm.ButtonFontClick(Sender: TObject);
var
  format: TCharFormat2;
begin
  if dlgFontCdxTxt.Execute then
  begin
    FillChar(format, sizeof(format), 0);
    format.cbSize:= Sizeof(format);
    format.dwMask:= CFM_CHARSET;
    format.bCharSet := 1; // or 0;
    redtTextBlock.Perform(EM_SETCHARFORMAT, SCF_SELECTION, Integer(@format));

    FillChar(format, sizeof(format), 0);
    format.cbSize:= Sizeof(format);
    format.dwMask:= CFM_FACE;
    StrPLCopy(format.szFaceName, dlgFontCdxTxt.Font.Name, High(format.szFaceName));
    redtTextBlock.Perform(EM_SETCHARFORMAT, SCF_SELECTION, Integer(@format));
  end;
  redtTextBlock.SetFocus;
end;

==================================================

According to Wikipedia, WordPad used Microsoft's RichEdit control, versions 1.0, 2.0 and 3.0 in Windows 95, 98 and Windows 2000, respectively. In Windows XP SP1 and later, WordPad uses RichEdit 4.1, including Windows 7.

Say a rtf document, being edited in WordPad, contains a non-text character Alt(0149), the bullet dot •. (Or U+2022)

In Windows 2000 SP4 or XP SP2, the font of that bullet dot can only take the default font of WordPad. That is to say, one cannot change font for that bullet dot interactively in WordPad.

However, in Windows 7 SP1, one could change its font by first changing to "Arial Unicode MS", and then to any desired font unlimited times.

Wordpad_1WordPad_2WordPad_3

Furthermore, WordPad document created using WordPad in Windows 7 that contains different fonts of the dot can be opened and viewed correctly in WordPad in Windows 2000 or XP.

The TRichEdit (Delphi XE, Windows 7) can also open and view the WordPad document created using WordPad in Windows 7 correctly by TRichEdit.Lines.LoadFromFile.

TRichEdit_1

Interactively, the TRichEdit (Delphi XE, Windows 7) allows the bullet dot's font to be changed to "Arial Unicode MS". However, one cannot go further to change to other fonts interactively in TRichEdit. TRichEdit_2TRichEdit_3TRichEdit_4

I am therefore wondering (1) the reason of the different behavior of WordPad in Windows 7, and (2) whether it is possible to make TRichEdit behave alike?

PS: One may need type Alt(0149) several times to get the dot in WordPad. Typing 2022 and Alt+x always works, as suggested here.

PS: One need to "activate" the fonts in WordPad as mentioned in the answer of Why TFontDialog gives less fonts than Screen.Fonts?

PS: One can always change the dot to different fonts in Word.

sample.rtf (paste into a plain text file and then change the extension to rtf to use)

{\rtf1\ansi\ansicpg936\deff0\deflang1033\deflangfe2052{\fonttbl{\f0\fswiss\fprq2\fcharset134 Arial Unicode MS;}{\f1\fnil\fcharset0 Arial Unicode MS;}{\f2\froman\fprq2\fcharset0 Times New Roman;}{\f3\fscript\fprq2\fcharset0 Comic Sans MS;}{\f4\fnil\fcharset0 Comic Sans MS;}{\f5\fmodern\fprq1\fcharset0 Consolas;}{\f6\fnil\fcharset0 Consolas;}{\f7\fmodern\fprq1\fcharset0 Lucida Console;}{\f8\fnil\fcharset0 Lucida Console;}{\f9\froman\fprq2\fcharset2 Symbol;}{\f10\froman\fprq2\fcharset0 Symbol;}{\f11\fnil\fcharset134 \'cb\'ce\'cc\'e5;}}
{\*\generator Msftedit 5.41.21.2510;}\viewkind4\uc1\pard\nowidctlpar\sa200\sl276\slmult1\lang2052\f0\fs22 Arial sample text \lang1033\f1\bullet\f2\par
\b\f3 Comic sample text \f4\bullet\f2\par
\b0\f5 Consolas sample text \f6\bullet\f2\par
\f7 Lucida sample text \f8\bullet\f2\par
\pard\nowidctlpar\qj\lang2052\f9 symbl sample text \lang1033\f10\u149?\kerning2\fs21\par
\pard\sa200\sl276\slmult1\lang2052\kerning0\f11\fs22\par
}

uMainForm.dfm to view the format of the lines of TRichEdit

object MainForm: TMainForm
  Left = 0
  Top = 0
  Caption = 'MainForm'
  ClientHeight = 362
  ClientWidth = 637
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 13
  object pnlBtn: TPanel
    Left = 0
    Top = 0
    Width = 637
    Height = 57
    Align = alTop
    Caption = 'pnlBtn'
    TabOrder = 0
    object Button1: TButton
      Left = 240
      Top = 14
      Width = 137
      Height = 31
      Caption = 'Analyze Rich Edit'
      TabOrder = 0
      OnClick = Button1Click
    end
  end
  object pnlClient: TPanel
    Left = 0
    Top = 57
    Width = 637
    Height = 305
    Align = alClient
    Caption = 'pnlClient'
    TabOrder = 1
    object redtTextBlock: TRichEdit
      Left = 1
      Top = 1
      Width = 225
      Height = 303
      Align = alLeft
      Font.Charset = GB2312_CHARSET
      Font.Color = clWindowText
      Font.Height = -11
      Font.Name = 'Tahoma'
      Font.Style = []
      Lines.Strings = (
        'redt1')
      ParentFont = False
      TabOrder = 0
    end
    object mmo1: TMemo
      Left = 226
      Top = 1
      Width = 410
      Height = 303
      Align = alClient
      Lines.Strings = (
        'mmo1')
      TabOrder = 1
    end
  end
  object Button2: TButton
    Left = 36
    Top = 14
    Width = 171
    Height = 31
    Caption = 'Font...'
    TabOrder = 2
    OnClick = Button2Click
  end
  object dlgFontCdxTxt: TFontDialog
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'Tahoma'
    Font.Style = []
    Left = 480
    Top = 16
  end
end

uMainForm.pas to view the format of the lines of TRichEdit

unit uMainForm;

interface

uses
  Contnrs,
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls;

type

  TCdxmlStyle = class
  public
    FontName: string;
    Str: string;
  end;

  TCdxmlText = class
  public
    Styles: TObjectList;
    constructor Create;
  end;

  TMainForm = class(TForm)
    redtTextBlock: TRichEdit;
    mmo1: TMemo;
    pnlBtn: TPanel;
    pnlClient: TPanel;
    Button1: TButton;
    Button2: TButton;
    dlgFontCdxTxt: TFontDialog;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    procedure TestLoadFromFile;
    procedure AnalyzeRichEdit;
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

uses
  RichEdit, StrUtils;

{ TCdxmlText }

constructor TCdxmlText.Create;
begin
  Styles := TObjectList.Create;
end;

var
  l_HiddenRichEdit: TRichEdit;

{ TMainForm }

procedure TMainForm.FormCreate(Sender: TObject);
begin
  TestLoadFromFile;
  AnalyzeRichEdit;
end;

procedure TMainForm.Button2Click(Sender: TObject);
var
  format: TCharFormat2;
begin
  if dlgFontCdxTxt.Execute then
  begin
    FillChar(format, sizeof(format), 0);
    format.cbSize:= Sizeof(format);
    format.dwMask:= CFM_FACE;

    StrPLCopy(format.szFaceName, dlgFontCdxTxt.Font.Name, High(format.szFaceName));

    redtTextBlock.Perform(EM_SETCHARFORMAT, SCF_SELECTION, Integer(@format));
  end;
  redtTextBlock.SetFocus;
end;

procedure TMainForm.Button1Click(Sender: TObject);
begin
  AnalyzeRichEdit;
end;

procedure TMainForm.TestLoadFromFile;
begin
  redtTextBlock.Clear;

  redtTextBlock.Lines.LoadFromFile('sample.rtf');
end;

procedure TMainForm.AnalyzeRichEdit;
var
  l_MemStream: TMemoryStream;
  l_Format: TCharFormat2;

  I, J: Integer;
  l_CdxmlStyle, l_CdxmlStyleWorker: TCdxmlStyle;
  l_StyleFont: string;

  l_CdxmlText: TCdxmlText;
begin
  l_CdxmlStyle := nil;
  l_CdxmlStyleWorker := nil;

  mmo1.Clear;

  l_MemStream := TMemoryStream.Create;
  redtTextBlock.Lines.SaveToStream(l_MemStream);
  l_MemStream.Seek(0, soFromBeginning);
  l_HiddenRichEdit.Lines.LoadFromStream(l_MemStream);

  l_CdxmlText := TCdxmlText.Create;
  for I := 0 to Length(TrimRight(l_HiddenRichEdit.Text)) - 1 do
  begin
    l_CdxmlStyleWorker := TCdxmlStyle.Create;

    FillChar(l_Format, sizeof(l_Format), 0);
    l_Format.cbSize:= Sizeof(l_Format);
    l_Format.dwMask:= CFM_FACE;

    l_HiddenRichEdit.SelStart := I;
    l_HiddenRichEdit.SelLength := 1;
    l_HiddenRichEdit.Perform(EM_GETCHARFORMAT, SCF_SELECTION, Integer(@l_Format));

    l_CdxmlStyleWorker.FontName := l_Format.szFaceName;

    l_CdxmlStyleWorker.Str := AnsiReplaceStr(l_HiddenRichEdit.SelText, #13, #13#10);

    if l_CdxmlStyle = nil then
    begin
      l_CdxmlText.Styles.Add(l_CdxmlStyleWorker);
      l_CdxmlStyle := l_CdxmlStyleWorker;
    end
    else if (l_CdxmlStyleWorker.FontName  <> l_CdxmlStyle.FontName ) then
    begin
      l_CdxmlText.Styles.Add(l_CdxmlStyleWorker);
      l_CdxmlStyle := l_CdxmlStyleWorker;
    end
    else
    begin
      l_CdxmlStyle.Str := l_CdxmlStyle.Str + l_CdxmlStyleWorker.Str;
    end;
  end;

  for I := 0 to l_CdxmlText.Styles.Count - 1 do
  begin
    l_CdxmlStyle := TCdxmlStyle(l_CdxmlText.Styles[I]);
    mmo1.Lines.Add(l_CdxmlStyle.Str + ':' + l_CdxmlStyle.FontName);
  end;
end;

initialization

  l_HiddenRichEdit := TRichEdit.CreateParented(HWND_MESSAGE);

end.

Solution 1:

One thing to check would be to see if the richedit controls used by WordPad and TRichEdit are the same. I would recommend you check with (Spy++) Spyxx.exe to make sure the control has the same class and the same styles. If they are the same, I would then also check to make sure the controls are receiving the same messages, again using Spy++. I'm guessing that the controls are NOT the same or that they are not configured the same.

If they're not the same control, then you should be able to use the same control as WordPad (assuming it is part of the standard Windows custom controls). And use Spy++ to set the style the same way that WordPad is. Also, you may need to send it the same messages as well.