首页  编辑  

RichEdit经典问答FAQ

Tags: /超级猛料/VCL/Memo&Edit&Richedit/RichEdit、RxRichEdit/   Date Created:
Tips about Delphi's TRichEdit
This is a list of tips and hints on Delphi database issues. Most are in the Q&A format.  Some are just e-mail messages.
This is a ragtag comglomeration of info from many sources. Most of the info came from Compuserve, Delphi Talk, and various newsgroups.
I can't vouch for the accuracy of every item - but I do make corrections to this document as I learn of problems.
Please send me corrections so I can share them with others.
I also like to get additional examples to include here.
Eric Engler,  October 21, 1998
englere@swcp.com
http://www.geocities.com/SiliconValley/Network/2114/

Delphi 4 Note: I do not have Delphi 4 yet, but most of the information here should work fine with Delphi 4.

The original release of Delphi 2 had a bug in RichEdit that prevented it from printing under NT 4.  It was fixed in one of the minor updates.
Delphi 3 never had that bug.

Delphi 1 had no RTF control.
Delphi 2 introduced TRichEdit.
Delphi 3 added TDBRichEdit.
Delphi 3's TDBRichEdit doesn't give you any way to access the raw RTF codes, so I normally use TRichEdit with Delphi 3, and I do my own loads/save to tables. Code for this is provided here.

General Note: The RichEdit control sometimes needs to have the focus before some options will work.  So, if any option in this document is not working for you, try to do this first:  RichEdit1.SetFocus;

Q: Which Microsoft DLL must be present on the user's hard drive to support TRichEdit?
A: The RTF DLL needed by deployed Delphi 2 and 3 programs that use TRichEdit is: RICHED32.DLL.  Mine is still from the original release of Win95.  Most Win32 users have this already.
Some RTF enhanced controls (add-ons) for Delphi 3 require RICHED20.DLL.
This is an updated RTF control.  It comes with NT 4, and it came with IE 3 and IE 4 for Win95.  It also comes with MS Office 97 and probably comes with lots of other software.  It has lots of enhancements over the older RICHED32.DLL, but you need an enhanced Delphi VCL in order to make use of the new features.
The best freeware enhanced richedit control that I know of is called TRichEdit98/TDBRichEdit98, which I found in file RICHED98.ZIP on the Delphi Super Page. The new features include support for embedding OLE objects (used to embed pictures), and automatic recognition of Internet URLs.  There are also handy properties to tell you the current row and column. It also supports multiple languages and Unicode. It supports multiple levels of UNDO/REDO.  It has many enhanced formatting options such as: kerning, animation, shadow, emboss. It is by Alexander Obukhov, E-mail alex@niiomr.belpak.minsk.by This document will only discuss features available with the Delphi native VCL, which uses the older RICHED32.DLL.

Q: How do I set the background color?
A: SendMessage(RichEdit1.Handle, EM_SETBKGNDCOLOR, 0, clYellow);
  or:
  RichEdit1.Color:=clYellow;

Q: Many times I see programmers use "SendMessage" to send messages that control a RichEdit, but other times I see them use "RichEdit1.Perform". Why are there 2 ways of doing the same thing?
A: The generic Windows solution is to use "SendMessage", with the control's handle as the first argument. This is what you normally see in the Microsoft RichEdit documentation.
 Delphi has a special "shortcut" method of sending a message to a VCL, by using the VCL's own "Perform" method.
 Both of these work fine, but the "Perform" is more efficient. I use them both in my code.
  SendMessage(RichEdit1.Handle, EM_SETBKGNDCOLOR, 0, clYellow);
  is the same as:
  RichEdit1.Perform(EM_SETBKGNDCOLOR, 0, clYellow);

Q: I want to find a specific word and color it blue.
A: This is a good and simple demo for you:
with RichEdit1 do
begin
   SelStart :=FindText('word',0,GetTextLen,[stWholeWord]);
   SelLength := 4; // select the word
   SelAttributes.color:=clBlue;
   SelLength:=0; // de-select the word
end;

Q: I can't seem to figure out how to make a certain word in a specific line of a TRichEdit component become BOLD or ITALIC?
A: Try something like the following:
procedure TForm1.Button1Click(Sender: TObject);
var
  start: integer;
begin
  start := RichEdit1.FindText(Edit1.Text, 0, -1, [stMatchCase]);
  if start > -1 then
    with RichEdit1 do begin
      SelStart  := start;
      SelLength := Length(Edit1.Text);
      SelAttributes.Style := [fsItalic];
      SelLength := 0;
    end;
  RichEdit1.SetFocus;
end;

Q: How do I position the cursor at the beginning of the text?
A: RichEdit1.SelLength := 0;
  RichEdit1.SelStart := 0;                   // here we go
  RichEdit1.Perform( EM_SCROLLCARET, 0, 0 ); // ensure viewport is right

Q: How do I position the cursor at the end of the text?
A: RichEdit1.SelLength := 0;
  RichEdit1.SelStart:=RichEdit1.GetTextLen; // position caret at end
  RichEdit1.Perform( EM_SCROLLCARET, 0, 0 ); // ensure viewport is right

Q: How do I find out where the last character is?
A: Length(RichEdit1.Text)
      or
  RichEdit1.GetTextLen
      or
(I think this last one works?)
LastChar := SendMessage(self.handle, EM_FORMATRANGE, 1, Longint(@Range));

Q: I want to programatically insert text into a RichEdit box.
A:
procedure TForm1.Button1Click(Sender: TObject);
begin
 with RichEdit1 do
 begin
   Lines.Clear;
   SelStart := 0// position to top of box
   SelLength := 0; // nothing is selected
   // Set the desired style for the first text
   SelAttributes.Style := SelAttributes.Style - [fsBold];
   // insert this text at the beginning of the box:
   SelText := 'The dog';
   // Position to point of insertion, but don't select anything.
   // Set the SelAttrib's and insert the text at the point of the caret.
   SelStart := 3;     // point after "The"
   SelLength := 0;
   SelAttributes.Style := SelAttributes.Style + [fsBold];
   SelText := ' big'// it was a big dog!
 end;
