到目前为止,Document
在WPF中进行DataBinding的工作RichtextBox
,我看到了2个解决方案,它们是从RichtextBox
和派生的,并加上一个DependencyProperty
带有“代理”的解决方案。
第一个或第二个都不令人满意。有人知道另一种解决方案,或者是可以进行数据绑定的商业RTF控件吗?正常TextBox
不是替代方案,因为我们需要文本格式。
任何的想法?
Answers:
我知道这是一篇旧文章,但请查看Extended WPF Toolkit。它具有一个RichTextBox,它支持您尝试执行的操作。
有一种更简单的方法!
您可以轻松创建一个附加的DocumentXaml
(或DocumentRTF
)属性,该属性可让您绑定RichTextBox
的文档。它的用法如下,Autobiography
数据模型中的string属性为:
<TextBox Text="{Binding FirstName}" />
<TextBox Text="{Binding LastName}" />
<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />
瞧!完全可绑定的RichTextBox
数据!
此属性的实现非常简单:设置该属性后,将XAML(或RTF)加载到new中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,您将拥有type属性,byte[]
而不是string
。
与普通XAML相比,XamlPackage格式具有多个优点,尤其是包含图像等资源的功能,并且与RTF相比,它更灵活,更易于使用。
很难相信这个问题已经搁置了15个月,而没有人指出这样做的简单方法。
range.Changed
事件永远不会被调用。
我可以给你一个确定的解决方案,你可以用它去,但之前,我做我要去尝试解释为何文档是不是一个DependencyProperty
开始。
在RichTextBox
控件的生存期内,Document
属性通常不会更改。用RichTextBox
初始化FlowDocument
。该文档将显示出来,可以通过多种方式进行编辑和修改,但是该Document
属性的基本值仍然是的一个实例FlowDocument
。因此,实际上没有理由应将其设为DependencyProperty
,即Bindable。如果您有多个引用此的位置,则FlowDocument
只需引用一次。由于到处都是同一实例,因此所有人都可以访问更改。
我并不认为FlowDocument
支持文件更改通知,但我不知道。
话虽如此,这是一个解决方案。在开始之前,由于RichTextBox
未实现,INotifyPropertyChanged
并且Document不是a DependencyProperty
,所以当RichTextBox
Document属性更改时,我们没有任何通知,因此绑定只能是OneWay。
创建一个将提供的类FlowDocument
。绑定需要存在a 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); }
}
}
Window
在XAML中使用富文本格式创建一个。
<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
一个type字段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
以便可以将其插入逻辑树中。
现在,如果您要更改的Document
属性HasDocument
,则富文本框的属性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;
我已经稍微调整了以前的代码。首先是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));
}
};
}
)
);
}
<RichTextBox>
<FlowDocument PageHeight="180">
<Paragraph>
<Run Text="{Binding Text, Mode=TwoWay}"/>
</Paragraph>
</FlowDocument>
</RichTextBox>
到目前为止,这似乎是最简单的方法,并且未在任何这些答案中显示。
在视图模型中只有Text
变量。
创建一个具有名为RTB的RichTextBox的UserControl。现在添加以下依赖项属性:
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 ...因此您必须以另一种方式来实现...
高温超导
这是Lolo的答案的VB.Net版本:
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
末级
这个VB.Net版本适合我的情况。我删除了线程集合信号,而不是使用RemoveHandler和AddHandler。另外,由于FlowDocument一次只能绑定到一个RichTextBox,因此我检查了RichTextBox的IsLoaded = True。让我们从我如何在使用ResourceDictionary而不是Window的MVVM应用程序中使用该类开始。
' Loaded and Unloaded events seems to be the only way to initialize a control created from a Resource Dictionary
' Loading document here because Loaded is the last available event to create a document
Private Sub Rtb_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
' only good place to initialize RichTextBox.Document with DependencyProperty
Dim rtb As RichTextBox = DirectCast(sender, RichTextBox)
Try
rtb.Document = RichTextBoxHelper.GetDocumentXaml(rtb)
Catch ex As Exception
Debug.WriteLine("Rtb_Loaded: Message:" & ex.Message)
End Try
End Sub
' Loaded and Unloaded events seems to be the only way to initialize a control created from a Resource Dictionary
' Free document being held by RichTextBox.Document by assigning New FlowDocument to RichTextBox.Document. Otherwise we'll see an of "Document belongs to another RichTextBox"
Private Sub Rtb_Unloaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim rtb As RichTextBox = DirectCast(sender, RichTextBox)
Dim fd As New FlowDocument
RichTextBoxHelper.SetDocumentXaml(rtb, fd)
Try
rtb.Document = fd
Catch ex As Exception
Debug.WriteLine("PoemDocument.PoemDocumentView.PoemRtb_Unloaded: Message:" & ex.Message)
End Try
End Sub
Public Class RichTextBoxHelper
Inherits DependencyObject
Public Shared Function GetDocumentXaml(ByVal depObj As DependencyObject) As FlowDocument
Return depObj.GetValue(DocumentXamlProperty)
End Function
Public Shared Sub SetDocumentXaml(ByVal depObj As DependencyObject, ByVal value As FlowDocument)
depObj.SetValue(DocumentXamlProperty, value)
End Sub
Public Shared ReadOnly DocumentXamlProperty As DependencyProperty = DependencyProperty.RegisterAttached("DocumentXaml", GetType(FlowDocument), GetType(RichTextBoxHelper), New FrameworkPropertyMetadata(Nothing, 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)
Dim rtb As RichTextBox = DirectCast(depObj, RichTextBox)
If rtb.IsLoaded Then
RemoveHandler rtb.TextChanged, AddressOf TextChanged
Try
rtb.Document = GetDocumentXaml(rtb)
Catch ex As Exception
Debug.WriteLine("RichTextBoxHelper.RegisterIt: ex:" & ex.Message)
rtb.Document = New FlowDocument()
End Try
AddHandler rtb.TextChanged, AddressOf TextChanged
Else
Debug.WriteLine("RichTextBoxHelper: Unloaded control ignored:" & rtb.Name)
End If
End Sub
' When a RichTextBox Document changes, update the DependencyProperty so they're in sync.
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, rtb.Document)
End If
End Sub
End Class
我的大部分需求被这个答案满意https://stackoverflow.com/a/2989277/3001007由克日什托夫。但是该代码的一个问题(我面对的是),绑定不适用于多个控件。所以我改变_recursionProtection
了一个Guid
基础的实现。因此,它也适用于同一窗口中的多个控件。
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));
}
};
}
)
);
}
为了完整起见,让我补充从原来的答案几行https://stackoverflow.com/a/2641774/3001007通过射线灼伤。这是使用助手的方法。
<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />
伙计们,为什么还要打扰所有人呢?这很完美。无需代码
<RichTextBox>
<FlowDocument>
<Paragraph>
<Run Text="{Binding Mytextbinding}"/>
</Paragraph>
</FlowDocument>
</RichTextBox>
Text
的属性Run
不是依赖项属性,因此甚至无法编译。只有依赖项属性才支持这种绑定。