65.9K
CodeProject 正在变化。 阅读更多。
Home

在无窗口的 RichEdit 控件中使用

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (18投票s)

2006 年 10 月 10 日

CPOL

5分钟阅读

viewsIcon

130340

downloadIcon

7435

如何使用无窗口的 RichEdit 控件,这是微软文档较少涉及的 API 之一。

Sample Image

引言

自 ActiveX 出现以来,无窗口控件就已存在,当您不希望(或不能拥有)用户界面的每个控件元素都有一个单独的 HWND 时,它们就非常有用。本文介绍 CRichDrawText 类,它是一个简单的包装器,用于无窗口版本的 RichEdit 控件,允许格式化文本在设备上下文中进行大小调整和绘制。

背景

在我正在开发的一个应用程序(Inform 7 的 Windows 版本)中,我需要在 CScrollView 派生类中绘制格式化文本的各个部分。由于该应用程序已经大量使用 RichEdit 控件,因此似乎是完美的控件。使用 CRichEditCtrl 中的 FormatRangeDisplayBand 函数的实验并不令人满意:我需要的是一个真正的无窗口控件。

然而,当我实际尝试使用无窗口 RichEdit 控件时,事情变得更有趣了。MSDN 在这方面的文档非常 sparse。我找到的大部分有用信息都隐藏在 MSDN Knowledge Base 文章 Q270161 中,其余的则通过反复试验确定。希望,如果您需要一个无窗口的 RichEdit 控件,您不必像我一样花费大量时间搜索。

使用代码

要在您的代码中使用该类,请包含 RichDrawText.h,并在某处声明 CRichDrawText 类的一个实例。

要了解 CRichDrawText 的内容所需的垂直空间,请调用 SizeText,传入一个设备上下文和一个宽度为您希望格式化文本的宽度矩形。要实际绘制文本,请调用 DrawText,传入一个设备上下文和边界矩形。

要实际设置格式化文本,CRichDrawText 提供了两种方法。SetText 用给定的 Unicode 字符串替换无窗口控件中的所有文本,而 Range 返回一个 ITextRange COM 接口指针,用于无窗口控件中的一段文本。ITextRange 是 TOM(文本对象模型)的一部分,它是 RichEdit 控件的一个被低估的部分,它提供了比通常的 RichEdit EM_ 消息更快速、更强大的文本操作。TOM 的描述超出了本文的范围,但本文提供的示例程序以及 MSDN 文档应该足以让您入门。

ITextHost 实现

CRichDrawText 构造函数中,我们通过调用 CreateTextServices 函数来创建一个无窗口的 RichEdit 控件,我们必须向其传递一个 ITextHost 接口的实现。CRichDrawText 类提供了一个最小的实现,它做了最少的事情来能够调整 RichEdit 文本的大小并绘制它。需要实现哪些方法是通过调试器观察哪些方法被调用来确定的。其中大多数方法都很直接,但是

HRESULT CRichDrawText::XTextHost::TxNotify(DWORD iNotify, void *pv)
{
  return S_OK;
}
TxNotify 实现必须返回 S_OK,即使它不对传入的通知消息进行任何处理。如果它返回错误代码,则大小调整和绘制调用将会失败。

另一个需要注意的地方是 TxGetCharFormat,它必须返回指向 CHARFORMATW 结构而不是 CHARFORMAT 的指针:即使在 Windows 95 上,也必须使用该结构的 Unicode 版本。

CreateTextServices 的返回值

成功调用 CreateTextServices 后,我们现在拥有一个 IUnknown COM 指针,根据 MSDN,我们可以对其使用 QueryInterface 来获取一个 ITextServices COM 指针。然而,我最初的尝试都失败了:我总是从 QueryInterface 调用中得到 E_NOINTERFACE

问题在于从 riched20.lib 链接的 IID_ITextServices 是错误的。感谢微软……从 Knowledge Base 文章 Q270161 链接的代码包含正确的 IID。

const IID IID_ITextServices = {
  // 8d33f740-cf58-11ce-a89d-00aa006cadc5
  0x8d33f740, 0xcf58, 0x11ce, {0xa8, 0x9d, 0x00, 0xaa, 0x00, 0x6c, 0xad, 0xc5}
};
当我们在 CRichDrawText 源文件中包含这个(它将优先于 riched20.lib 中的那个被链接)时,我们就能获取回一个 ITextServices COM 指针。

调用 ITextServices 方法

实现了 ITextHost 并获得了 ITextServices 接口后,我们就准备好实际调用方法来调整文本大小和绘制文本了。

CRichDrawText::SizeText 方法调用 ITextServices::TxGetNaturalSize 来计算所需的高度。这里不得不通过反复试验来确定所有参数应该是什么。

  • hicTargetDevptd 都是 NULL,尽管文档没有说允许空值。对于绘制到显示设备上下文,很难看到还能传递什么。
  • psizelExtent 是一个指向 SIZEL 结构的指针,其两个成员变量都设置为 -1。MSDN 声称此参数“目前未使用”,但这似乎并非如此:传递 NULL 会导致 riched20.dll 中发生访问冲突。传递零(或小值)似乎会影响返回的大小矩形。鉴于缺乏文档,使用 {-1,-1} 似乎是一种合理的方法。

CRichDrawText::DrawText 方法调用 ITextServices::TxDraw 来绘制 RichEdit 文本。该方法的参数问题稍微少一些。但是

  • pvAspectNULL,尽管文档没有说明是否允许。考虑到该参数的类型是 void*,并且文档称其为“用于绘图优化的信息指针”,这可能值得一项“无用文档奖”。
  • hicTargetDevptd 再次都是 NULL
  • lViewId 是零。MSDN 声称此参数未使用,但在 Platform SDK 的 TextServ.h 中,enum TXTVIEW 被定义为“TxDraw lViewId 参数的有用值”。

结论

对于文档不完善的接口,最困难的问题通常是让它们首先工作起来。我希望本文能为任何研究无窗口 RichEdit 控件的人提供足够的起点,使他们能够实现所需的功能。总之,如果事情很容易,那还有什么乐趣呢?

© . All rights reserved.