end;

Q: How do I indent text in from the left?
A: RichEdit1.Paragraph.LeftIndent :=  20;
  RichEdit1.Paragraph.FirstIndent := 22;
Make sure your text is wrapped in a paragraph (CR at the end).

Q: How do I Print a page or range of pages?
A: Take a look at TGWRich in the Compuserve Delphi File Libraries. It's supposed to handle this. But, the std TRichEdit has no concept of "printed page boundaries".
NOTE: I found this VCL here:
http://www.delphiexchange.com/files/comvisual.html
There's a new freeware Richedit VCL that offers page-print support. I haven't checked it out, but it's called RICHPR.ZIP and it's by Gerrit Wolsink.  It is available at the Delphi  Super Page:
http://SunSITE.ICM.edu.pl/delphi/ftp/d20free/richpr.zip

Q: How can I assign the contents of 1 RichEdit box to another?
A: There is no way to directly assign them over.  As you may have found, if you simply assign the lines over you will lose the formatting info. But, you can do it this way:
var
  ms: TMemoryStream;
begin
  ms:= TMemoryStream.Create;
  RichEdit1.Lines.SaveToStream(ms);
  ms.Position:=0;
  RichEdit2.Lines.LoadFromStream(ms);
  ms.Free;
end;
Note: Most people forget to set the position back to 0 before loading from the stream.

Q: How could I merge 2 RichEdits?  I want to append RichEdit1's data to RichEdit2.
A: Use this:
RichEdit2.Lines.AddStrings(RichEdit1.Lines);
Follow-up Question:
Thank you very much for your answer.
It should work. It compiles fine, but when I run it, it seems like I enter an infinite loop when executing the "AddStrings".
Here is exactly the code I tried:
  RichEdit1.Lines.LoadFromFile('C:\Temp\Test1.RTF');
  RichEdit2.Lines.LoadFromFile('C:\Temp\Test2.RTF');
  RichEdit2.Lines.AddStrings(RichEdit1.Lines);     // Infinite Loop ?
  RichEdit2.Lines.SaveToFile('C:\Temp\Test.RTF');
I tried with different files RTF, but the result is the same). In this precise case, I use the RTF file shipped with Delphi, in  "Delphi 2.0\Demos\RICHEDIT".  Is there a bug around there, or did I goof up ?
Answer:
I haven't tried it with those files, but when I wrote an example the other day to test it I had no problems.

Q: How do I Page forward/backward.
A: I assume you want to scroll up/down one logical screen at a time.
var
 ScrollMessage:TWMVScroll;
...
 {scroll the edit box all the way down}
 ScrollMessage.Msg:=WM_VScroll;
 ScrollMessage.ScrollCode:=sb_Bottom;
 ScrollMessage.Pos:=0;
 RichEdit.Dispatch(ScrollMessage);
 {scroll the edit box all the way up}
 ScrollMessage.Msg:=WM_VScroll;
 ScrollMessage.ScrollCode:=sb_Top;
 ScrollMessage.Pos:=0;
 RichEdit.Dispatch(ScrollMessage);
 {one page-up}
 ScrollMessage.Msg:=WM_VScroll;
 ScrollMessage.ScrollCode:=sb_PageUp;
 ScrollMessage.Pos:=0;
 RichEdit.Dispatch(ScrollMessage);
 {one page-down}
 ScrollMessage.Msg:=WM_VScroll;
 ScrollMessage.ScrollCode:=sb_PageDown;
 ScrollMessage.Pos:=0;
 RichEdit.Dispatch(ScrollMessage);

Q: How do I Move to specific line by index number?
A: For those who don't know, an index number is an embedded bookmark in a RTF document.  I don't think TRichEdit supports index numbers.

Q: How do I change the font attributes for a single line, such as color?
A: Select a section of text and use SelAttributes to change it.
Set SelStart to the start, set SelLength to the length.
and SelAttributes.Color:=your_color.
Don't forget to de-select the text after by setting SelLength to 0.

Q: How do I read a text file into a RichEdit box and give the new text a specific set of attributes?
A: NOTE: This example also shows how to tell if a textfile is already in an RTF format, so it can read it either way.
With RichEdit1 do
begin
  Lines.Clear;
  PlainText := not IsRtfFile(FileName);
  // If PlainText is true, then we will NOT look for RTF tags in the input
  DefAttributes.Name:='Fontname';
  DefAttributes.size:=fontsize;
  DefAttributes.color:=clRed;
  DefAttributes.style:=DefAttributes.style + [fsBold];
  Lines.LoadFromFile(Filename);
 end;
Note: The ONLY time you should mess with "DefAttributes" is when you know the RichEdit box is empty.  
If it may already have text, you must use "SelAttributes" instead. But if you do this, you'll have to select the last char before setting the attributes.  After setting them, de-select the last character.  Those attrib's will remain in effect as you read in the text file.

Q: Does anybody know a way to get the current cursor position (in columns and rows) in a TRichEdit component ?
A: The following should work with TEdits, TMemos, and TRichEdits...
procedure GetEditColRow( CustEdit: TCustomEdit; var Col, Row: Integer );
begin
  Row := SendMessage(CustEdit.Handle, EM_LINEFROMCHAR, CustEdit.SelStart, 0);
  Col := Edit.SelStart - SendMessage(CustEdit.Handle, EM_LINEINDEX, -1, 0);
end;

Q: How do I position the cursor to a certain row and column?
A: 
RichEdit1.SelStart := RichEdit1.Perform(EM_LINEINDEX, Row, 0) + Column;
RichEdit1.Perform(EM_SCROLLCARET, 0, 0);

Q: Have you got any ideas how to activate a RichEdit component when it isn't activated?
A:
  if Screen.ActiveControl is TRichEdit then
     TRichEdit(Screen.ActiveControl).SetFocus;

