首页 文章

Richtextbox wpf绑定

提问于
浏览
66

为了在 WPF RichtextBox 中对Document进行DataBinding,到目前为止,我看到了2个解决方案,它们来自RichtextBox并添加了DependencyProperty,以及带有"proxy"的解决方案 . 第一次或第二次都不令人满意 . 有人知道另一个解决方案,或者说是一个能够 DataBinding 的商业RTF控制吗?普通的Textbox不是替代品,因为我们需要文本格式化 .

任何的想法?

11 回答

  • 92

    我知道这是一个老帖子,但请查看Extended WPF Toolkit . 它有一个RichTextBox,支持您尝试执行的操作 .

  • 0

    有一个更简单的方法!

    您可以轻松创建附加的 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个月而没有人指出这么做的简单方法 .

  • 1

    我可以给你一个好的解决方案,你可以使用它,但在我做之前,我将尝试解释为什么Document不是DependencyProperty开始 .

    在RichTextBox控件的生命周期中,Document属性通常不会更改 . RichTextBox使用FlowDocument初始化 . 该文档显示,可以通过多种方式进行编辑和修改,但Document属性的基础值仍然是FlowDocument的一个实例 . 因此,它确实没有理由成为依赖属性,即Bindable . 如果您有多个引用此FlowDocument的位置,则只需要一次引用 . 由于它在任何地方都是相同的实例,因此每个人都可以访问这些更改 .

    我不认为FlowDocument支持文档更改通知,但我不确定 .

    话虽如此,这是一个解决方案 . 在开始之前,由于RichTextBox未实现INotifyPropertyChanged且Document不是依赖项属性,因此当RichTextBox的Document属性更改时,我们没有通知,因此绑定只能是OneWay .

    创建一个将提供FlowDocument的类 . 绑定需要存在依赖属性,因此该类继承自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 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>
    

    为窗口提供HasDocument类型的字段 .

    HasDocument hasDocument;
    

    Window构造函数应该创建绑定 .

    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;
    
  • 12

    我稍微调整了以前的代码 . 首先是范围 . 改变对我不起作用 . 在我更改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));
                        }
                    };
                }
            )
        );
    }
    
  • 0

    创建一个具有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;
        }
    

    这个解决方案可能就是你在某处找到的“代理”解决方案..但是...... RichTextBox根本没有Document作为DependencyProperty ......所以你必须以另一种方式做到这一点......

    HTH

  • 8

    为什么不使用FlowDocumentScrollViewer?

  • 18
    <RichTextBox>
         <FlowDocument PageHeight="180">
             <Paragraph>
                 <Run Text="{Binding Text, Mode=TwoWay}"/>
              </Paragraph>
         </FlowDocument>
     </RichTextBox>
    

    到目前为止,这似乎是最简单的方法,并没有在任何这些答案中显示 .

    在视图模型中只有 Text 变量 .

  • 6

    这是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
    

    结束班

  • 16

    这个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
    
  • 8

    我的大多数需求都得到了https://stackoverflow.com/a/2989277/3001007的答案 . 但是该代码的一个问题(我面临的是),绑定不适用于多个控件 . 所以我用基于 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));
                                }
                            };
                    }
                )
            );
        }
    

    为了完整起见,让我在ray-burns的原始答案https://stackoverflow.com/a/2641774/3001007中添加更多行 . 这是如何使用帮助器 .

    <RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />
    
  • -3

    伙计们为什么要烦恼所有的faff . 这非常有效 . 无需代码

    <RichTextBox>
        <FlowDocument>
            <Paragraph>
                <Run Text="{Binding Mytextbinding}"/>
            </Paragraph>
        </FlowDocument>
    </RichTextBox>
    

相关问题