简介:这是为使用 Delphi 与 C++ Builder 的桌面开发者整合的一套完整富文本编辑组件套件,包含 TRichView 21.6 与 ScaleRichView 8.0 两个已注册正式版,并附带 RichViewActions、SRVControls、Report Workshop、RvHtmlImport、RichViewXML 等模块,可在 VCL 程序中实现文档编辑、格式排版、缩放显示、报表设计、网页内容导入与 XML 持久化功能,常用于制作文字处理软件、富文本编辑器或报告生成工具。压缩包内共两千个文件,其中一千零九十五个 C++ 源文件与八百余个头文件构成组件主体,便于查阅类声明和实现细节;另有七十个文本说明、四十六个 XML 配置、四份 PDF 手册及一个 HTML 总览页,辅助快速理解配置方式与开发接口。整体体积约一百一十兆,目录按功能模块划分清晰。所有组件均为注册正式版且提供完整源代码,开发者可深入研究富文本排版、坐标变换、样式管理等底层机制,自由修改扩展以满足业务定制需求,也免去从零自建文本引擎的重复工作。当前已有二百七十四位开发者下载学习,适合对源代码掌控力要求较高的中高级桌面应用工程师选用。
1. 为什么还在用 TRichView:富文本编辑的刚需场景与正式版的价值
TRichView 21.6 + ScaleRichView 8 for Delphi + C++Builder 的已注册正式版,对于还在用原生 RichEdit 做桌面编辑器的开发者来说,是一剂迟来的后悔药。你需要的不是又一个文本框,而是能同时处理段落格式、内嵌图片、页眉页脚、打印预览的完整排版内核。系统 RichEdit 在复杂文档、缩放显示、跨平台输出上总有各种边界问题,浏览器内核又太重。TRichView 系列从 Delphi 时代一路演进至今,21.6 已经是把稳定性和 API 打磨得非常成熟的版本,适合既想要 Word 级编辑体验、又不想背上几十兆浏览器依赖的桌面应用。无论你是做病历编辑、合同编辑还是报告设计器,这套控件都值得重新评估。
2. 安装与注册:拿到“已注册正式版”后先做这三件事
很多人以为双击安装包、点完 Next 就完事了。TRichView 的安装包远不止放几个组件到 IDE 里,尤其是你手里已经是一个“已注册正式版”时,更要确认注册信息是否正确写入了系统,否则编译出来的程序在客户机器上可能退化回试用版。
2.1 安装顺序:先装 TRichView 再装 ScaleRichView 的原因
常见做法是先把 TRichView 21.6 安装包解压到固定目录,比如D:\Components\TRichView,再解压 ScaleRichView 8 到同一目录的上层或并列目录。为什么顺序不能反?因为 ScaleRichView 的安装包默认会依赖 TRichView 的源码路径,它需要把多个*_Scale.inc或资源文件覆盖到 TRichView 的目录中。如果你先装 ScaleRichView,再去安装 TRichView,后者的安装程序可能不会保留前者写入的配置,导致后续 IDE 里能见到两个控件,但引用单元时总是报找不到RVSC相关文件。
我一般的做法是:先解压并安装 TRichView 21.6,编译一次TRichView21.dpk,确认 IDE 工具箱里出现TRichView、TRichViewEdit、RVStyle等图标;再安装 ScaleRichView 8,编译ScaleRichView8.dpk。此时会让选择依赖路径,直接指向刚才那份 TRichView 的源码即可。安装完成后,用安装包自带的示例工程Demos里的RichViewDemo编译一遍,能跑通说明安装顺序没问题。
2.2 检查注册状态与运行期许可证:三个验证点
已注册正式版通常会在安装时向系统注册表写入 License 信息,或者在源码路径下生成一个证书文件。但你不能只看安装界面显示“Registered”就放心。我一般检查三个点:
第一,打开 Delphi 或 C++Builder,在 IDE 的组件面板里找到TRichView,右键看 About 信息,里面会显示Licensed to和一个注册码后缀。如果显示Evaluation,说明注册信息没有和 IDE 版本配对。
第二,在工程代码里加一行运行时验证:
if RichView1.Version < 2100 then raise Exception.Create('TRichView 版本异常');这里的Version属性返回十六进制数值,21.6 对应值会大于 2100。如果编译时提示没有Version属性,多半是你引用的是旧版文件,不是 21.6 的CRVData单元。
第三,运行你的程序,在客户机器上第一次显示 TRichView 时,观察有没有弹出试用期提示框。正式版不会弹。如果弹了,说明你把某些设计期拖拽生成的RVF资源文件当成了运行期依赖,而 License 文件没有被打包进发布目录。真正需要随程序发布的是.rvf、.rtf这些文档资源,并不需要复制 License 文件到客户机。
2.3 在 IDE 里配置搜索路径:避免安装成功但找不到控件
安装完成后,IDE 工具箱里能看到控件,但新建工程拖拽时提示“类未注册”是常见玄学。这往往不是注册表问题,而是 IDE 的 Library Path 没有包含 TRichView 的源码目录。Delphi 和 C++Builder 都是通过搜索路径查找.pas或.hpp文件的。
打开 IDE 的 Tools > Options > Environment Options > Delphi Options > Library - Win32,在 Library Path 里加入D:\Components\TRichView\Source,以及D:\Components\TRichView\Lib\D23(数字对应你的 IDE 版本)。对于 C++Builder,还要额外加入D:\Components\TRichView\Source\CBuilder目录,否则编译#include <RVEdit.hpp>时找不到头文件。
一个容易被忽略的坑是:TRichView 有多个编译中间目录,比如Lib\D23表示 Delphi 23 即 RAD Studio 11,Lib\D24对应 RAD Studio 12。如果你是从旧版本迁移的工程,可能还在引用Lib\D20,导致明明装了 21.6,编译用的却是残留在内存里的旧 DCU。遇到这种诡异问题,我建议直接把Lib目录下与 IDE 版本相关的文件夹全部清空,重新打开 IDE 编译一次,让 21.6 重新生成对应版本的 DCU。
3. 用 TRichView 21.6 实现富文本编辑:核心控件、事件与最小示例
TRichView 21.6 不是单个控件,而是一组按照编辑 / 显示 / 样式三个维度拆分的组件。不看清楚分工,代码会越写越乱。
3.1 从 TRichViewEdit 到 TRichView:编辑与显示的分工
TRichView是一个只读的富文本显示控件,适合做预览、模板输出。TRichViewEdit继承自TRichView,增加了编辑能力,可以响应用户输入、光标移动、选中状态。两者共享同一套底层文档模型,所以你可以把一份文档在编辑控件和显示控件之间互相传递。
我见过不少新人把TRichViewEdit当成一个功能更多的TRichView,直接用它做所有事。这在简单场景没问题,但如果你要做“编辑区 + 右侧缩略预览”,就很容易出问题:两个控件各自维护一套文档,缩略图不同步。正确的做法是让编辑区持有真正的文档,预览区只通过AssignRVData从编辑区同步数据。
ScaleRichView 8 的定位则是排版引擎,它把 TRichView 的内容映射到一张页面上,负责分页、页面背景、页边距、缩放。它不直接替代 TRichViewEdit,而是作为两者之间的“打印通道”。后面第 4 章专门讲。
所以在设计期,我通常会放一个TRichViewEdit作为主编辑器,放一个TRichView作为只读视图,再放一个ScaleRichView8作为打印预览。三者通过同一个TRVStyle控制默认字体和颜色。
3.2 最小步骤:在 Form 上放控件并加载 RTF
下面这个例子演示了从空文档开始,加载一个 RTF 文件,并把它显示在只读视图里:
procedure TMainForm.LoadDocument(const FileName: string); begin // 从编辑器加载 RTF 文件,编辑器和只读视图共享同一份文档 RichViewEdit1.Clear; RichViewEdit1.LoadRTF(FileName); RichViewEdit1.Position := 0; // 只读视图同步编辑器中的文档内容 RichViewPreview.AssignRVData(RichViewEdit1.RichViewData); RichViewPreview.Format; end;这段代码里,Clear是为了释放旧文档,避免反复加载时残留未释放的项。LoadRTF是 TRichView 21.6 内置的 RTF 解析器,不需要额外的 VCL 基础组件。AssignRVData只是拷贝引用关系,不是深层复制,所以编辑后预览会自动跟随格式化。最后调用Format重排所有段落,否则只读视图里显示的还是旧的分行。
这里有一个参数值得注意:LoadRTF的第二个可选参数是bool,表示是否从文件读取时把图片数据也保存到文档里。如果你明确知道 RTF 中的图片不需要展示,可以传False,能显著降低内存占用。但如果是编辑场景,建议保持默认True,否则用户保存回 RTF 时会丢失图片。
3.3 常用编辑操作:插入、删除、设置字体颜色——注意 Item 模型
TRichView 把文档拆成多个Item,文本是文本 Item,图片是图片 Item,分页符、段落分隔符也是 Item。所以“删除最后一段”和“删除一个图片”看似一致,实际要调用不同方法:
// 在当前位置插入一行蓝色文字 procedure TMainForm.InsertBlueText(const S: string); var StyleNo: Integer; begin RichViewEdit1.SetSelectionBounds(1, 0, 1, 0); StyleNo := RichViewEdit1.RVData.Style.AddStyle( clBlue, 14, [], 'Arial'); RichViewEdit1.InsertText(S, StyleNo, 0); RichViewEdit1.Format; end; // 删除当前位置的图片项 procedure TMainForm.DeleteCurrentImage; var ItemNo: Integer; begin ItemNo := RichViewEdit1.CurItemNo; if (ItemNo >= 0) and (RichViewEdit1.GetItemStyle(ItemNo) = rvsPicture) then RichViewEdit1.DeleteItems(ItemNo, ItemNo); end;AddStyle返回一个样式编号,这个编号会一直保留在文档中。同一个文档里相同字体、颜色、字号的文本应该复用同一个StyleNo,而不是每次插入都新建,否则保存到 RTF 时会产生大量冗余样式,文件变大,而且在别的软件里打开时会字体错乱。
删除图片必须用DeleteItems,因为图片 Item 并不对应一段文本。如果你直接SelLength := 0然后SelText := '',实际上可能只删除了图片占位符的属性,图像对象还残留在文档里,导致界面显示空白块。这是新手最容易翻车的地方。
4. ScaleRichView 8 的缩放排版:打印预览和分页的实现要点
ScaleRichView 存在的意义,就是把 TRichView 这种流式排版变成按页排版。它管理页面尺寸、页边距、缩放比例、打印输出。很多人装了它却只在打印时调用PrintPreview,这是没能发挥它的价值。
4.1 ScaleRichView 和 TRichView 的协作模式
ScaleRichView 不是独立工作,它内部关联着一个TRichView对象。常见配置有两种:一种是让 ScaleRichView 直接使用编辑器RichViewEdit1.RichViewData;另一种是把编辑器文档先拷贝到一个只读TRichView里,再让 ScaleRichView 显示那个只读对象。第一种模式适合打印时实时取当前编辑内容;第二种模式适合做“打印预览快照”,因为在预览期间即便用户继续编辑,预览页不会跳动。
我偏爱第二种。实现方式很简单:
procedure TMainForm.UpdatePrintPreview; begin // 先把编辑区内容复制到缓存 TRichView 中 CacheRichView.Clear; CacheRichView.Assign(RichViewEdit1.RichViewData); CacheRichView.Format; // 再把缓存文档交给 ScaleRichView 分页 ScaleRichView1.RichView := CacheRichView; ScaleRichView1.Format; ScaleRichView1.Zoom := 100; end;注意这里的Assign是深拷贝,和上文的AssignRVData不同。深拷贝会让预览的不再受编辑区影响。如果不需要快照,用RichView := RichViewEdit1.RichViewData更省内存,但因为 ScaleRichView 需要排版,你必须在编辑器每次Change后手动调用Format,否则预览滞后。
Format的调用时机很关键。TRichView 在编辑过程中不断格式化局部内容,ScaleRichView 则需要整页重排。如果在OnChange里每一次击键都调用ScaleRichView1.Format,大文档会卡顿。我一般用OnModifyChanged配合一个Timer,延迟 300ms 后再刷新预览。
4.2 正确设置页边距与打印比例
ScaleRichView 的页边距属性不在PageWidth上,而在ScaleRichView1的LeftMargin、RightMargin、TopMargin、BottomMargin,单位是毫米。而 TRichView 的宽度单位是像素,所以需要换算:
// 设置 A4 纸张,左右 2 厘米,上下 2.5 厘米 const MM_PER_INCH = 25.4; PPI = 96; // 屏幕像素密度 begin ScaleRichView1.PageWidth := Round(210 / MM_PER_INCH * PPI); ScaleRichView1.PageHeight := Round(297 / MM_PER_INCH * PPI); ScaleRichView1.LeftMargin := 20; // 毫米 ScaleRichView1.RightMargin := 20; ScaleRichView1.TopMargin := 25; ScaleRichView1.BottomMargin := 25; ScaleRichView1.Format; end;毫米直接赋给LeftMargin,内部会保存为像素。PageWidth必须是整数像素,所以如果你直接写210表示 210 像素,那打印出来会窄得离谱。这个坑在于:不同屏幕的PPI不同,如果你写死96,高分屏上预览比例和打印结果会不一致。正确做法是用 Screen 的PixelsPerInch:
ScaleRichView1.PageWidth := Round(210 / MM_PER_INCH * Screen.PixelsPerInch);打印比例则用ScaleRichView1.Zoom。Zoom表示显示缩放百分比,100就是实际大小。但要注意Zoom不会影响打印输出的 DPI,它只影响屏幕预览。打印时真正决定输出比例的是ScaleRichView1.PrintScale属性,默认是0表示自动适配纸张,如果你强制设为100,那打印出来的文字大小会跟文档里定义的点数一致,但可能会被裁切。
4.3 缩放显示与滚动同步:ZoomBy 和 ScrollBy 的配合
当你要实现“放大镜”功能,比如点击工具栏的放大和缩小按钮,不要直接改Zoom属性再清零滚动条,那样用户会突然跳到文档顶部。正确做法是围绕当前鼠标位置或当前页面中心进行缩放:
procedure TMainForm.ZoomIn; var OldZoom, NewZoom: Double; XCaret, YCaret: Integer; begin OldZoom := ScaleRichView1.Zoom; NewZoom := Round(OldZoom * 1.1); // 每次放大 10% if NewZoom > 400 then Exit; // 记录当前可视区中心对应的文档坐标 XCaret := ScaleRichView1.HScrollPosition + ScaleRichView1.ClientWidth div 2; YCaret := ScaleRichView1.VScrollPosition + ScaleRichView1.ClientHeight div 2; ScaleRichView1.Zoom := NewZoom; ScaleRichView1.HScrollPosition := Round(XCaret * NewZoom / OldZoom - ScaleRichView1.ClientWidth div 2); ScaleRichView1.VScrollPosition := Round(YCaret * NewZoom / OldZoom - ScaleRichView1.ClientHeight div 2); end;这里核心是把当前视口中心映射到文档坐标,缩放后再反向映射回新的滚动位置。直接乘NewZoom/OldZoom是线性近似,对纯文本已经够用。如果你有大量图片,最好调用ScaleRichView1.ScrollToBeginningOfPage先定位到页首,再按页签滚动。
ScrollBy方法用于逐行、逐页滚动。它的参数是逻辑单元还是像素,取决于ScaleRichView1.ScrollByStep的设置。默认情况下,ScrollBy(+1, 0)是向右滚动一行的距离;如果设置ScrollByStep := [ssHorizontal, ssVertical],则一次滚动 80 像素。这个参数如果不在设计期确认,鼠标滚轮会很别扭,因为滚轮事件会同时触发水平和垂直滚动,导致预览区域横向乱跳。
5. 避坑:TRichView 21.6 + ScaleRichView 8 最常见的 5 个坑
这一章写的都是真实项目中反复踩到的问题。每一条都按现象、原因、解决来记录。
5.1 坑一:直接对一个 TRichView 调用 Print 会丢失分页
现象:你用RichViewEdit1.Print打印,打印预览看起来正常,但打印机输出时,表格被截断,跨页内容消失了,甚至出现只有第一页有内容的情况。
原因:TRichView 自身的Print是流式打印,它不感知页面尺寸。它只会把文档内容按照连续流输出到打印机,分页交给打印机驱动。遇到表格、固定行高时,驱动无法正确拆行,于是整块内容被丢弃。
解决:不要用 TRichView 的Print,统一通过 ScaleRichView 打印。ScaleRichView 会在内部把文档分出物理页,再逐页输出。代码里应该这样写:
ScaleRichView1.RichView := RichViewEdit1.RichViewData; ScaleRichView1.Format; ScaleRichView1.Print; // 调用的是 ScaleRichView 的 Print同时还要确认打印机本身的纸张大小和ScaleRichView1.PageWidth一致,否则 ScaleRichView 会自动缩放,导致字号变小。
5.2 坑二:ScaleRichView 的背景色显示异常
现象:预览时页面背景是黑色或花屏,但 TRichViewEdit 里背景正常。
原因:ScaleRichView 依赖Color属性传递页面背景。如果你设置RichViewEdit1.Color := clWhite,但没有把 ScaleRichView 的Color也设置成clWhite,它默认使用clWindow,在某些恰好被系统深色模式设置为深色的机器上,就会显示成深色。另外,ScaleRichView 在每次Format后会把背景色重新绘制,如果你在OnPaint里改颜色,会被覆盖。
解决:统一使用RVStyle的BackgroundColor属性,而不是控件自身的Color。例如:
RVStyle1.BackgroundColor := clWhite; ScaleRichView1.Color := clWhite; ScaleRichView1.PaperColor := clWhite;PaperColor是 ScaleRichView 特有的属性,控制着纸张的颜色,而Color控制着页面外部的底色。两个都要设置。
5.3 坑三:插入图片后文件变大或显示模糊
现象:用户插入一张 300 DPI 的位图,编辑器里看起来挺清晰,但保存为 RTF 后文件大小暴涨到几十 MB,且在其他文字软件里打开图片变模糊。
原因:TRichView 插入图片时默认把图像以原始像素存入文档,不会重新采样。而显示时是缩放到当前缩放比例,所以屏幕上看不出大,但文件体积大。另外,RTF 对 JPEG/PNG 压缩的支持有限,它会把位图转成整个 BMP 块,文件必然大。
解决:插入前先做尺寸归一化。按你想要的显示宽度重新采样图片,而不是直接插入原始图:
procedure TMainForm.InsertImageScaled(Graphic: TGraphic; DisplayWidthMM: Double); var Scale: Double; BMP: TBitmap; begin BMP := TBitmap.Create; try BMP.PixelFormat := pf24bit; // 显示目标宽度(像素) Scale := DisplayWidthMM / 25.4 * Screen.PixelsPerInch; BMP.Width := Round(Graphic.Width * Scale / Graphic.Height); BMP.Height := Round(Scale); BMP.Canvas.StretchDraw(Rect(0, 0, BMP.Width, BMP.Height), Graphic); RichViewEdit1.InsertBitmap(BMP, 0); finally BMP.Free; end; end;DisplayWidthMM是你要在页面上显示多少毫米宽。换算成像素后再缩放,可以避免图片在编辑器里显示清晰但打印出来发虚。如果图片本身是 PNG 带透明层,StretchDraw会丢失 Alpha,建议用TGPImage转换后再插入。
5.4 坑四:在 C++Builder 中链接时出现重复符号
现象:在 Delphi 下编译正常,同样的工程切到 C++Builder,链接时提示duplicate symbol: RVRegisterClipboardFormat之类的错误。
原因:TRichView 21.6 的源码同时包含给 Delphi 的.pas和给 C++Builder 的.hpp/.cpp。如果你把全部.pas都加到 C++Builder 工程的 Unit 列表里,C++ 编译器会把某些.pas也当作 C++ 源文件处理,导致二次编译。
解决:C++Builder 工程里不要手动添加 TRichView 的.pas单元。你只需要在项目属性 > C++ 共享选项 > 附加包含路径里指向Source\CBuilder头文件目录,并在用到的地方#include <RVEdit.hpp>。Linker 会自动从预编译的.lib里取符号。另外,确保库目录里有与当前 IDE 版本对应的.lib,比如LibD23\*.lib,否则链接器会拿到旧版本的符号表。
5.5 坑五:属性继承与单位换算导致字体大小不一致
现象:同一个 RTF 文件,在用户的机器上打开,字号明显比设计时小一号;或者标尺显示的厘米和实际打印出来的长度差几毫米。
原因:TRichView 的字体大小使用一个内部单位twips(1 磅 = 20 twips),而屏幕上显示时Zoom和PixelsPerInch参与换算。你的设计机器可能是 96 DPI,用户机器是 120 DPI,于是字号按比例缩放,但文档里的绝对长度(如缩进)却没有同步缩放。
解决:把文档的度量单位明确设为rvruTwips或rvruPixels,不要让它自动跟随系统 DPI。
RichViewEdit1.RichViewData.UnitMode := rvruTwips;UnitMode决定LeftMargin、TabWidth、Indent这些值解释成什么。设置成rvruTwips后,文档内部的度量不再依赖屏幕 DPI,打印输出和跨机器显示的一致性会好很多。但要注意,如果你在代码里直接用像素设置缩进,需要先除以Screen.PixelsPerInch * 1440再赋给属性,否则数字会偏大 15 倍。
6. 进阶:把 TRichView 做成可保存的文档编辑器:文件格式与自定义选项
到此为止,你已经有能编辑、能预览、能打印的软件了。但真正的产品还差最后一步:如何把用户的文档持久化,并且能在下一个版本中继续读取。
6.1 存储格式选择:RTF、HTML 还是原生 RVF
TRichView 21.6 支持保存为 RTF、HTML、PDF 和它自己的 RVF 格式。我在真实项目中总是优先选 RVF 作为“工程文件”,因为 RVF 保存了 TRichView 的所有内部状态,包括样式编号、图片引用方式、打印设置、表头重复。RTF 适合作为导出格式,HTML 适合做网页预览,PDF 适合交付。
如果用户要求“必须能用 Word 打开”,那就没法避免 RTF。但你要接受一个损失:TRichView 的某些页面级元素(如背景水印、表格单元格边框样式)在导出 RTF 时会被简化。反过来,从 Word 生成的不规范 RTF 再导入 TRichView 时,偶尔会出现段落样式错乱,需要做一次标准化。
RVF 的存读只有两个方法:
RichViewEdit1.RichViewData.SaveRVF('D:\demo.rvf'); // 读取并恢复编辑状态 RichViewEdit1.Clear; RichViewEdit1.RichViewData.LoadRVF('D:\demo.rvf'); RichViewEdit1.Format;注意SaveRVF不会自动保存当前光标位置和选中状态。你可以自己把RichViewEdit1.Position写到一个 INI 文件里,下次打开后恢复:
ini.WriteInteger('State', 'CaretPos', RichViewEdit1.Position); RichViewEdit1.Position := ini.ReadInteger('State', 'CaretPos', 0);6.2 自定义文件扩展名与文档属性
很多人会把 TRichView 保存成.txt或.rtf,但这样用户无法双击自己的自定义扩展名直接打开。其实你可以把 RVF 数据外包一层自定义文件头。我习惯的做法是:文件前 16 字节写魔数和版本号,之后是压缩的 RVF 数据流。
procedure WriteToCustomerFile(const FileName: string); var FS: TFileStream; Buffer: array[0..15] of Byte; MS: TMemoryStream; begin FS := TFileStream.Create(FileName, fmCreate); try // 魔数 'TRVDOC' 和版本 0x2168 Buffer[0] := Ord('T'); Buffer[1] := Ord('R'); Buffer[2] := Ord('V'); Buffer[3] := Ord('D'); Buffer[4] := $21; Buffer[5] := $68; FS.Write(Buffer, 6); MS := TMemoryStream.Create; try RichViewEdit1.RichViewData.SaveRVFToStream(MS); // 简单的 LZ 压缩,实际项目中可以换成 ZLib MS.SaveToStream(FS); finally MS.Free; end; finally FS.Free; end; end;这样做的价值在于:你的文件格式可以包含额外信息,比如版本迁移标志、加密标记。以后升级 21.7 时,可以通过文件头判断老版本格式,做兼容转换。方案落地时,不要只在代码里写死,而是在程序集信息里把文件描述和图标都注册好。
6.3 导出 PDF 前的字体替换策略
TRichView 导出 PDF 依赖TRichViewPDF单元。最常出问题的不是排版,而是字体——用户机器上可能安装了某个字体,但你的服务器上没装,导致 PDF 中文字变成方块。我一般会建一张替换表:
| 文档中使用的字体 | 替换字体 | 适用场景 |
|---|---|---|
| 宋体 | SimSun | 中文正文 |
| 黑体 | SimHei | 标题 |
| Arial | Helvetica | 西文无衬线 |
| Courier New | Courier | 代码块 |
在导出之前遍历RVStyle所有样式,检查字体名是否存在:
procedure CheckFontsBeforePDFExport(RVStyle: TRVStyle); var i: Integer; FontName: string; begin for i := 0 to RVStyle.TextStyles.Count - 1 do begin FontName := RVStyle.TextStyles[i].FontName; if not IsFontInstalled(FontName) then RVStyle.TextStyles[i].FontName := GetFallbackFont(FontName); end; end;这种替换只影响导出,不改动原文档。导出后你最好生成一个映射日志,方便用户反馈。这个技巧虽然简单,但在合同导出、病历存档这类必须保证打印一致性的项目里,能省掉大量售后沟通。
说到底,TRichView 这套组合并没有过时,很多还在日常业务中稳定运行的程序就是用它支撑的。落地时别贪多,先把一个编辑 + 一个预览 + 一个打印通道跑通,再慢慢加表格和图片。我在自己的项目里还吃过直接改Zoom导致滚动条失灵的亏,后来养成了先记录坐标再缩放的代码习惯。希望这份笔记能帮你把正式版的价值真正发挥出来,少走几趟弯路。
本文还有配套的精品资源,点击获取