Q: How can I add some text, and change it's attrib's later?
A: After a Lines.Add the selection start is on the start of the next line. To find the position of the start of a specific line, use the EM_LINEINDEX message. Example:
procedure TForm1.Button1Click(Sender: TObject);
Begin
  With RichEdit1 Do Begin
    // insert some text now
    Lines.Add('normal bold');
    // later, change it's attributes
    SelStart := SelStart-6;    { position to the b of bold }
    SelLength := 4;            { select the word }
    SelAttributes.Style := [fsBold];   { set boldface style for it }
    SelStart := Perform(EM_LINEINDEX, Pred(Lines.Count), 0);
                               { position to start of added line }
    SelLength := 6;            { select "normal" }
    SelAttributes.Color := clRed;  { color it red }
    SelLength := 0;            { remove selection }
    SetFocus;                  { activate control }
  End;
End;

Q: In the above example, you show a EM_LINEINDEX message. Is it a valid Edit Control message, since it is not listed under the standard RICH Edit Control messages?
A: EM_LINEINDEX is a valid message (and a very common one) that can be sent to a RICHEDIT control.  It is used to find your current line number. Delphi's RichEdit documentation is very weak.

Q: I am using the standard edit control messaging capabilities to find out where I am in the RichEdit text at a particular time, based on the current line number. To do this, I use the following syntax:
 Offset := SendMessage(Handle, EM_LINEINDEX, LineNumber, 0);
This returns the current Offset within the control so that I can insert text at that point. This code gets executed several times in a loop,  and it works perfectly until either the Control loses focus or the Application loses focus. When this happens, I get spurious results back from this procedure. Can you help?
A: Sorry, as I noted above, RichEdit often needs to have the focus before it can do it's job.  
I've found that code in the Help files:
procedure TMainForm1.FindDialog1Find(Sender: TObject);
var
 I, J, PosReturn, SkipChars: Integer;
begin
 For I := 0 to RichEdit1.Lines.Count do
 begin
   PosReturn := Pos(FindDialog1.FindText,RichEdit1.Lines[I]);
   if PosReturn <> 0 then {found!}
     begin
       Skipchars := 0;
       for J := 0 to I - 1 do
         Skipchars := Skipchars + Length(RichEdit1.Lines[J]);
       SkipChars := SkipChars + (I*2);
       SkipChars := SkipChars + PosReturn - 1;
       RichEdit1.SetFocus;
       RichEdit1.SelStart := SkipChars;
       RichEdit1.SelLength := Length(FindDialog1.FindText);
     end;
 end;
end;
But when I use this code it scrolls to the end of the text without waiting when it finds the text.
BTW. This code is only working with frHideMatchCase, frHideWholeWord, frHideUpDown on. Have you got any idea how I can make my finddialog working with these functions off?
A: No

Q: How do I change the alignment for all the text in the control?
A: use the following code:
LockWindowUpdate(RichEdit1.handle); //turn off updating of the RichEdit;
RichEdit1.SelectAll;
RichEdit1.Paragraph.Alignment:=taLeftJustify; // switch for other alignments
RichEdit1.SelLength:=0;
LockWindowUpdate(0); //turn on the updating of the RichEdit.
if you need to do it for the current paragraph, use the same code as above, but take out the following lines:
RichEdit1.SelectAll;
RichEdit1.SelLength:=0;
The reason for this is because each paragraph in the RichEdit control has it's own alignment setting and when you set the general one, it does not change the paragraph that you are working on.

Q: What does EM_CharFromPos do?
A:Check out the win32.hlp file and look for the EM_CharFromPos message for the RichEdit control. When sent, it returns the character index and line index of the nearest character to the position that you send it.

Q: When I use the following EM_CHARFROMPOS sendmessage code for a RICH-EDIT under Windows 95, the app raises an access violation:
  res := sendmessage(RichEdit.Handle,EM_CHARFROMPOS,0,MAKELPARAM(50,50));
but the same code together with a normal TMemo (Memo1.Handle instead of RichEdit1.Handle) works fine !! Is there a bug with this message type and the Rich-Edit-Control ?
A: Yes, there is a bug in EM_CharFromPos, but I don't know the details about the bug. See the following Q and A.

Q: I want the cursor to change when is slides over some text with unique attributes like an HTML link or a help link). I have been trying to use EM_CharFromPos, but it doesn't seem to work right.
A: It appears there is an error in the Win32 SDK documentation regarding the EM_CharFromPos message.  This error got into the Windows.pas file.
Secondly, there are two messages needed which won't work in NT 3.51 or before. They are EM_EXSETSEL and EM_EXGETSEL.  Now, I'm sure there is a more elegant solution but this works very nice indeed:
procedure TMainForm.RichEdit1MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var
  mpt: TPoint;
  cr,ccr: TCharRange;
  i: Integer;
begin
  i := SendMessage(RichEdit1.Handle,EM_EXGETSEL,0,longInt(@ccr));
  mpt.x := x;
  mpt.y := y;
  i := SendMessage(RichEdit1.Handle,EM_CHARFROMPOS,0,LongInt(@mpt));
  cr.cpmin := i;
  cr.cpMax := i;
  i := SendMessage(RichEdit1.Handle,EM_EXSETSEL,0,longInt(@cr));
  if (fsBold in Richedit1.SelAttributes.Style) and
     (fsUnderline in RichEdit1.SelAttributes.Style) then
        Screen.Cursor := crHand
  else
        Screen.Cursor := crDefault;
  i := SendMessage(RichEdit1.Handle,EM_EXSETSEL,0,longInt(@ccr));
end;
The declaration for TCharRange is contained in the RichEdit.Pas file. However, if you include it in your uses clause it causes havoc with the Windows.pas file and things just don't work.  Again Richedit.pas is translated directly from Richedit.H (part of the win32 SDK) and MS indicates that error pointed out above spills into this header file also.  I'll leave it to the reader to hunt this down.  To solve the declaration problems just declare the needed type and const declarations in the mainform unit.
The next step is to be able to select all of the specially formated text and use that as an index to a jump to some other topic or traditional help.

