Mega Code Archive
RichEdit Print Preview
Title: RichEdit Print Preview
Question: How do you display a correct Print-Preview for a common
Richedit Control ?
Answer:
The task seems simple, but is not so easy as it seems.
Even Microsoft admits, that there are some problems,
as the Microsoft Articles
Q142320 PRB: Rich Edit Control Improperly Displays Print Preview
Q253262 HOWTO: Store the Contents of an RTF Control in an EMF File
show.
To display a correct Print-Preview with correct
Text-Heights and -Widths, you can draw the RichEdit-Control
on a Metafile, and print this Metafile to the Preview-Canvas :
// r : Destination Rectangle (on Preview or Printer Canvas)
// XInch : Width in Inch of rectangle to print
// YInch : Height in Inch of rectangle to print
// xalign,yalign : Alignment in horizontal and vertical direction
tRichEditAlign = (rTop,rBottom,rCentered);
procedure PrintRichEdit(RichEdit : tMyRichEdit;
Canvas : tCanvas;
r : tRect;
XInch,YInch : extended;
xalign : tAlignment;
yalign : tRicheditAlign);
var Meta : tMetafile;
MetafileRect : tRect;
begin
RichEdit.SelStart:=0;
RichEdit.SelLength:=Length(RichEdit.Text);
RichEdit.Paragraph.Alignment:=xAlign;
if (XInch if Canvas Printer.Canvas then
begin
// Preview with Metafile
Meta:=tMetafile.Create;
Meta.MMWidth :=round(XInch*2.54*1000); // Unit is 0.01 mm
Meta.MMHeight:=round(YInch*2.54*1000);
MetafileRect:=Rect(0,0,Meta.MMWidth,Meta.MMHeight);
with tMetafileCanvas.Create(Meta,0) do
try
RichEdit.PrintPreview(Handle,MetafileRect,yAlign);
finally
free;
end;
Canvas.StretchDraw(r,Meta);
Meta.Free;
end else
// Printing
RichEdit.PrintRect(Printer.Canvas.Handle,r,yAlign);
end;
To extent the functionality I use here a modified
print procedure, that replaces the original
the TCustomRichEdit.Print method :
tMyRichEdit = class(tRichEdit)
public
procedure PrintRect(dc : HDC;r : tRect;align : tRichEditAlign);
procedure PrintPreview(dc : HDC;r : tRect;align : tRichEditAlign);
end;
procedure tMyRichEdit.PrintRect(dc : HDC;
r : tRect;
align : tRichEditAlign);
// see Windows SDK in MSDN :
// in /Platform SDK/User Interface Services/Windows User Interface/...
// .../Controls/Rich Edit Controls
var
Range: TFormatRange; // defined in Unit RichEdit.pas
LogX, LogY, OldMap, h : Integer;
SaveRect: TRect;
begin
FillChar(Range, SizeOf(TFormatRange), 0);
with Printer, Range do
begin
hdc := dc;
hdcTarget := dc;
LogX := GetDeviceCaps(dc, LOGPIXELSX);
LogY := GetDeviceCaps(dc, LOGPIXELSY);
// 1 Twip = 1/20 of a Point, a typographic point is 1/72 Inch
// (Device-Coordinates / DPI -- Inches) *72 * 20 -- Twips
rc.left := round((r.Left / LogX) * 1440);
rc.top := round((r.Top / LogY) * 1440);
rc.right := round((r.Right / LogX) * 1440);
rc.bottom := round((r.Bottom / LogY) * 1440);
rcPage := rc;
SaveRect := rc;
// print entire content
chrg.cpMax := -1;
// ensure printer DC is in text map mode
OldMap := SetMapMode(hdc, MM_TEXT);
// flush buffer
SendMessage(Self.Handle, EM_FORMATRANGE, 0, 0);
try
rc := SaveRect;
chrg.cpMin := 0;
// wParam = 0 : get Text-Height
SendMessage(Self.Handle, EM_FORMATRANGE, 0, Longint(@Range));
chrg.cpMin := 0;
h:=rc.bottom-rc.top;
rc := SaveRect;
case align of
rBottom : rc.top := SaveRect.Bottom-h-5; // 5 Twips = 0.1 mm
rCentered : rc.top := SaveRect.top+
((SaveRect.Bottom-SaveRect.top)-h) div 2;
end;
// wParam = 1 : print (only text that fits in region range.rc)
SendMessage(Self.Handle, EM_FORMATRANGE, 1, Longint(@Range));
// flush buffer
SendMessage(Self.Handle, EM_FORMATRANGE, 0, 0);
finally
SetMapMode(hdc, OldMap); // restore previous map mode
end;
end;
end;
function GetPreciseTextLen(ARichEditCtrl : TRichedit): integer;
var
gtlex : TGetTextLengthEx;
begin
with gtlex do
begin
flags := GTL_PRECISE;
codepage := CP_ACP;
end;
Result := ARichEditCtrl.Perform(EM_GETTEXTLENGTHEX,WPARAM(@gtlex), 0 );
end;
procedure tMyRichEdit.PrintPreview(dc : HDC;
r : tRect;
align : tRichEditAlign);
// Extension of tCustomRichEdit.Print in ComCtrls.pas
var
Range: TFormatRange; // definiert in Unit RichEdit.pas
SaveRect: TRect;
fx,fy : double;
DisplayDC : HDC;
dpiX,dpiY,HSize,VSize,HRes,VRes,h,maxlen : integer;
begin
FillChar(Range, SizeOf(TFormatRange), 0);
with Printer, Range do
begin
hdc := dc;
// Calculate correction-factors :
// fx = GetDeviceCaps(DC,HorzSize) (Screen-Width in mm)
// * GetDeviceCaps(DC,LogPixelsX) (dpi Screen)
// / 25.4 (Factor Inch - mm)
// / GetDeviceCaps(DC,HorzRes) (Screen-Width in Pixel)
//
// you find e.g. fx = 1.18 for Screen.Width = 1024,
// fx = 1.5 for Screen.Width = 800
DisplayDC := CreateDC('DISPLAY',nil,nil,nil);
HSize:=GetDeviceCaps(DC,HorzSize);
VSize:=GetDeviceCaps(DC,VertSize);
dpiX :=GetDeviceCaps(DC,LogPixelsX);
dpiY :=GetDeviceCaps(DC,LogPixelsY);
VRes :=GetDeviceCaps(DC,VertRes);
HRes :=GetDeviceCaps(DC,HorzRes);
if HRes 0
then fx:=HSize*dpiX/25.4/HRes
else fx:=1.0;
if VRes 0
then fy:=VSize*dpiY/25.4/VRes
else fy:=1.0;
hdcTarget:=DisplayDC;
// convert 0.01 mm to 1 Twip
rc.left := round((r.Left * 1440 ) / 2540 );
rc.top := round((r.Top * 1440 ) / 2540 );
rc.right := round((r.Right * 1440 ) / 2540 / fx );
rc.bottom := round((r.Bottom * 1440 ) / 2540 / fy);
rcPage := rc;
SaveRect := rc;
// print entire content
chrg.cpMin := 0;
chrg.cpMax := -1;
MaxLen := GetPreciseTextLen(Self);
SendMessage(Self.Handle, EM_FORMATRANGE, 0, 0);
// wParam = 0 : just measure Text-Size, don't print
SendMessage(Self.Handle, EM_FORMATRANGE, 0, Longint(@Range));
chrg.cpMin := 0;
h:=rc.bottom-rc.top;
rc := SaveRect;
case align of
rBottom : rc.top := SaveRect.Bottom-h;
rCentered : rc.top := SaveRect.top+
((SaveRect.Bottom-SaveRect.top)-h) div 2;
end;
SendMessage(Self.Handle, EM_FORMATRANGE, 1, Longint(@Range));
SendMessage(Self.Handle, EM_FORMATRANGE, 0, 0);
DeleteDC(DisplayDC);
end;
end;