Richtextbox的WPF绑定
为了在WPF RichtextBox中对Document进行DataBinding, 到目前为止我看到了2个解决scheme,它们是从RichtextBox派生出来的,并添加一个DependencyProperty,还有一个“proxy”的解决scheme。 第一个或第二个都不令人满意。 有人知道另一种解决scheme,或者,而不是一个商业RTF控件是能够数据绑定 ? 正常的文本框不是一种select,因为我们需要文本格式化。
任何想法?
我知道这是一个旧的post,但看看扩展的WPF工具包 。 它有一个RichTextBox,支持你所要做的事情。
有一个更简单的方法!
您可以轻松地创build一个附加的DocumentXaml
(或DocumentRTF
)属性,它将允许您绑定RichTextBox的文档。 它是这样使用的,其中Autobiography是数据模型中的一个string属性:
<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 []types的属性而不是string。
与普通的XAML相比,XamlPackage格式具有几个优势,特别是包含图像等资源的能力,与RTF相比,它更加灵活和易于使用。
很难相信这个问题坐了15个月没有人指出简单的方法来做到这一点。
我可以给你一个好的解决scheme,你可以用它,但在我做之前我会试着解释为什么Document 不是一个DependencyProperty开始。
在RichTextBox控件的生命周期中,Document属性通常不会更改。 RichTextBox使用FlowDocument进行初始化。 该文档被显示,可以用许多方式进行编辑和修改,但是Document属性的基础值仍然是FlowDocument的一个实例。 因此,真的没有理由应该是一个依赖属性,即Bindable。 如果您有多个引用此FlowDocument的位置,则只需要引用一次。 由于在任何地方都是同样的情况,所有人都可以访问这些更改。
我不认为FlowDocument支持文档更改通知,但我不确定。
这就是说,这是一个解决scheme。 在开始之前,因为RichTextBox没有实现INotifyPropertyChanged而Document不是依赖属性,所以当RichTextBox的Document属性发生变化时,我们没有通知,所以绑定只能是OneWay。
创build一个将提供FlowDocument的类。 绑定需要存在一个依赖属性,所以这个类inheritance了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中用富文本框创build一个窗口。
<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>
给窗口一个HasDocumenttypes的字段。
HasDocument hasDocument;
窗口构造函数应该创build绑定。
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;
我已经调整了一些前面的代码。 首先range.Changed已经不适合我了。 当我改变了range.Changed为richTextBox.TextChanged事实certificate,TextChanged事件处理程序可以recursion地调用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)); } }; } ) ); }
创build一个具有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; if (e.NewValue == null) control.RTB.Document = new FlowDocument(); //Document is not amused by null :) control.RTB.Document = document; }
这个解决scheme可能就是你在某处看到的“代理”解决scheme。但是,RichTextBox根本没有Document作为DependencyProperty …所以你必须以另一种方式来做这件事…
HTH
为什么不使用FlowDocumentScrollViewer?
这是一个VB.Net版本的Lolo的答案:
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
末class
这个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
<RichTextBox> <FlowDocument PageHeight="180"> <Paragraph> <Run Text="{Binding Text, Mode=TwoWay}"/> </Paragraph> </FlowDocument> </RichTextBox>
这似乎是迄今为止最简单的方法,并没有显示在任何这些答案中。
在视图模型中只有Text
variables。
伙计们为什么打扰所有faff。 这完美的作品。 没有要求的代码
<RichTextBox TextAlignment="Justify" TextWrapping="Wrap" Margin="0,0,0,20"> <Paragraph> <Run Text="{Binding mytextbinding"></Run> </Paragraph> </RichTextBox>