Q: Does anybody know how to correct the vertical scrolling of the RichEdit control when the RichEdit control is resized. If the RichEdit height is say 100, and then the enduser sizes the form so the RichEdit' height now appears to be 300.... when the enduser scrolls through the RichEdit's text, the text is still displayed in the upper third of the RichEdit control instead of the entire RichEdit control.  This bug is happening in Delphi's RichEdit demo project.
A: This is caused by a bug in NT 3.51. As a work-around, you can use EM_SETRECT to forcibly resize the editing rectangle. This problem doesn't exist on NT 4+ or Win95.

Q: I need to ensure that the text I add to the RichEdit component is visible.  Currently, I'm doing this by scrolling it all the way to the bottom, then executing one page up.  
Like this:
var
 ScrollMessage:TWMVScroll;
...
 {scroll the edit box all the way down}
 ScrollMessage.Msg:=WM_VScroll;
 ScrollMessage.ScrollCode:=sb_Bottom;
 ScrollMessage.Pos:=0;
 RichEdit.Dispatch(ScrollMessage);
 {one page-up}
 ScrollMessage.Msg:=WM_VScroll;
 ScrollMessage.ScrollCode:=sb_PageUp;
 ScrollMessage.Pos:=0;
 RichEdit.Dispatch(ScrollMessage);
Do you know of an easier way to do this?  And while we are on the subject of messages, where can I find out what the parts of TWMVScroll *mean*?  Delphi's help says what the are, but not what you can do with them.  For example, what does pos do?  I've played with all the values but haven't had much luck understanding them...
A:
After you have added text or moved the SelStart, send a EM_SCROLLCARET message to the RichEdit to force it to scroll the caret into view if it's not visible:
 RichEdit1.Perform(EM_SCROLLCARET, 0 ,0);
The parameters for windows messages are described under the corresponding message identifiers, e.g. WM_VSCROLL. The only problem is that these (API) descriptions constantly refer to the wparam and lparam message parameters, which is exactly what the Delphi message records try to hide.

Q: I want to use TRichEdit to build an RTF file for use in making a
help file...
A: Sorry, you can't.  It doesn't support most of the features needed to
make a help file (footnotes, etc).  You must use MS Word, or some other
truly good RTF editor.

Q: I'm having problems setting the tabstops in a TRichEdit control, I tried the same code on a TMemo and there it works properly...
procedure SetTabstops(..)
var
 TabStops: array[0..3] of word;
begin
 TabStops[0]:=10;
 TabStops[1]:=20;
 TabStops[2]:=30;
 TabStops[3]:=40;
 TS:=SendMessage(RichEdit.Handle, EM_SETTABSTOPS, 4, LPARAM(@TabStops));
end;
Why this doesn't work ? or what am I doing wrong?
A: Tab settings are part of the Paragraph formatting property. Any changes you do to that property apply to selected paragraphs only.
I suggest you do a "RichEdit1.SelectAll;" first, and after setting the tabs do a "RichEdit1.SelLength:=0;". 
By the way, I'm not sure of the syntax of your code.  I have never done this by sending an API message, but I think it will work.

Q: Give me another example of setting tabs.
A: Use the tab sub-property of the paragraph property. You should do this in the OnCreate event of the form, so it will affect all the paragraphs you create.
WARNING: It will not apply to paragraphs that are pasted in.
const
  TabStops := 4;
var
  i: Integer;
begin
  RichEdit1.SelectAll;
  With RichEdit1.Paragraph do
  begin
     TabCount := 40// number of tab stops
     for i:=0 to TabCount do
        Tab[i] := (i+1)*TabStops;
  End;
  RichEdit1.SelLength := 0;
end;

Q: How do I read a BIG FILE into RichEdit?
A: Is the Bible big enough?
procedure TForm1.LoadFile1Click(Sender: TObject);
var
 data: string;
 linecount: integer;
 infile: textfile;
begin
 assignfile(infile,'c:\text\Bible\kjv.txt');
 reset(input);
 if (IOresult<>0) then exit;
 RichEdit1.lines.clear;
 linecount:=0;
 RichEdit1.visible:=false;
 RichEdit1.MaxLength := High (Integer) - 1024;
 while not eof(infile) do
 begin
   readln(infile,data);
   inc(linecount);
   if linecount mod 50 = 0 then
      Application.ProcessMessages;  // This is a friendly thing to do
   RichEdit1.lines.add(inttostr(linecount)+' '+data);
 end;
 closefile(infile);
 RichEdit1.visible:=true;
end;
OR, more simply (but Windows "locks up" while the file is being read):
 RichEdit1.lines.clear;
 RichEdit1.MaxLength := High (Integer) - 1024;
 RichEdit1.PlainText := True;
 RichEdit1.Lines.LoadFromFile('c:\text\Bible\kjv.txt');

Q: Does anyone know how to carry out a word count for the delphi richedit component?
A:
function GetWord: boolean;
var
  s: string; {presume no word > 255 chars}
  c: char;
begin
  result:= false;
  s:= ' ';
  while not eof(f) do
  begin
     read(f, c);
     if not (c in ['a'..'z','A'..'Z'{,... etcetera}]) then
        break;
     s:=s+c;
  end;
  result:= (s<>' ');
end;

procedure GetWordCount(TextFile: string);
begin
  Count:= 0;
  assignfile(f, TextFile);
  reset(f);
  while not eof(f) do
     if GetWord then inc(Count);
  closefile(f);
end;

Q: How do I capture the complete contents of an RTF memo to a bitmap?
A: Well, I surprised myself. I figured out how to do what you want. I looked at the source for the TRichEdit.Print method and found the appropriate way to copy a portion of the rich edit control to another canvas. This isn't very well tested, but should at least get you headed in the right direction.
procedure TForm1.Button1Click(Sender: TObject);
var
 Range: TFormatRange;
 LastChar, MaxLen, LogX, LogY: Integer;
