类似 Word 的编辑器和 RTF 文本操作(使用 Telerik RadRichTextEditor)






3.67/5 (3投票s)
使用 RadRichTextEditor 和 C# 创建一个类似 Word 的软件
引言
Telerik 是一家知名公司——于 2014 年被 Progress Software 收购,该公司提供顶级的软件开发工具,例如高级定制控件、库等。Telerik 主要专注于 .NET 开发工具,最近还销售用于 Web、混合和原生应用程序开发的平台。在本文中,我们将使用 Telerik 的一款 WinForms 控件,即 RadRichTextEditor
和 C#,来了解如何创建类似 Word 的软件。此外,我们将讨论一些关于该控件托管的 RTF 文本的可编程修改的可能性,以生成由我们的应用程序编译的 DOCX/PDF 文件。
先决条件
- Microsoft Visual Studio >= 2015
- Telerik DevCraft Suite(任何版本),可在 https://www.telerik.com/purchase.aspx 购买
对于本文,将假定 Telerik DevCraft Suite 已安装在开发机器上。
如果没有,Telerik 提供了一个非常直接的安装方法。请参考 此链接 以获取 WinForms 库和控件的安装说明。
创建新项目
在 Visual Studio 中,点击“新建项目”,然后在 Visual C# 中选择“Windows 窗体应用程序”模板。为您的解决方案命名,然后点击 OK。
项目创建完成后,查看工具箱:如果之前已安装 Visual Studio Extensions(来自 Telerik DevCraft),Telerik 的控件将会显示出来。在下图,读者可以看到突出显示的 RadRichTextEditor,与其他控件一样,它可以拖放到我们的窗体上。
现在运行,示例将显示一个带有 RadRichTextEditor
的简单 Windows 窗体,但后者已经完全可用。在下图,可以看到一个示例输入文本。要修改文本部分,例如为输入的文本添加粗体或下划线,可以使用熟悉的 Microsoft Word 快捷方式:按 Ctrl+B 会将文本变为粗体,Ctrl+U 会生成带下划线的文本,以此类推。有关 RadRichTextEditor
快捷方式的完整列表,请参阅 本文档。
添加 RichTextEditorRibbonBar 以快速访问常用功能
Telerik 提供了一个非常精美的功能区栏,可以与 RadRichTextEditor
结合使用,方便访问常用命令,从而更轻松地修改文本。RichTextEditorRibbonBar
可以很容易地从工具箱拖放到窗体上,并通过设置其 AssociatedRichTextEditor
属性来链接到 RadRichTextEditor
。
运行示例后,读者现在可以测试这两个控件联合产生的特性:一个相当功能齐全的文本编辑器,而无需编写任何代码。
案例研究前言
但是,这些控件在可编程自动化使用方面提供了一些什么可能性?
我们将在下面看一个小案例研究。假设用户有一组现有的 .docx 模型,他必须从中选择并加载合适的模型,每个模型都包含一些必须替换为给定文本的标签(例如,我们来考虑一个预编译的信函,需要通过添加收件人姓名和其他信息来完成。为了增加复杂性,假设信函必须包含编译器的印章)。
我们将分解完成上述任务所需的代码。从最简单的任务开始,我们将看到如何以编程方式实现每个步骤。在分析结束时,将呈现一个名为 EMWordProcess.cs 的完整类和一个使用它的简短示例。
将文档另存为 .Docx 或 .Pdf 格式
从 RadRichTextEditor
保存文档非常简单。DocxFormatProvider
和 PdfFormatProvider
提供了非常直接的 Export()
方法。在这两种情况下,给定一个 RadDocument
对象(RichTextEditor
的内容,可以从 RadRichTextEditor Document
属性读取)和一个要保存的路径,只需将文档与指向目标路径的已打开流一起传递给 Export()
方法即可。
以下是用于将 RadRichTextEditor.Document
保存到 .Docx 和 .Pdf 文件的两个 RadDocument
的扩展方法
public static void SaveAsDocx(this RadDocument document, string path)
{
var provider = new DocxFormatProvider();
using (Stream output = new FileStream(path, FileMode.OpenOrCreate))
{
provider.Export(document, output);
}
}
public static void SaveAsPdf(this RadDocument document, string path)
{
var provider = new PdfFormatProvider();
using (Stream output = new FileStream(path, FileMode.OpenOrCreate))
{
provider.Export(document, output);
}
}
加载以前编辑过的 .Docx 文件
DocxFormatProvider
也可用于加载/读取 .Docx 文件。在以下方法中,给定现有的 .Docx 路径,文件将被读取并导入(通过 Import
方法)到一个 RadDocument
变量中,该变量以后可以分配给 RadRichTextEditor
的 Document
属性。
public static RadDocument LoadDocument(string path)
{
RadDocument document = null;
var provider = new DocxFormatProvider();
using (var stream = new FileStream(path, FileMode.Open))
{
document = provider.Import(stream);
}
return document;
}
克隆文档
在某些情况下,克隆文档可能很有用。实现以下方法主要是因为 RadDocument
总是按引用传递。如果我们想获取一个 RadDocument
并修改它,这样做也会修改源 RadDocument
,而我们可能不想这样做。当我们看到 merge
方法时,这一点会更清楚。现在,让我们说克隆将导致 RadDocument
的精确副本被创建到另一个 RadDocument
中。可以通过使用 XamlFormatProvider
访问源文档的数据,并使用 Export
方法将其复制到第二个 RadDocument
来实现此功能。
public static RadDocument CloneDocument(this RadDocument document)
{
var copy = new RadDocument();
var provider = new XamlFormatProvider();
string data = provider.Export(document);
copy = provider.Import(data);
return copy;
}
合并文档
合并两个或多个文档意味着我们有一个 RadDocuments
列表,希望从中获得一个单一的文档。这里可以看到克隆的典型用法:由于原始文件不应被修改,因此第一个文档将被克隆到一个新的 RadDocument
中,然后将插入点移动到其末尾,并通过 InsertFragment
方法插入第 n 个文档,该方法允许插入一个 RTF 文本以添加到插入点位置。
public static RadDocument MergeDocuments(RadDocument[] documents)
{
if (documents[0] == null) return null;
RadDocument mergedDocument = CloneDocument(documents[0]);
for (int i = 1; i < documents.Length; i++)
{
if (documents[i] == null) continue;
mergedDocument.CaretPosition.MoveToLastPositionInDocument();
mergedDocument.InsertFragment(new DocumentFragment(documents[i]));
}
return mergedDocument;
}
替换文本
对于替换文本,我们将实现一个带有源文档、要搜索的文本、要替换的文本作为参数的方法。
首先,我们将声明一个 DocumentTextSearch
对象,该对象将用于执行文本搜索,识别匹配文本的位置(TextRange
对象),并利用这些信息应用替换文本。为此,我们使用两个 for
循环:第一个循环基于 search.FindAll(toSearch)
方法的出现次数,将填充一个 TextRanges
列表;第二个循环使用该列表将插入点移动到每个找到的位置,并在原位插入替换文本。
public static void ReplaceText
(RadDocument document, string toSearch, string toReplaceWith)
{
var search = new DocumentTextSearch(document);
var rangesTrackingDocumentChanges = new List<TextRange>();
foreach (var textRange in search.FindAll(toSearch))
{
var newRange = new TextRange
(new DocumentPosition(textRange.StartPosition, true),
new DocumentPosition(textRange.EndPosition, true));
rangesTrackingDocumentChanges.Add(newRange);
}
foreach (var textRange in rangesTrackingDocumentChanges)
{
document.CaretPosition.MoveToPosition(textRange.StartPosition);
document.DeleteRange(textRange.StartPosition, textRange.EndPosition);
var r = new RadDocumentEditor(document);
r.Insert(toReplaceWith);
textRange.StartPosition.Dispose();
textRange.EndPosition.Dispose();
}
}
用图像替换文本
用图像替换文本——程序的第一部分——与替换文本的方式相同:通过第一个 for
each
循环,搜索要替换的文本,填充一个位置列表,或 TextRange
s。第二个循环将遍历该列表,相应地移动插入点,并将找到的文本替换为 FloatingImageBlock
对象,这是一个特殊的 RadDocument
元素,可以从 stream
获取图片。
在下面的示例中,我们声明了一个 160x120 像素的 FloatingImageBlock
,它将托管一个 JPG 图像。请注意,使用的 MemoryStream
必须指向一个有效的 JPG 文件。然后我们继续设置一些属性,例如 WrappingStyle
,将图像放在文本后面,以及 AllowOverlap
。使用 InsertInline
方法,图像将被插入到所需位置。
public static void ReplaceWithImage(RadDocument document, string toSearch)
{
var search = new DocumentTextSearch(document);
var rangesTrackingDocumentChanges = new List<TextRange>();
foreach (var textRange in search.FindAll(toSearch))
{
var newRange = new TextRange
(new DocumentPosition(textRange.StartPosition, true),
new DocumentPosition(textRange.EndPosition, true));
rangesTrackingDocumentChanges.Add(newRange);
}
foreach (var textRange in rangesTrackingDocumentChanges)
{
document.CaretPosition.MoveToPosition(textRange.StartPosition);
document.DeleteRange(textRange.StartPosition, textRange.EndPosition);
var r = new RadDocumentEditor(document);
using (var imgStream = new MemoryStream(File.ReadAllBytes
(@"<PATH_TO_A_VALID_JPG_FILE>")))
{
var imgInline = new FloatingImageBlock(imgStream,
new Telerik.WinControls.RichTextEditor.UI.Size(160, 120), "jpg");
imgInline.VerticalPosition = new FloatingBlockVerticalPosition
(Telerik.WinForms.Documents.Model.FloatingBlocks.
VerticalRelativeFrom.Paragraph, -140);
imgInline.AllowOverlap = true;
imgInline.WrappingStyle = WrappingStyle.BehindText;
r.InsertInline(imgInline);
}
textRange.StartPosition.Dispose();
textRange.EndPosition.Dispose();
}
}
完整的 EMWordProcess 类源代码
以下是 EMWordProcess.cs 类文件的完整源代码,它将用于开发我们的案例研究
using System.Collections.Generic;
using System.IO;
using Telerik.WinForms.Documents;
using Telerik.WinForms.Documents.FormatProviders.OpenXml.Docx;
using Telerik.WinForms.Documents.FormatProviders.Pdf;
using Telerik.WinForms.Documents.FormatProviders.Xaml;
using Telerik.WinForms.Documents.Model;
using Telerik.WinForms.Documents.TextSearch;
namespace TelerikRadTextEdSample
{
public static class EMWordProcess
{
public static void SaveAsDocx(this RadDocument document, string path)
{
var provider = new DocxFormatProvider();
using (Stream output = new FileStream(path, FileMode.OpenOrCreate))
{
provider.Export(document, output);
}
}
public static void SaveAsPdf(this RadDocument document, string path)
{
var provider = new PdfFormatProvider();
using (Stream output = new FileStream(path, FileMode.OpenOrCreate))
{
provider.Export(document, output);
}
}
public static RadDocument LoadDocument(string path)
{
RadDocument document = null;
var provider = new DocxFormatProvider();
using (var stream = new FileStream(path, FileMode.Open))
{
document = provider.Import(stream);
}
return document;
}
public static RadDocument CloneDocument(this RadDocument document)
{
var copy = new RadDocument();
var provider = new XamlFormatProvider();
string data = provider.Export(document);
copy = provider.Import(data);
return copy;
}
public static RadDocument MergeDocuments(RadDocument[] documents)
{
if (documents[0] == null) return null;
RadDocument mergedDocument = CloneDocument(documents[0]);
for (int i = 1; i < documents.Length; i++)
{
if (documents[i] == null) continue;
mergedDocument.CaretPosition.MoveToLastPositionInDocument();
mergedDocument.InsertFragment(new DocumentFragment(documents[i]));
}
return mergedDocument;
}
public static void ReplaceText(RadDocument document,
string toSearch, string toReplaceWith)
{
var search = new DocumentTextSearch(document);
var rangesTrackingDocumentChanges = new List<TextRange>();
foreach (var textRange in search.FindAll(toSearch))
{
var newRange = new TextRange(new DocumentPosition
(textRange.StartPosition, true),
new DocumentPosition(textRange.EndPosition, true));
rangesTrackingDocumentChanges.Add(newRange);
}
foreach (var textRange in rangesTrackingDocumentChanges)
{
document.CaretPosition.MoveToPosition(textRange.StartPosition);
document.DeleteRange(textRange.StartPosition, textRange.EndPosition);
var r = new RadDocumentEditor(document);
r.Insert(toReplaceWith);
textRange.StartPosition.Dispose();
textRange.EndPosition.Dispose();
}
}
public static void ReplaceWithImage(RadDocument document,
string toSearch, string imagePath)
{
var search = new DocumentTextSearch(document);
var rangesTrackingDocumentChanges = new List<TextRange>();
foreach (var textRange in search.FindAll(toSearch))
{
var newRange = new TextRange(new DocumentPosition
(textRange.StartPosition,
true), new DocumentPosition(textRange.EndPosition, true));
rangesTrackingDocumentChanges.Add(newRange);
}
foreach (var textRange in rangesTrackingDocumentChanges)
{
document.CaretPosition.MoveToPosition(textRange.StartPosition);
document.DeleteRange(textRange.StartPosition, textRange.EndPosition);
var r = new RadDocumentEditor(document);
using (var imgStream = new MemoryStream(File.ReadAllBytes(imagePath)))
{
var imgInline = new FloatingImageBlock(imgStream,
new Telerik.WinControls.RichTextEditor.UI.Size(160, 120), "jpg");
imgInline.VerticalPosition = new FloatingBlockVerticalPosition
(Telerik.WinForms.Documents.Model.FloatingBlocks.
VerticalRelativeFrom.Paragraph, -140);
imgInline.AllowOverlap = true;
imgInline.WrappingStyle = WrappingStyle.BehindText;
r.InsertInline(imgInline);
}
textRange.StartPosition.Dispose();
textRange.EndPosition.Dispose();
}
}
}
}
开发一个简单的应用程序
要开发上述案例研究,我们需要将一个 RadRichTextEditor
和一个 RichTextEditorRibbonBar
(与编辑器绑定)拖放到一个 WinForm 上,并创建如列表所示的 EMWordProcess
类。
现在,假设我们有一个简单的 .Docx 文件,如下所示
在应用程序中,括号内的单词将用作标签,以便于识别。CURRENTDATE
标签将用于显示当前日期,RECIPIENT
将被替换为信函收件人的姓名,而 SENDER
将是信函作者的姓名。STAMP
是我们将用图像替换的标签,该图像可以假设代表发件人的印章。
在窗体的 Load()
事件中,可以通过使用 EMWordProcess
类中的 LoadDocument()
方法来简单地加载该 .Docx 文件
可以使用上述方法替换标签。
考虑以下代码片段
EMWordProcess.ReplaceText(radRichTextEditor1.Document,
"{CURRENTDATE}", DateTime.Now.ToLongDateString());
EMWordProcess.ReplaceText(radRichTextEditor1.Document, "{RECIPIENT}", "Mr. Smith");
EMWordProcess.ReplaceText(radRichTextEditor1.Document, "{SENDER}", "The author");
EMWordProcess.ReplaceWithImage(radRichTextEditor1.Document,
"{STAMP}", @"c:\tmp\keyb.jpg");
给定从 radRichTextEditor1.Document
属性读取的 RadDocument
,我们只需继续要求将特定文本/标签替换为另一个文本。对于 STAMP
标签,我们将使用 ReplaceWithImage()
方法,并将一个有效的 JPG 文件路径传递给它。这样运行我们的示例将得到以下结果
这里介绍的是使用已实现文本操作功能的最简单方法,但可以根据实际需求扩展代码。例如,我们可以通过用 SQL Server 数据源中的记录替换标签来生成一定数量的 PDF 文件,或者任何其他可用于满足我们生产需求的自动化。
演示
GIF 文件,如果看不到动画,请在另一个标签页中打开
源代码
上述示例中使用的源代码可以从 https://code.msdn.microsoft.com/C-Word-like-editor-and-RTF-a81219ef 免费下载
请注意,可下载的包不包含任何 Telerik 库和/或控件。要获取这些组件,需要购买许可证(请参阅“先决条件”部分中的链接)。
参考文献
历史
- 2017 年 11 月 21 日:初始版本