无论是第一种还是第二种都不尽如人意。是否有其他解决方案,或者有商业RTF控件可以进行数据绑定?正常的TextBox不是一个选择,因为我们需要文本格式化。
有什么想法吗?
有一种更简单的方法!
你可以轻松地创建一个附加的DocumentXaml
(或DocumentRTF
)属性,它将允许你绑定RichTextBox
的文档。在你的数据模型中,使用如下代码,其中Autobiography
是一个字符串属性:
<TextBox Text="{Binding FirstName}" />
<TextBox Text="{Binding LastName}" />
<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />
看这里!完全可绑定的RichTextBox
数据!
这个属性的实现非常简单:当属性被设置时,将XAML(或RTF)加载到一个新的FlowDocument
中。当FlowDocument
更改时,更新属性值。
这段代码应该能解决问题:
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
public class RichTextBoxHelper : DependencyObject
{
public static string GetDocumentXaml(DependencyObject obj)
{
return (string)obj.GetValue(DocumentXamlProperty);
}
public static void SetDocumentXaml(DependencyObject obj, string value)
{
obj.SetValue(DocumentXamlProperty, value);
}
public static readonly DependencyProperty DocumentXamlProperty =
DependencyProperty.RegisterAttached(
"DocumentXaml",
typeof(string),
typeof(RichTextBoxHelper),
new FrameworkPropertyMetadata
{
BindsTwoWayByDefault = true,
PropertyChangedCallback = (obj, e) =>
{
var richTextBox = (RichTextBox)obj;
// Parse the XAML to a document (or use XamlReader.Parse())
var xaml = GetDocumentXaml(richTextBox);
var doc = new FlowDocument();
var range = new TextRange(doc.ContentStart, doc.ContentEnd);
range.Load(new MemoryStream(Encoding.UTF8.GetBytes(xaml)),
DataFormats.Xaml);
// Set the document
richTextBox.Document = doc;
// When the document changes update the source
range.Changed += (obj2, e2) =>
{
if (richTextBox.Document == doc)
{
MemoryStream buffer = new MemoryStream();
range.Save(buffer, DataFormats.Xaml);
SetDocumentXaml(richTextBox,
Encoding.UTF8.GetString(buffer.ToArray()));
}
};
}
});
}
可以使用相同的代码来处理TextFormats.RTF或TextFormats.XamlPackage。对于XamlPackage,你需要一个类型为byte[]
的属性而不是string
。
XamlPackage格式比纯XAML具有几个优点,特别是包含图像等资源的能力,它比RTF更灵活、更易于操作。
难以置信这个问题在15个月内没有人指出如何轻松解决。
range.Changed
事件从未被调用。 - Patrick我知道这是一个旧帖子,但是请查看扩展WPF工具包。它有一个支持你想要做的功能的RichTextBox。
我稍微调整了之前的代码。 首先,range.Changed对我没有起作用。 在我将range.Changed更改为richTextBox.TextChanged后,发现TextChanged事件处理程序可以递归调用SetDocumentXaml,因此我提供了保护。我还使用了XamlReader/XamlWriter而不是TextRange。
public class RichTextBoxHelper : DependencyObject
{
private static HashSet<Thread> _recursionProtection = new HashSet<Thread>();
public static string GetDocumentXaml(DependencyObject obj)
{
return (string)obj.GetValue(DocumentXamlProperty);
}
public static void SetDocumentXaml(DependencyObject obj, string value)
{
_recursionProtection.Add(Thread.CurrentThread);
obj.SetValue(DocumentXamlProperty, value);
_recursionProtection.Remove(Thread.CurrentThread);
}
public static readonly DependencyProperty DocumentXamlProperty = DependencyProperty.RegisterAttached(
"DocumentXaml",
typeof(string),
typeof(RichTextBoxHelper),
new FrameworkPropertyMetadata(
"",
FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(obj, e) => {
if (_recursionProtection.Contains(Thread.CurrentThread))
return;
var richTextBox = (RichTextBox)obj;
// Parse the XAML to a document (or use XamlReader.Parse())
try
{
var stream = new MemoryStream(Encoding.UTF8.GetBytes(GetDocumentXaml(richTextBox)));
var doc = (FlowDocument)XamlReader.Load(stream);
// Set the document
richTextBox.Document = doc;
}
catch (Exception)
{
richTextBox.Document = new FlowDocument();
}
// When the document changes update the source
richTextBox.TextChanged += (obj2, e2) =>
{
RichTextBox richTextBox2 = obj2 as RichTextBox;
if (richTextBox2 != null)
{
SetDocumentXaml(richTextBox, XamlWriter.Save(richTextBox2.Document));
}
};
}
)
);
}
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"><Paragraph Foreground="Red"><Bold>Hello</Bold></Paragraph></FlowDocument>
- Istvan Heckl我可以给你一个可行的解决方案,你可以采纳它,但在此之前,我会尝试解释为什么Document
本质上并不是一个DependencyProperty
。
在RichTextBox
控件的生命周期中,Document
属性通常不会改变。 RichTextBox
被初始化为一个FlowDocument
。该文档被显示出来,可以进行编辑和修改,但是Document
属性的基础值仍然是那个FlowDocument
实例。因此,它没有理由成为一个DependencyProperty
,即可绑定的属性。如果您有多个位置引用了这个FlowDocument
,您只需要引用一次。由于它在所有地方都是相同的实例,所以更改将对每个人都可访问。
我认为FlowDocument
可能不支持文档更改通知,尽管我不确定。
话虽如此,下面是一个解决方案。在开始之前,由于RichTextBox
没有实现INotifyPropertyChanged
且Document
不是一个DependencyProperty
,所以当RichTextBox
的Document
属性发生更改时,我们无法接收通知,因此绑定只能是单向的。
创建一个类来提供FlowDocument
。绑定需要存在一个DependencyProperty
,因此这个类继承自DependencyObject
。
class HasDocument : DependencyObject
{
public static readonly DependencyProperty DocumentProperty =
DependencyProperty.Register("Document",
typeof(FlowDocument),
typeof(HasDocument),
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); }
}
}
在XAML中创建一个带有富文本框的窗口(Window)
。
<Window x:Class="samples.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Flow Document Binding" Height="300" Width="300"
>
<Grid>
<RichTextBox Name="richTextBox" />
</Grid>
</Window>
为Window
添加一个类型为HasDocument
的字段。
HasDocument hasDocument;
窗口构造函数应该创建绑定。
hasDocument = new HasDocument();
InitializeComponent();
Binding b = new Binding("Document");
b.Source = richTextBox;
b.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(hasDocument, HasDocument.DocumentProperty, b);
如果你想在XAML中声明绑定,那么你需要让HasDocument
类继承自FrameworkElement
,这样它就可以被插入到逻辑树中。
现在,如果你改变了HasDocument
上的Document
属性,富文本框的Document
也会随之改变。
FlowDocument d = new FlowDocument();
Paragraph g = new Paragraph();
Run a = new Run();
a.Text = "I showed this using a binding";
g.Inlines.Add(a);
d.Blocks.Add(g);
hasDocument.Document = d;
<RichTextBox>
<FlowDocument PageHeight="180">
<Paragraph>
<Run Text="{Binding Text, Mode=TwoWay}"/>
</Paragraph>
</FlowDocument>
</RichTextBox>
看起来这似乎是迄今为止最简单的方法,并且没有显示在任何答案中。
在视图模型中,只需要使用Text
变量。
public FlowDocument Document
{
get { return (FlowDocument)GetValue(DocumentProperty); }
set { SetValue(DocumentProperty, value); }
}
public static readonly DependencyProperty DocumentProperty =
DependencyProperty.Register("Document", typeof(FlowDocument), typeof(RichTextBoxControl), new PropertyMetadata(OnDocumentChanged));
private static void OnDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RichTextBoxControl control = (RichTextBoxControl) d;
FlowDocument document = e.NewValue as FlowDocument;
if (document == null)
{
control.RTB.Document = new FlowDocument(); //Document is not amused by null :)
}
else
{
control.RTB.Document = document;
}
}
这个解决方案可能是你在某处看到的“代理”解决方案。但是,RichTextBox没有Document作为DependencyProperty... 所以你必须用另一种方式来实现...
希望能帮到你。
Guid
的实现来替换_recursionProtection
。这使得它也能在同一窗口中使用多个控件。 public class RichTextBoxHelper : DependencyObject
{
private static List<Guid> _recursionProtection = new List<Guid>();
public static string GetDocumentXaml(DependencyObject obj)
{
return (string)obj.GetValue(DocumentXamlProperty);
}
public static void SetDocumentXaml(DependencyObject obj, string value)
{
var fw1 = (FrameworkElement)obj;
if (fw1.Tag == null || (Guid)fw1.Tag == Guid.Empty)
fw1.Tag = Guid.NewGuid();
_recursionProtection.Add((Guid)fw1.Tag);
obj.SetValue(DocumentXamlProperty, value);
_recursionProtection.Remove((Guid)fw1.Tag);
}
public static readonly DependencyProperty DocumentXamlProperty = DependencyProperty.RegisterAttached(
"DocumentXaml",
typeof(string),
typeof(RichTextBoxHelper),
new FrameworkPropertyMetadata(
"",
FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(obj, e) =>
{
var richTextBox = (RichTextBox)obj;
if (richTextBox.Tag != null && _recursionProtection.Contains((Guid)richTextBox.Tag))
return;
// Parse the XAML to a document (or use XamlReader.Parse())
try
{
string docXaml = GetDocumentXaml(richTextBox);
var stream = new MemoryStream(Encoding.UTF8.GetBytes(docXaml));
FlowDocument doc;
if (!string.IsNullOrEmpty(docXaml))
{
doc = (FlowDocument)XamlReader.Load(stream);
}
else
{
doc = new FlowDocument();
}
// Set the document
richTextBox.Document = doc;
}
catch (Exception)
{
richTextBox.Document = new FlowDocument();
}
// When the document changes update the source
richTextBox.TextChanged += (obj2, e2) =>
{
RichTextBox richTextBox2 = obj2 as RichTextBox;
if (richTextBox2 != null)
{
SetDocumentXaml(richTextBox, XamlWriter.Save(richTextBox2.Document));
}
};
}
)
);
}
<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />
以下是基于Ray Burns答案的解决方案,使用DataBinding和将XAML字符串转换为RichTextBox文档:
ViewModel
TestText = @"<FlowDocument xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""><Paragraph><Bold>Hello World!</Bold></Paragraph></FlowDocument>";
查看
<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding TestText}"/>
RichTextBoxHelper
public class RichTextBoxHelper : DependencyObject
{
public static string GetDocumentXaml(DependencyObject obj) { return (string) obj.GetValue(DocumentXamlProperty); }
public static void SetDocumentXaml(DependencyObject obj,
string value)
{
obj.SetValue(DocumentXamlProperty, value);
}
public static readonly DependencyProperty DocumentXamlProperty = DependencyProperty.RegisterAttached
(
"DocumentXaml",
typeof(string),
typeof(RichTextBoxHelper),
new FrameworkPropertyMetadata
{
BindsTwoWayByDefault = true,
PropertyChangedCallback = (obj,
e) =>
{
var richTextBox = (RichTextBox) obj;
var xaml = GetDocumentXaml(richTextBox);
Stream sm = new MemoryStream(Encoding.UTF8.GetBytes(xaml));
richTextBox.Document = (FlowDocument) XamlReader.Load(sm);
sm.Close();
}
}
);
}
Public Class RichTextBoxHelper
Inherits DependencyObject
Private Shared _recursionProtection As New HashSet(Of System.Threading.Thread)()
Public Shared Function GetDocumentXaml(ByVal depObj As DependencyObject) As String
Return DirectCast(depObj.GetValue(DocumentXamlProperty), String)
End Function
Public Shared Sub SetDocumentXaml(ByVal depObj As DependencyObject, ByVal value As String)
_recursionProtection.Add(System.Threading.Thread.CurrentThread)
depObj.SetValue(DocumentXamlProperty, value)
_recursionProtection.Remove(System.Threading.Thread.CurrentThread)
End Sub
Public Shared ReadOnly DocumentXamlProperty As DependencyProperty = DependencyProperty.RegisterAttached("DocumentXaml", GetType(String), GetType(RichTextBoxHelper), New FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender Or FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, Sub(depObj, e)
RegisterIt(depObj, e)
End Sub))
Private Shared Sub RegisterIt(ByVal depObj As System.Windows.DependencyObject, ByVal e As System.Windows.DependencyPropertyChangedEventArgs)
If _recursionProtection.Contains(System.Threading.Thread.CurrentThread) Then
Return
End If
Dim rtb As RichTextBox = DirectCast(depObj, RichTextBox)
Try
rtb.Document = Markup.XamlReader.Parse(GetDocumentXaml(rtb))
Catch
rtb.Document = New FlowDocument()
End Try
' When the document changes update the source
AddHandler rtb.TextChanged, AddressOf TextChanged
End Sub
Private Shared Sub TextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
Dim rtb As RichTextBox = TryCast(sender, RichTextBox)
If rtb IsNot Nothing Then
SetDocumentXaml(sender, Markup.XamlWriter.Save(rtb.Document))
End If
End Sub
结束类
大家为什么要费尽周折呢?这个方法完美无缺,不需要任何代码。
<RichTextBox>
<FlowDocument>
<Paragraph>
<Run Text="{Binding Mytextbinding}"/>
</Paragraph>
</FlowDocument>
</RichTextBox>
Run
的 Text
属性不是依赖属性,所以甚至无法编译。只有依赖属性支持此类型的绑定。 - Hopeless