begin
 FillChar(Range, SizeOf(TFormatRange), 0);
 with Image1, Range do
 begin
   LogX := GetDeviceCaps(Canvas.Handle, LOGPIXELSX);
   LogY := GetDeviceCaps(Canvas.Handle, LOGPIXELSY);
   hdc := Canvas.Handle;
   hdcTarget := hdc;
   rc.right := Image1.ClientWidth * 1440 div LogX;
   rc.bottom := Image1.ClientHeight * 1440 div LogY;
   rcPage := rc;
   LastChar := 0;
   MaxLen := GetTextLen;
   chrg.cpMax := -1;
   repeat
     chrg.cpMin := LastChar;
     LastChar := SendMessage(RichEdit1.Handle, EM_FORMATRANGE, 1,
                             Longint(@Range));
   until (LastChar >= MaxLen) or (LastChar = -1);
 end;
 SendMessage(RichEdit1.Handle, EM_FORMATRANGE, 0, 0);
end;
I don't know what you are going to do if your bitmap is not large enough to accommodate the contents of the rich edit. I think the repeat..until in the code above will at least things from blowing up if you run out of room on the bitmap.
Be carefull... There are limits to the size of a bitmap under different version of Windows. If I recall correctly, its somewhere in the range of 2.75 to 3.75 megs. The Video driver limitations will also some into play as well.
Joe C. Hecht (Borland)

Q: Is it possible to dragover a RichEditBox or MemoBox with the mouse and in the meantime moving the caret with the position of the mouse?
A:
You could try sending a WM_LBUTTONDOWN message to the rich edit to simulate the user clicking the mouse, then WM_MOUSEMOVE messages as the mouse moves and
finally a WM_LBUTTONUP when the mouse is released.
Two things to note: first, that you'll need to use Windows.GetFocus to note the focus window before sending the WM_LBUTTONDOWN message.  Then, use Windows.SetFocus to set the focus back to the control that had it immediately afterwards; secondly, you'll need to convert the mouse coordinates to local coordinates for the richedit.

Q: I need to print a huge RTF file.  How can I do this without first waiting for TRichEdit to read the entire file?
A: You can shell out to WordPad with the "/p" commandline switch:
 
ShellExecute(mainForm.handle,
              nil,
              'write.exe',
              'myfile.rtf /p',
              nil,
              SW_HIDE);
(I found that using the WRITE.EXE stub is a bit more universal because WORDPAD.EXE isn't always on the path.)
The "/p" parameter is the undocumented feature. It will launch WordPad, print the file, then close WordPad. And with SW_HIDE, the only thing you see is the Printing status box.
WordPad probably loads as much as it can into memory before printing, but it should be able to handle any size file by segmentation. And WordPad has a pretty small footprint, so it loads and prints fairly quickly. It's also generally on every Win95 system.

Q: I need to know when a TRichEdit's scroll bars are visible or not. The following code, adopted from something similar in the VCL, is not reliable for TRichEdits (with HideScrollBars set to true and ScrollBars set to ssBoth):
function TMyForm.ScrollBarVisible(code: word): boolean;
var
 min, max: Integer;
begin
 result := False;
 if (MyRichEdit.ScrollBars = ssBoth) or
    ((code = SB_HORZ) and (MyRichEdit.ScrollBars = ssHorizontal)) or
    ((code = SB_VERT) and (MyRichEdit.ScrollBars = ssVertical)) then
     begin
       GetScrollRange(MyRichEdit..Handle, code, min, max);
       { !!! Sometimes GetScrollRange returns 0 for min and 100 for max, even
             though no scroll bar is visible. !!! }
       result := (min <> max);
     end{ if }
end;
Are there any other ways to tell if a TRichEdit's scroll bars are visible?
A: Not that I know of.

Q: Why can't I feed in filenames with spaces into my program that uses LoadFromFile?
A: Your problem is in the registry.  You told me before that this problem occurs when double-clicking on .RTF files from Explorer.
When you changed the registered .RTF extension in the registry to point to your program you had to specify the Shell/Open/Command keys and for the Command to the path+program to execute plus a %1 to tell the Explorer to pass the path+filename.ext that you double clicked as parameter string 1. You need to modify this entry to enclosed the %1 in double quotes as follows "%1" this way the embedded spaces will not act as delimiters.
Example:
 HKEY_CLASSES_ROOT
         .dmp                  Passmore file
         Passmore file         Dennis Passmore file
             DefaultIcon       C:\DELPHI32\SYSMANW\SYSMANW.EXE
             Shell
                Open
                   Command     C:\DELPHI32\SYSMANW\SYSMANW.EXE "%1"

Q: How do I read an RTF Blob from a TTable? I want to read from a Memo Blobfield and store the data into RichEdit1.
A: This is my preferred way to do it. I assume that Table1 is already open and positioned on the record you want to update, and that 'RTF' is the fieldname of the Blob in the table:
procedure TForm1.Button1Click(Sender: TObject);
var
  theBStream: TBlobStream;
begin
  RichEdit1.Clear; // in case there is no RTF data in this record
  if Table1.State <> dsBrowse then
     Showmessage('Error: table not in browse mode to read RTF');
  if (not Table1.FieldByName('RTF').IsNull) then
  begin
     BlobStream:=nil;
     try
        BlobStream := Table1.CreateBlobStream(
              Table1.FieldByName('RTF') as TBlobField, bmRead);
        //ShowMessage('rtf blob size=' + IntToStr(BlobStream.Size));
        if BlobStream.Size > 0 then
           RichEdit1.Lines.LoadFromStream (BlobStream);
     finally
        if BlobStream <> nil then
           BlobStream.Free;
     end;
  end;
  RichEdit1.Modified:=False;
  SetModifiedIndicator(False);
end;

Q: How do I save the contents of my RichEdit1 into a Memo Blobfield of a table?
A: This is my preferred way to do it. I assume that Table1 is already open and positioned on the record you want to update, and that 'RTF' is the fieldname of the Blob in the table:
var
  BlobStream : TStream;
begin
  if (not Richedit1.Modified) then
     Exit// exit if the data in the RichEdit1 VCL was not changed
  BlobStream:=nil;
  try
     Table1.Edit;
     BlobStream := Table1.CreateBlobStream(
          Table1.FieldByName('RTF') as TBlobField, bmWrite);
     RichEdit1.Lines.SaveToStream(BlobStream);
     RichEdit1.Modified := False; // we saved the data, so start out fresh
  finally
     if BlobStream <> nil then
     begin
        BlobStream.Free; // IMPORTANT! Free the stream before doing the Post.
        // By free'ing first, it prevents possible trouble if you have an
        // OnDataChanged event handler that may change the current record
        // immediately after the post takes effect.
        try
           Table1.Post;
           dbiSaveChanges(Table1.Handle); // flush cache - in case of power failure
        except
           showmessage('Error while saving text');
        end;
     end;
     if Table1.State = dsEdit then
        Table1.Cancel;
  end;
end;

Q: How do I limit the amount of text that can go into RichEdit?
A: Send the TRichEdit component a EM_EXLIMITTEXT message.

Q: I want to show the user's current line number on a status bar on the bottom of a form,  How do I tell which line I'm on in a file opened in a RichEdit component?
A: The following unit demonstrates:
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    RichEdit1: TRichEdit;
    Label1: TLabel;
    procedure RichEdit1KeyUp(Sender: TObject; var Key: Word;
      Shift: TShiftState);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}
