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

WPF 富文本编辑器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (73投票s)

2009 年 6 月 10 日

CPOL

4分钟阅读

viewsIcon

370043

downloadIcon

15337

可绑定的 WPF WYSIWYG 文本编辑器。

依赖项

引言

本文将为您提供一些使用 WPF 的 RichTextbox 的技巧/窍门。众所周知,内置的 WPF RichTexbox 并不提供我们所需的一些功能,因此,如果您需要在 WPF 项目中使用 RichTexbox,您应该知道您至少需要自己实现一部分功能。在本文中,我将简要介绍如何使 WPF RichTextbox 可绑定,如何显示 WPF 中的 HTML,以及如何创建一个带工具栏的 RichTextbox 编辑器。

目录

  • 可绑定的 RichTextbox
  • RichText 编辑器
  • HTML 到 XAML 转换
wpf-rich-text-editor

可绑定的 Rich Textbox

很多人在网上询问如何绑定 RichTextbox。是的。在我看来,Rich Textbox 应该是可绑定的,但我不确定为什么 WPF 中 RichTextDocument 属性不是依赖属性(有人可以问我这个问题吗?)但是像我们这样使用 MVVM 模式的人需要在 RichTextboxViewModel 的属性之间进行绑定。我们该如何实现这一点呢?

嗯,我们可能需要一个自定义属性,该属性可以在该控件上进行绑定,所以我想到的第一件事就是 附加属性。它可能有许多定义,但我的理解是它是一个可以附加到控件的自定义属性。例如:AA 属性附加到 B 控件,依此类推。

您可以看看 Sam 在他的帖子中是如何为 Passwordbox 实现绑定支持的。(暂时忽略内存中密码的加密等问题。)我们将遵循这种方法来实现 RichTextbox 的绑定支持。

您可能首先注意到的是 RichTextbox 具有 Document 属性。因此,您可以创建一个附加属性来包装 RichTextbox.Document 属性。请看 siz 下面的实现(链接:参考)。

class RichTextboxAssistant : DependencyObject
{
public static readonly DependencyProperty DocumentProperty =
DependencyProperty.Register("Document",
typeof(FlowDocument),
typeof(RichTextboxAssistant),
new PropertyMetadata(new PropertyChangedCallback(DocumentChanged)));

private static void DocumentChanged(DependencyObject obj,
			DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("Document has changed");
}

public FlowDocument Document
{
get { return GetValue(DocumentProperty) as FlowDocument; }
set { SetValue(DocumentProperty, value); }
}
}

但是......我的天!为什么使用 FlowDocument 如此困难?为什么我们必须调用 Content.StartEnd 才能获取文本?为什么不提供一个名为 Textstring 类型属性呢?

是的。与 string 类型相比,使用 FlowDocument 确实不那么简单。我们在实现此功能时也感觉到了。我们做了什么?我们决定将 Document 属性(一种 FlowDocument 类型)更改为“BoundDocument”(一种 string 类型)。所以,新的代码如下所示。如您所见,它比以前稍微复杂一些,因为我们在这里处理了所有复杂的事情。

public static class RichTextboxAssistant
{
public static readonly DependencyProperty BoundDocument =
DependencyProperty.RegisterAttached("BoundDocument",
		typeof(string), typeof(RichTextboxAssistant),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnBoundDocumentChanged));

