请问谁有RTF格式的中文资料?RichEdit空间的编程实例?(300分)

M

MyCool

Unregistered / Unconfirmed
GUEST, unregistred user!
现在有一个项目要用到RTF希望大家能够提供这方面的资料和例子,最好是VC++下的。
如果分数不够,还有很多。
 
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);
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+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.

==============
 
RTF文件结构分析及其应用
  邱立铭 王键

  RTF是一种非常流行的文件结构,很多文字编辑器都支持它,VB等开发工具甚至还提供了Richtxtbox的控件。编写通用工具的程序员应该考虑在自己的软件中加入读写RTF文件的功能,这样就需要对RTF的结构有充分的了解。而现在最重要的信息发布手段莫过于WWW了,在编辑软件中提供RTF到HTML的转换也是程序员应该考虑的事情。尽管WORD中已经有这个功能,但不能因此就对您的顾客说:“先用我的程序存成RTF,然后再用WORD......”。

  下面将对RTF文件结构分析及其应用进行讨论。

  一、RTF文件结构分析

  RTF的结构并不复杂,但内容繁多,本文不可能一一说明,只能从总体上讨论一下(如果想阅读详尽的RTF文档,则可在Internet上寻找或与笔者联系。)每个RTF文件都是一个文本文件,显示时由RTF阅读器格式化。文件开始处是{/rtf,它作为RTF文件的标志是必不可少的,RTF阅读器根据它来判断一个文件是否为RTF格式。然后是文件头和正文,文件头包括字体表、文件表、颜色表等几个数据结构,正文中的字体、表格的风格就是根据文件头的信息来格式化的。每个表用一对大括号括起来,当中包含了很多用字符“/”开始的命令。例如,某个颜色表如下:

  {/colortbl;/red0/green0/blue0;/red0/green0/blue255;/red0/green255/blue255;/red0/green255/blue0;/red255/green0/blue255;/red255/green0/blue0;/red255/green255/blue0;/red255/green255/blue255;/red0/green0/blue128;/red0/green128/blue128;/red0/green128/blue0;/red128/green0/blue128;/red128/green0/blue0;/red128/green128/blue0;/red128/green128/blue128;/red192/green192/blue192;}

  开始时用/colortbl标明大括号内是颜色表,接着是/red0/green0/blue0,登记了一种颜色,这种颜色的红绿蓝分量都为0。其它表依此类推。文件头之后是正文,正文由版面格式化命令、文字和各种特殊命令组成。其中只有特殊命令用大括号括起来,而版面格式化命令和文字是“开放式”的,从而把文字和命令分离。文件结束时有一个“}”,和第一个“{”对应。在整个文件中,“}”和“{”必须一一对应。这种格式是RTF阅读器和转换器算法的基础。

  RTF格式还有一个特别之处,就是有些字符在命令中有特殊的含义,所以当它们作为文本出现时需要在它们的前面加一个“/”,例如“/”本身就要表示为“//”。事实上,这种形式在大多数编程语言中是很常见的。

  二、算法分析

  本节介绍的算法虽然是针对RTF的读写,但也是一般文件过滤器通用的方法,适用于各种格式文件之间的转换。具体来说就是把各种文件都转换成一种中间格式,再根据要求进行显示或转换。其中有个原则是一定要遵守的:程序必须能过滤掉不认识的格式。各种文件都有其特殊的格式,在转换过程中不可避免会出现格式损失的现象,在算法中要考虑这种情况。对于RTF这类格式化文本文件来说,最重要的是要正确地显示或转换文件的大小、颜色、字体等风格。因此,在程序中应该用一个数据结构把这些信息存起来,这个结构就是所谓的中间格式,怎样规定悉听尊便。以下是其流程图:

  三、难点分析

  在开发过程中我们遇到了不少问题,其中有两个问题特别有意思。

  第一个问题是中文的表示方式。在RTF中中文用命令的形式表示:“/'内码”。内码就是汉字机内码。不过,请注意:RTF是文本文件,内码是用ASCII码来储存的,必须把它转换成数字才能使用。例如,“电子与电脑”在RTF中的形式是:

  /'b5/'e7/'d7/'d3/'d3/'eb/'b5/'e7/'c4/'d4

  第二个是图片的问题,这也是本文的重点。RTF中图片以两种方式存在:第一种方式是直接嵌入,以{/pict开始;第二种方式是作为OLE对象嵌入,这时以{/object开始。当RTF处理器能直接使用OLE时,RTF文件中提供了OLE的数据;否则,文件中直接提供图片的数据,以{/result开始。在使用中最常见的图片格式是内含DIB BITMAP的元文件(METAFILE),这种格式在SDK中没有说明,而且在RTF中是以压缩形式储存的,所以在转换时有一定困难。我们采取了一种比较新的方法:先把META FILE的数据读出来存成一个文件,然后用GetMetafile和PlayMetafile函数把文件中的图形打印在一个内存DC上,最后用抓图的方法把它存成BITMAP文件。以下是具体程序:

  void WmfToBmp(int width,int height,char *metafile)

  {

  //width为图片宽度,height为图片高度,*metafile为元文件名

  static HMETAFILE hmf;

  char num[4];

  FILE *fh;

  HDC hMemDC;

  HBITMAP hbmp,hold;

  BITMAPFILEHEADER hdr;

  LPBITMAPINFOHEADER lpbmpih;

  HGLOBAL hg;

  

  BitmapCount++;

  if(BitmapCount==3)

  {

  width=128;

  height=132;

  }

  if(BitmapCount==1)

  {

  width=80;

  height=50;

  }

  ToString10(BitmapCount,num);

  //把字符串转换成数字的函数

  lstrcpy(bmpFile,FilePath);

  lstrcat(bmpFile,num);

  lstrcpy(gifFile,bmpFile);

  lstrcat(bmpFile,".bmp");

  lstrcat(gifFile,".gif");

  hmf=GetMetaFile(metafile);

  hDisplayDC=CreateDC("DISPLAY",NULL,NULL,NULL);

  hMemDC=CreateCompatibleDC(hDisplayDC);

  hg=GlobalAlloc(GHND,sizeof(BITMAPINFOHEADER));

  lpbmpih=(LPBITMAPINFOHEADER)GlobalLock(hg);

  lpbmpih->biSize=sizeof(BITMAPINFOHEADER);

  lpbmpih->biWidth=width;

  lpbmpih->biHeight=height;

  lpbmpih->biPlanes=1;

  lpbmpih->biBitCount=8;

  lpbmpih->biCompression=0;

  hbmp=CreateCompatibleBitmap(hDisplayDC,width,height);

  GlobalUnlock(hg);

  GlobalFree(hg);

  hold=SelectObject(hMemDC,hbmp);

  SetMapMode(hMemDC,MM_ANISOTROPIC);

  SetWindowOrgEx(hMemDC,0,0,NULL);

  SetViewportExtEx(hMemDC,width,height,NULL);

  PlayMetaFile(hMemDC,hmf);

  hbmp=SelectObject(hMemDC,hold);

  if((fh=fopen(bmpFile,"w+b"))==NULL)

  {

  return;

  }

  hdr.bfType=0x4d42;

  hdr.bfSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD)+width*height;

  hdr.bfReserved1=0;

  hdr.bfReserved2=0;

  hdr.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);

  fwrite((LPSTR)&amp;hdr,sizeof(BITMAPFILEHEADER),1,fh);

  hg=GlobalAlloc(GHND,sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD));

  lpbmpih=(LPBITMAPINFOHEADER)GlobalLock(hg);

  lpbmpih->biSize=sizeof(BITMAPINFOHEADER);

  lpbmpih->biWidth=width;

  lpbmpih->biHeight=height;

  lpbmpih->biPlanes=1;

  lpbmpih->biBitCount=8;

  lpbmpih->biCompression=0;

  lpbmpih->biSizeImage=width*height;

  lpbmpih->biClrUsed=256;

  lpbmpih->biClrImportant=0;

  j=GetDIBits(hMemDC,hbmp,0,height,NULL,(BITMAPINFO*)lpbmpih,DIB_RGB_COLORS);

  lpbmpih->biSize=sizeof(BITMAPINFOHEADER);

  fwrite(lpbmpih,sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD),1,fh);

  GetDIBits(hMemDC,hbmp,0,height,(LPSTR)FileBuffer,(BITMAPINFO*)lpbmpih,DIB_RGB_COLS);

  fwrite(FileBuffer,width*height,1,fh);

  GlobalUnlock(hg);

  GlobalFree(hg);

  fclose(fh);

  DeleteDC(hMemDC);

  DeleteDC(hDisplayDC);

  DeleteObject(hbmp);

  DeleteObject(hold);

  DeleteMetaFile(hmf);

  }//end function WmfToBmp()

  用这种方法还有一个好处,那就是当你从最底层开始开发RTF阅读器时,只需把PlayMetaFile的参数从内存DC换成屏幕DC就可以显示图片了。

  四.RTF格式的扩展

  最后讨论一下RTF格式的扩展。RTF格式作为一个标准应该是统一的,但在某种情况下进行扩展是必要的。最明显的例子是微软的WORD,它有自己独有的RTF命令。如果想使自己的软件在技术上占有优势,也可以通过创造新的RTF命令来实现。例如,如果你希望在软件中支持DHTML,则可在RTF中嵌入{/dhtml或{/java之类的命令。由于RTF阅读器有过滤不认识命令的功能,所以这样做不会影响RTF文件的通用性。

  (广州东风东路福今东39号201暨南大学电子工程系95级 邱立铭 王键 510632)




 
好像RTF文档网上也找不到多少了,还是自己翻译RTF文档来的实在,好在我已经翻译了一不少了
谢谢两位了。
 
顶部