type
  TMemoInfo = record
    Line: integer;
    Col:  integer;
  end;

procedure GetMemoPos(AMemo: TCustomMemo; var AMemoInfo:TMemoInfo);
begin
  with AMemo, AMemoInfo do begin
    { Line number of line containing cursor. }
    Line := SendMessage(Handle, EM_LINEFROMCHAR, SelStart, 0);
    { Offset of cursor in line. }
    Col := SelStart - SendMessage(Handle, EM_LINEINDEX, Line, 0);
  end;
end;

procedure TForm1.RichEdit1KeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  MemoInfo: TMemoInfo;
begin
  GetMemoPos(RichEdit1, MemoInfo);
  with MemoInfo do
    Label1.Caption := IntToStr(Succ(Line)) + ': ' + IntToStr(Succ(Col));
end;

end.

Q: I've got a form with a TListView and a TRichEdit component. I already can drag and drop from the TListView to the TRichEdit component but I inserts the text at the place of the cursor. I'd like to insert this text at the position of the mouse cursor, not the textcursor position.
Has anyone got an answer?
A: You could change the text cursor position so that it corresponds to the mouse position and then insert the text. Send a ButtonDown message to "lock in" the current mouse location as the new cursor location. 
procedure TForm1.RichEdit1DragDrop( Sender, Source: TObject;
 X, Y: Integer );
begin
 if Source is TListView then
 begin
   SendMessage( RichEdit1.Handle, WM_LButtonDown, MK_LBUTTON,
     MakeLParam( X, Y ) );
   SendMessage( RichEdit1.Handle, EM_ReplaceSel, 1,
     LongInt( PChar( ListView1.Selected.Caption ) ) );
 end;
end;

Q: I want to do something special when the user enters a certain word...
A: Try this.  In the form turn KeyPreview to True then respond to the OnKeyPress event.  In the event check for the characters you want and send to the specific objects you want by using the Perform method.  For example:
    if Key = 'A' then begin
       ObjectThatGetsAs.Perform(WM_CHAR, ...);
       Key := char(0);
    end;
This is off the top of my head and it almost 12:00am, but it looks correct.
Give it a try.

Q: I want to dynamically create an RTF control onto a tab sheet.
A:
procedure TMainForm1.Button12Click(Sender: TObject);
var
 RichEdit: TRichEdit;
begin
 NoteBook1.Pages.Add('Untitled');
 TabSet1.Tabs.Assign(Notebook1.Pages);
 TabSet1.TabIndex := NoteBook1.Pages.Count - 1;
 RichEdit := TRichEdit.Create(self); // the form should be the Owner.
 // The Notebook page should be the Parent.
 RichEdit.Parent := Notebook1.Pages.Objects[Notebook1.PageIndex] as TPage;
end;

Q: I've loaded a large text (over 100K) into a RichEdit and now it won't let me add more text.  I have MaxLength = 0.  But if I set Maxlength to (MaxInt-2), it works.  Is this a known bug?
A: This may be a Delphi 2 limit, since Borland used the non-extended GETSEL and SETSEL messages in Delphi 2's TRichEdit control.
Although you can expand the maximum text by using EM_EXLIMITTEXT, you may still run into 64K limits.  The "SelStart" property of a TRichEdit is the same as that for a TMemo - it uses the messages EM_GETSEL and EM_SETSEL, which are limited to values between 0 and 65,535.  If the selection is in the first 64K, you're fine.  Otherwise, these messages return -1.  To get around this, you need to use EM_EXGETSEL and EM_EXSETSEL, which don't have the 64K limit.  To me, this is a bug in TRichEdit - Borland should've used these new messages instead of the 16-bit ones.
In addition, the message EM_LINEFROMCHAR also has a 64K limit.  You should use EM_EXLINEFROMCHAR instead.
It took me a while to track these down, so I figured I could save someone else some time by posting this information here.  What's odd about this is that I never ran into the limit myself - it was users of my application who found it.
I've been able to edit files in excess of 800K without a problem, but several users reported problems with files >64K.  I was never able to reproduce the problems under either Win95 or NT4, and I used the same files that users reported problems with.  Does anyone know why I would never experience the problem while others would?

Q: How many bytes can I store in RichEdit?
A: In theory, it seems to be about 2 Gigabytes. It depends on the property "MaxLength". For some obscure reasons, setting this to 0 limits the plain text size to 64 KByte.  I use a value of:
 Editor.MaxLength := High (Integer) - 1024;
OR, you can do it with a message:
 RichEdit.Perform(EM_EXLIMITTEXT, 0, NewSize);
But, some of Delphi's RTF methods don't use the extended Win32 messages (as of Delphi 2; not sure about Delphi 3), so some features may fail to work when you have lots of text!