private static void OnBoundDocumentChanged(DependencyObject d,
			DependencyPropertyChangedEventArgs e)
{

RichTextBox box = d as RichTextBox;

if (box == null)
return;

RemoveEventHandler(box);

string newXAML = GetBoundDocument(d);

box.Document.Blocks.Clear();

if (!string.IsNullOrEmpty(newXAML))
{

using (MemoryStream xamlMemoryStream =
	new MemoryStream(Encoding.ASCII.GetBytes(newXAML)))
{

ParserContext parser = new ParserContext();
parser.XmlnsDictionary.Add
	("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
parser.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
FlowDocument doc = new FlowDocument();

Section section = XamlReader.Load(xamlMemoryStream, parser) as Section;
box.Document.Blocks.Add(section);
}
}

AttachEventHandler(box);
}

private static void RemoveEventHandler(RichTextBox box)
{
Binding binding = BindingOperations.GetBinding(box, BoundDocument);

if (binding != null)
{
if (binding.UpdateSourceTrigger == UpdateSourceTrigger.Default ||
binding.UpdateSourceTrigger == UpdateSourceTrigger.LostFocus)
{
box.LostFocus -= HandleLostFocus;
}
else
{
box.TextChanged -= HandleTextChanged;
}
}
}

private static void AttachEventHandler(RichTextBox box)
{

Binding binding = BindingOperations.GetBinding(box, BoundDocument);
if (binding != null)
{
if (binding.UpdateSourceTrigger == UpdateSourceTrigger.Default ||
binding.UpdateSourceTrigger == UpdateSourceTrigger.LostFocus)
{
box.LostFocus += HandleLostFocus;
}
else
{
box.TextChanged += HandleTextChanged;
}
}
}

private static void HandleLostFocus(object sender, RoutedEventArgs e)
{
RichTextBox box = sender as RichTextBox;
TextRange tr = new TextRange(box.Document.ContentStart, box.Document.ContentEnd);
using (MemoryStream ms = new MemoryStream())
{
tr.Save(ms, DataFormats.Xaml);
string xamlText = ASCIIEncoding.Default.GetString(ms.ToArray());
SetBoundDocument(box, xamlText);
}
}

private static void HandleTextChanged(object sender, RoutedEventArgs e)
{
// TODO: TextChanged is currently not working!
RichTextBox box = sender as RichTextBox;
TextRange tr = new TextRange(box.Document.ContentStart,
box.Document.ContentEnd);

using (MemoryStream ms = new MemoryStream())
{
tr.Save(ms, DataFormats.Xaml);
string xamlText = ASCIIEncoding.Default.GetString(ms.ToArray());
SetBoundDocument(box, xamlText);
}
}

public static string GetBoundDocument(DependencyObject dp)
{
return dp.GetValue(BoundDocument) as string;
}

public static void SetBoundDocument(DependencyObject dp, string value)
{
dp.SetValue(BoundDocument, value);
}
}

是的。就这样。您现在可以将此附加属性与 string 绑定,而不是 Flow Document

<RichTextBox attached:RichTextboxAssistant.BoundDocument="{Binding Text}" Height="92" />

RichText 编辑器

在实现了 WPF RichTextbox 的绑定支持后,我们有了一个新需求,就是需要开发一个 RichText 编辑器(类似于 TinyMCE)。所以,我们很快创建了一个名为 RichTextEditor.xaml 的新用户控件,并在其中放置了一个带有我们附加属性的 RichTextbox。几分钟后,我们得到了一个 WPF RichText 编辑器,如下所示。(由于本文中已经有很多代码片段,我在这里就不再一一列出了。请随时在示例项目中查看 RichTextEditor.xaml。)

wpf-rich-text-editor1

HTML 到 XAML 转换

我们的经理对我们快速而酷炫的 WPF Rich Textbox 实现方案非常满意,所以我们将所做的更改提交到了 SVN,然后,持续集成将我们的最新更改集成到新的构建中,这样 QA 的人员就可以开始测试我们的新功能了。

几个小时后,我们开始收到 QA 关于我们新的 RichText 编辑器的错误报告。哎哟!

发生的情况是,有一个 ASP.NET 网站在使用相同的服务和相同的表。ASP.NET 团队在该网站上使用了 TinyMCE,一个 Javascript WYSIWYG 编辑器,因此该编辑器生成的 HTML 标签被保存在数据库中。这就是为什么我们的 WPF RichText 编辑器无法渲染这些 HTML 标签的原因。同样,他们的 TinyMCE 也对我们的 XAML 标签有问题。

wysiwyg-javascript-editor

那么,我们该怎么办?哈!我能猜到您现在在想什么。是的,一个转换器!我们需要一个能够将 HTML 转换为 XAML(反之亦然)的转换器。幸运的是,Microsoft 提供了一组类可以为您进行转换。您可以从 此链接 获取这些类的副本。(谢谢你!Microsoft)我们将这些类嵌入到我们的应用程序中,并像下面这样更改了代码以支持转换。

public static string GetBoundDocument(DependencyObject dp)
{
var html = dp.GetValue(BoundDocument) as string;
var xaml = string.Empty;

if (!string.IsNullOrEmpty(html))
xaml = HtmlToXamlConverter.ConvertHtmlToXaml(html, false);

return xaml;
}

public static void SetBoundDocument(DependencyObject dp, string value)
{
var xaml = value;
var html = HtmlFromXamlConverter.ConvertXamlToHtml(xaml, false);
dp.SetValue(BoundDocument, html);
}

就是这样。我已经将所有源代码都附加在 zip 文件中。请随时下载并随意使用。但是,嘿!如果您发现任何不酷的地方,请不要忘记提供反馈。

这是我的示例的样子。祝您 WPF 愉快!!!:)

wpf-super-cool-rich-text-editor
© . All rights reserved.