Using TRichEdit at runtime without defining a parent
Asked Answered
S

4

20

I need to use a TRichEdit at runtime to perform the rtf to text conversion as discussed here. I succeded in doing this but I had to set a dummy form as parent if not I cannot populate the TRichedit.Lines. (Error: parent is missing). I paste my funciton below, can anyone suggest a way to avoid to define a parent? Can you also comment on this and tell me if you find a more performant idea?

Note: I need a string, not TStrings as output, this is why it has been designed like this.

function RtfToText(const RTF: string;ReplaceLineFeedWithSpace: Boolean): string;
var
  RTFConverter: TRichEdit;
  MyStringStream: TStringStream;
  i: integer;
  CustomLineFeed: string;

begin
  if ReplaceLineFeedWithSpace then
    CustomLineFeed := ' '
    else
    CustomLineFeed := #13;
  try
    RTFConverter := TRichEdit.Create(nil);
    try
      MyStringStream := TStringStream.Create(RTF);
      RTFConverter.parent := Form4; // this is the part I don't like
      RTFConverter.Lines.LoadFromStream(MyStringStream);
      RTFConverter.PlainText := True;
      for i := 0 to RTFConverter.Lines.Count - 1 do
      begin
        if i < RTFConverter.Lines.Count - 1 then
          Result := Result + RTFConverter.Lines[i] + CustomLineFeed
          else
          Result := Result + RTFConverter.Lines[i];
      end;
    finally
      MyStringStream.Free;
    end;
  finally
    RTFConverter.Free;
  end;

end;

UPDATE: After the answer I updated the function and write it here for reference:

function RtfToText(const RTF: string;ReplaceLineFeedWithSpace: Boolean): string;
var
  RTFConverter: TRichEdit;
  MyStringStream: TStringStream;
begin
  RTFConverter := TRichEdit.CreateParented(HWND_MESSAGE);
  try
    MyStringStream := TStringStream.Create(RTF);
    try
      RTFConverter.Lines.LoadFromStream(MyStringStream);
      RTFConverter.PlainText := True;
      RTFConverter.Lines.StrictDelimiter := True;
      if ReplaceLineFeedWithSpace then
        RTFConverter.Lines.Delimiter := ' '
        else
        RTFConverter.Lines.Delimiter := #13;
      Result := RTFConverter.Lines.DelimitedText;
    finally
      MyStringStream.Free;
    end;
  finally
    RTFConverter.Free;
  end;
end;
Storyteller answered 12/7, 2010 at 11:7 Comment(4)
Line Feed char is #10, not #13, which is Carriage ReturnPhotoconductivity
It's annoying to have to reference a visual component (TRichEdit), which I presume requires you to call Synchronize when doing this in a separate thread. I'm wanting to convert RTF to plain text in a server, and I haven't found any code that I'm comfortable using as yet. But thanks for posting this.Underbred
I have moved the Create calls outside of the try/finally blocks, which is the correct way of doing it. Otherwise, if there's an exception in the Create call, the code will attempt to call Free on an uninitialized variable (remember that an exception means that the code never returns to the calling statement but jumps directly to the except/finally part, so the assignment to the variable is never done).Vannie
This gives back only the RTF file path. Look at the correct answer here: #70914897Quietism
B
34

TRichEdit control is an wrapper around the RichEdit control in Windows. Windows's controls are... well.. Windows, and they need an Window Handle to work. Delphi needs to call CreateWindow or CreateWindowEx to create the Handle, and both routines need an valid parent Window Handle to work. Delphi tries to use the handle of the control's parent (and it makes sense!). Happily one can use an alternative constructor (the CreateParanted(HWND) constructor) and the nice people at Microsoft made up the HWND_MESSAGE to be used as parent for windows that don't actually need a "window" (messaging-only).

This code works as expected:

procedure TForm2.Button2Click(Sender: TObject);
var R:TRichEdit;
    L:TStringList;
begin
  R := TRichEdit.CreateParented(HWND_MESSAGE);
  try
    R.PlainText := False;
    R.Lines.LoadFromFile('C:\Temp\text.rtf');
    R.PlainText := True;

    Memo1.Lines.Text := R.Lines.Text;
  finally 
    R.Free;
  end;
end;
Bimetallic answered 12/7, 2010 at 13:49 Comment(2)
I set this as accepted answer because it shows how to create a TRichEdit control almost without defining a parent.Storyteller
Would it be possible to do such conversion in a thread-safe manner? I assume (and have tested) that trying to do this in a worker thread is very dangerous.Browband
D
8

This is part of the way the VCL works, and you're not going to get it to work differently without some heavy workarounds. But you don't need to define a dummy form to be the parent; just use your current form and set visible := false; on the TRichEdit.

If you really want to improve performance, though, you could throw out that loop you're using to build a result string. It has to reallocate and copy memory a lot. Use the Text property of TrichEdit.Lines to get a CRLF between each line, and DelimitedText to get somethimg else, such as spaces. They use an internal buffer that's only allocated once, which will speed up the concatenation quite a bit if you're working with a lot of text.

Draftsman answered 12/7, 2010 at 11:25 Comment(1)
I didn't know about these feature of TString, I will update my question by putting the final version, I also included a runtime generated form, so the function becomes a "standalone" function.Storyteller
N
4

I use DrawRichText to draw RTF without a RichEdit control. (IIRC this is called Windowless Rich Edit Controls.) Maybe you can use this also for converting - however I have never tried this.

Nauru answered 12/7, 2010 at 13:40 Comment(0)
G
0

This has been the most helpfull for me to get started with TRichEdit, but not with the conversion. This however works as expected and you don't need to set the Line Delimiter:

// RTF to Plain:
procedure TForm3.Button1Click(Sender: TObject);
var
    l:TStringList;
    s:WideString;
    RE:TRichEdit;
    ss:TStringStream;
begin
    ss := TStringStream.Create;
    s := Memo1.Text; // Input String
    RE := TRichEdit.CreateParented(HWND_MESSAGE);
    l := TStringList.Create;
    l.Add(s);
    ss.Position := 0;
    l.SaveToStream(ss);
    ss.Position := 0;
    RE.Lines.LoadFromStream(ss);
    Memo2.Text := RE.Text; // Output String
end;

// Plain to RTF:
procedure TForm3.Button2Click(Sender: TObject);
var
    RE:TRichEdit;
    ss:TStringStream;
begin
    RE := TRichEdit.CreateParented(HWND_MESSAGE);
    RE.Text := Memo2.Text; // Input String
    ss := TStringStream.Create;
    ss.Position := 0;
    RE.Lines.SaveToStream(ss);
    ss.Position := 0;
    Memo1.Text := ss.ReadString(ss.Size); // Output String
end;

I'm using the TStringList "l" in the conversion to plain because somehow the TStringStream puts every single character in a new line.

Edit: Made the code a bit nicer and removed unused variables.

Godfree answered 12/12, 2014 at 10:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.