Q: If you could help me, I would be most gracious.  I am having trouble reading a TRichEdit from a stream.  (I want to read the whole component and it's contents)?
A:
Note: your problem has nothing whatsoever to do with streams. You just happened to notice it here!
Actually, what you are missing is related to how Windows works, not Delphi. And believe me, to extend the tree analogy, Delphi is a walk in the park while Windows is the deepest darkest part of the Black Forest. Here's the deal. For Delphi components which are windowed controls two things actually get created: 1) the Delphi component itself, and 2) the Windows windowed control. The first occurs in the constructor, like all Delphi objects.
The second occurs *after the constructor*. This is the key to your problem. You showed us your streaming code:
 S := TFileStream.Create (OpenDialog1.FileName,fmOpenRead);
 S.Seek(0,0);
 ...
But you didn't show WHERE you were calling it. This is a crucial bit of information. I would guess you are calling it from a constructor (or OnCreate event). The problem with that is the Windows control hasn't been created yet.
To understand why the Lines property is causing the problem, you must understand how Delphi components interface to Windows. The write method for TRichEdit.Lines looks like this:
procedure TRichEditStrings.Put(Index: Integer; const S: string);
var
 Selection: TSelection;
begin
 if Index >= 0 then
 begin
   Selection.StartPos := SendMessage(RichEdit.Handle, EM_LINEINDEX, Index, 0);
   if Selection.StartPos <> -1 then
   begin
     Selection.EndPos := Selection.StartPos +
       SendMessage(RichEdit.Handle, EM_LINELENGTH, Selection.StartPos, 0);
     SendMessage(RichEdit.Handle, EM_SETSEL, Selection.StartPos,
                 Selection.EndPos);
     SendMessage(RichEdit.Handle, EM_REPLACESEL, 0, Longint(PChar(S)));
   end;
 end;
end;
TRichEditStrings is a private class (descended from TStrings) which Delphi uses to manage the strings, but you don't really need to know this (because it is an implementation detail).
The key here is the SendMessage statements. Instead of managing the strings in memory owned by the component, TRichEdit uses the *standard Windows control itself to manage the strings*. It does so by sending *standard Windows messages to and from the control*.
This is a beautiful encapsulation of the Windows interface, and is one of the great aspects of Delphi.
The problem is that SendMessage requires a valid window handle as its first parameter. The window handle *is not valid when the component is first created*. It only becomes valid at some point after the component's constructor has completed, and the Windows control has been created.
So what you are doing is changing a property which requires a window handle (via SendMessage), but doing so before the window handle has been created. This is why you get the error message, and, by the way, the "Control '' has no parent window" comes from Windows, not Delphi. It is Windows way of saying "Hey, you just sent a message to a window handle but we cannot figure out what the heck you were referring to".
The solution is to defer modifying the Lines property (or any other property which requires a window handle) until you are sure the entire Windows control(and the window handle) have been created. You have two options for this:
1) Use an overridden CreateWnd method. Calling inherited first will ensure the handle is properly created. This is formally the correct place for this, and is what I prefer.
2) Use an overridden Loaded method. Some people like this better because it is easier to understand. The Loaded method gets called as the tail end of the process, so you'll know the handle is created by that point.
Note: this entire discussion relates to many other properties besides the TStrings you mentioned (TMemo.Lines, TRichEdit.Lines, etc.). These all have in common the fact that they use the Windows API for storage (via SendMessage) but there are other properties like this as well.
Rick's Rule: if you see the "Control '' has no parent window" message, move the property assignment to the CreateWnd or Loaded methods. It is that simple.
- Rick Rogers, Fenestra Technologies
UPDATE:
My previous suggestion to set the properties in the CreateWnd or Loaded methods only pertains to component development. If you had tried to set the RichEdit lines property in the component's constructor, you would have gotten the "Control '' has no parent window" message, and moving this assignment to CreateWnd or Loaded would have solved your problem.
Your problem is that ReadComponent reads in a component and its properties, but you haven't set the component's Parent yet, so ReadComponent itself is assigning a value to Lines when the control doesn't have a Parent.
The solution is to use the TReader and TWriter classes, as this unit demonstrates:
unit Unit1;

interface

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

type
 TForm1 = class(TForm)
   RichEdit1: TRichEdit;
   Button1: TButton;
   Button2: TButton;
   procedure Button1Click(Sender: TObject);
   procedure Button2Click(Sender: TObject);
 private
   { Private declarations }
 public
   { Public declarations }
 end;

var
 Form1: TForm1;
implementation

