【问题标题】:Painting TRichEdit to a canvas将 TRichEdit 绘制到画布上
【发布时间】:2011-10-13 09:31:09
【问题描述】:

我正在尝试在 Delphi XE 中实现支持 RTF 的工具提示窗口。为了呈现富文本,我使用了屏幕外的 TRichEdit。我需要做两件事:

  1. 测量文本的大小。
  2. 绘制文本

为了完成这两个任务,我写了这个方法:

procedure TLookupHintWindow.CallFormatRange(R: TRect; var Range: TFormatRange;
  MustPaint: Boolean);
var
  TextRect: TRect;
begin
  RichText.SetBounds(R.Left, R.Top, R.Right, R.Bottom);
  TextRect := Rect(0, 0,
    RichText.Width * Screen.Pixelsperinch,
    RichText.Height * Screen.Pixelsperinch);

  ZeroMemory(@Range, SizeOf(Range));
  Range.hdc := Canvas.Handle;
  Range.hdcTarget := Canvas.Handle;
  Range.rc := TextRect;
  Range.rcpage := TextRect;
  Range.chrg.cpMin := 0;
  Range.chrg.cpMax := -1;

  SendMessage(RichText.Handle, EM_FORMATRANGE,
    NativeInt(MustPaint), NativeInt(@Range));
  SendMessage(RichText.Handle, EM_FORMATRANGE, 0, 0);
end;

传入的是Range参数,所以我可以在这个方法之外使用计算出来的尺寸。 MustPaint 参数确定是否应计算范围 (False) 或绘制 (True)。

为了计算范围,我调用这个方法:

function TLookupHintWindow.CalcRichTextRect(R: TRect; const Rtf: string): TRect;
var
  Range: TFormatRange;
begin
  LoadRichText(Rtf);

  CallFormatRange(R, Range, False);

  Result := Range.rcpage;
  Result.Right := Result.Right div Screen.PixelsPerInch;
  Result.Bottom := Result.Bottom div Screen.PixelsPerInch;
  // In my example yields this rect: (0, 0, 438, 212)
end;

绘制它:

procedure TLookupHintWindow.DrawRichText(const Text: string; R: TRect);
var
  Range: TFormatRange;
begin
  CallFormatRange(R, Range, True);
end;

问题在于,虽然它计算了一个 438 像素宽和 212 像素高的矩形,但它实际上绘制了一个非常宽(被剪裁)且只有 52 像素高的矩形。

我打开了自动换行,虽然我的印象是不需要。

有什么想法吗?

【问题讨论】:

  • 您不只是在您的工具栏控件上包含一个只读的 TRichTextEdit。听起来比你正在做的要容易得多。
  • 如果我不让这个策略发挥作用,那就是我的计划。原因是工具提示通常具有渐变背景,该背景会被控件隐藏。这将使 RTF 工具提示在同一屏幕上脱颖而出。问题是,我使用相同的调用来进行计算,所以我希望这幅画能正常工作。
  • 你不能让richedit透明,在背景中放一个稍微深一点的渐变吗?请参阅此处的一些透明度代码:stackoverflow.com/questions/7750224/…

标签: delphi delphi-xe off-screen trichedit


【解决方案1】:

您的单位已关闭。考虑一下代码中的这个表达式,例如:

RichText.Width * Screen.Pixelsperinch

左项以像素为单位,右项以像素/英寸为单位,因此结果的单位为像素²/英寸。 em_FormatRange 中使用的矩形的预期单位是缇。如果要将像素转换为缇,则需要:

const
  TwipsPerInch = 1440;

RichText.Width / Screen.PixelsPerInch * TwipsPerInch

您不需要屏幕外富编辑控件。您只需要一个windowless rich-edit control,您可以指示它直接在您的工具提示上进行绘制。 I've published some Delphi code that makes the basics straightforward. 注意它不支持 Unicode,我也没有计划这样做(尽管它可能不会太复杂)。

我的代码中的主要功能是DrawRTF,如下所示,在RTFPaint.pas中。但是,它不太适合您的需求;您想在绘制之前发现尺寸,而我的代码假设您已经知道绘图目标的尺寸。要测量 RTF 文本的大小,请致电 ITextServices.TxGetNaturalSize

自动换行很重要。没有它,控件将假定它有无限的宽度可以使用,并且它只会在 RTF 文本请求它时开始一个新行。

procedure DrawRTF(Canvas: TCanvas; const RTF: string; const Rect: TRect;
  const Transparent, WordWrap: Boolean);
var
  Host: ITextHost;
  Unknown: IUnknown;
  Services: ITextServices;
  HostImpl: TTextHostImpl;
  Stream: TEditStream;
  Cookie: TCookie;
  res: Integer;
begin
  HostImpl := TDrawRTFTextHost.Create(Rect, Transparent, WordWrap);
  Host := CreateTextHost(HostImpl);
  OleCheck(CreateTextServices(nil, Host, Unknown));
  Services := Unknown as ITextServices;
  Unknown := nil;
  PatchTextServices(Services);

  Cookie.dwCount := 0;
  Cookie.dwSize := Length(RTF);
  Cookie.Text := PChar(RTF);
  Stream.dwCookie := Integer(@Cookie);
  Stream.dwError := 0;
  Stream.pfnCallback := EditStreamInCallback;
  OleCheck(Services.TxSendMessage(em_StreamIn, sf_RTF or sff_PlainRTF,
    lParam(@Stream), res));

  OleCheck(Services.TxDraw(dvAspect_Content, 0, nil, nil, Canvas.Handle,
    0, Rect, PRect(nil)^, PRect(nil)^, nil, 0, txtView_Inactive));
  Services := nil;
  Host := nil;
end;

【讨论】:

  • 哇,看起来很棒!今天我会花一些时间来看看这是否是我所需要的。我对自动换行的评论:我知道它的作用以及为什么它很重要,但是当您将 TRichEdit 控件添加到表单时默认情况下它也会打开,因此在运行时创建控件时不需要打开它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-05
  • 2014-09-03
  • 2020-10-14
  • 2013-11-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多