{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
 Stream : TFileStream;
 Filer : TWriter;
begin
 Stream := nil;
 Filer := nil;
 try
   Stream := TFileStream.Create('c:\test.dat', fmCreate);
   Filer := TWriter.Create(Stream, 4096);
   Filer.WriteRootComponent(RichEdit1);
 finally
   Filer.Free;
   Stream.Free;
 end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
 Stream : TFileStream;
 Filer : TReader;
begin
 RichEdit1.Free;
 Stream := nil;
 Filer := nil;
 try
   RichEdit1.Free;
   Stream := TFileStream.Create('c:\test.dat', fmOpenRead);
   Filer := TReader.Create(Stream, 4096);
   Filer.Parent := Self;
   Filer.Owner := Self;
   Filer.ReadRootComponent(RichEdit1);
 finally
   Filer.Free;
 end;
end;

initialization
begin
 RegisterClass(TRichEdit);
end;

end.
Setting the TReader Parent and Owner properties means any objects dynamically created from the stream will have their Parent and Owner properties set before other properties are assigned. Cheers.
- Rick Rogers, Fenestra Technologies

Q: I need a way to have the Found Text shown at the top of RichEdit1 (not at the last line)
A:
procedure TForm1.TabSet1Change(Sender: TObject; NewTab: Integer;
 var AllowChange: Boolean);
var
 S: String;
 CurrentCursorPos, FoundTextPos : longint;
begin
 RichEdit1.SelStart := 0;
 S := TabSet1.Tabs.Strings[NewTab];
 CurrentCursorPos:=RichEdit1.SelStart;
 FoundTextPos:=RichEdit1.FindText('CHAPTER'+' '+S,CurrentCursorPos+1,
                   RichEdit1.GetTextLen,[]);
 if FoundTextPos=-1 then begin
   MessageDlg('Text Not Found',mtError,[mbOK],0);
   RichEdit1.SelStart:=CurrentCursorPos;
 end
 else
 begin
   RichEdit1.SelStart:=FoundTextPos;
   SendMessage(RichEdit1.Handle,EM_SCROLLCARET,0,0); <=== REPOSITION MEMO
 end;
end;

Q: I can't get wingdings to show up even in design mode!
A: Windings is a "symbol style" true type font.  The MS DLL (RICHED32.DLL) that TRichedit wraps doesn't support the use of symbol style fonts.
UPDATE:
The RTF-Control displays these fonts: format a text with WordPad, save it with a .rtf extension and load it into your TRichEdit. Borland simply forgot the CFM_CHARSET flag (it's still undocumented in the Delphi 3 Win32 help file).
Accordingly the Delphi 3 TextAttributes class got a new (documented) CharSet property.
Try something like this :
procedure TForm1.FormCreate(Sender: TObject);
begin
 RichEdit1.Text:='Hello World!'#13#10'Hello World!';
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 with RichEdit1 do
 begin
   SelStart:=0;
   SelLength:=12;
   SelAttributes.Name:='Wingdings';
 end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
 cf:TCharFormat;
begin
 RichEdit1.SelStart:=14;
 RichEdit1.SelLength:=12;
 fillchar(cf,SizeOf(cf),#0);
 with cf do
 begin
   cbSize:=SizeOf(cf);
   dwMask:=CFM_FACE or CFM_CHARSET;
   bCharSet:=DEFAULT_CHARSET;
   bPitchAndFamily:=VARIABLE_PITCH;
   szFacename:='Wingdings';
 end;
 SendMessage(RichEdit1.Handle, EM_SETCHARFORMAT, SCF_SELECTION, LPARAM(@cf));
end;

Q: How can I convert an RTF file to a std text file?
A:
Use the tools you've got. Put a TRichEdit on a form, set Visible := false, adjust Width to avoid word-wrap and execute code similar to the following:
 with RichEdit1 do begin
   Clear;
   PlainText := false;
   Lines.LoadFromFile('QandD.Rtf');
   PlainText := true;
   Lines.SaveToFile('QandD.Txt');
 end;

Q: How can I automatically scroll the text?
 RichEdit.SelLength := 0;
 currentline := Richedit.Perform(EM_LINEFROMCHAR, Richedit.SelStart, 0 );
 Richedit.SelStart := richEdit.perform( EM_LINEINDEX, currentline+1, 0 );
 RichEdit.Perform( EM_SCROLLCARET, 0, 0 );
This should move the caret one line down and scroll the control when the caret moves beyond the bottom edge.
Also, there is this way:
 RichEdit1.perform (WM_VSCROLL, SB_BOTTOM, 0)
 RichEdit1.perform (WM_VSCROLL, SB_PAGEUP, 0)

q: How do I implement context-sensitive help on words in a RichEdit box?
A:
well, the main task is to convert the caret position (i assume you are working in a TMemo or TRichedit) into a line/column postion, get the lineit is in and isolate the word by searching back- and forwards from the caret position to find where the word starts and end. The rest is just a call to Application.helpcommand( HELP_KEY ...);
The key is the first task:
caretRow := Memo1.PerForm(EM_LINEFROMCHAR,Memo1.SelStart,0);
caretCol := Memo1.SelStart-Memo1.Perform(EM_LINEINDEX,caretRow,0);
line := memo1.lines[caretRow];
line[caretCol+1] is the starting position for the search for where the word starts and ends.
Some tips to help you isolate the word:
in Pascal you can access the individual characters in a string like the elements of an array (in fact a string is an array, of characters). So you go about it this way:
 Const
   wordchars = ['a'..'z','A'..'Z'];   { a set of char }
     {add other chars that are allowed in a word}
 Var
   line, theWord: String;
   n, wordstart, wordend: Integer;
 Begin
   line := RichEdit.Lines[currentlineindex];
   n := currentcolumnindex+1;
     { column indices are zero based, string indices are 1-based }
   if line[n] In wordchars Then Begin
     wordstart := n-1;
     While (wordstart >0) and (line[wordstart] In wordchars) Do
       Dec(wordstart);
     { we moved one position to far back, wordstart is now on the
       character before the word starts. Fix that. }
     Inc(wordstart);
     
     wordend := n+1;
     While( wordend <= Length(line)) and (line[wordend] In wordchars) Do
       Inc(wordend);
     { wordend is now one character beyond the end of the word }
       
     theWord := copy(line, wordstart, wordend-wordstart);  
   End {if}
   Else
     theWord := EmptyStr;
       { caret was not on a word }

Q: I need to select a bunch of text to send it to the clipboard, but I don't want to distract the user.  How can I do it?
A:
move the focus to another control (see SetFocus) before you select the text in the RichEdit. The selection is only visible (unless you set the HideSelection property to false) if the RichEdit has the focus.
Just remember that you need to send the focus to a visual control, not a form.

Q: I want to copy the richedit codes to a memo box so I can study them. How can I see these codes, which are normally hidden?
A:
var
 MemStream1: TMemoryStream;
begin
 { copy data from RTFEdit control to a std memo }
 MemStream1 := TMemoryStream.Create;
 RichEdit1.PlainText:=False;  { we want the rtf formatting stuff }
 RichEdit1.Lines.SaveToStream(MemStream1);
 MemStream1.Position := 0;
 RichEdit1.PlainText:=False;  { we want the rtf formatting stuff }
 Memo1.Lines.LoadFromStream(MemStream1);
 MemStream1.Free;
end;
WARNING! Although this should work the same way for DBRichEdit, it doesn't work on Delphi 3's implementation.  I haven't tested other Delphi versions for this bug.  On Delphi 3, DBRichEdit can only write the RTF formatting codes to a data table, not to a stream or a file.  It seems to ignore the PlainText property, and it is always treated as true.