Richtextbox wpf绑定

Ale*_*ker 70 data-binding wpf richtextbox

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

任何的想法?

Ray*_*rns 99

有一个更简单的方法!

您可以轻松创建一个附加DocumentXaml(或DocumentRTF)属性,该属性允许您绑定RichTextBox的文档.它是这样使用的,其中Autobiography是数据模型中的字符串属性:

<TextBox Text="{Binding FirstName}" />
<TextBox Text="{Binding LastName}" />
<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />
Run Code Online (Sandbox Code Playgroud)

瞧!完全可绑定的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()));
            }
          };
       }});
     }
Run Code Online (Sandbox Code Playgroud)

相同的代码可用于TextFormats.RTF或TextFormats.XamlPackage.对于XamlPackage,您将拥有byte []类型的属性而不是string.

XamlPackage格式与普通XAML相比具有多个优势,尤其是包含图像等资源的能力,并且比RTF更灵活,更易于使用.

很难相信这个问题会持续15个月而没有人指出这么做的简单方法.

  • 双向对我不起作用(使用Rtf).`range.Changed`事件永远不会被调用. (15认同)
  • @Kelly,使用DataFormats.Rtf,这可以解决多个richtextboxes问题. (6认同)
  • 有人可以举例说明自传的价值吗? (2认同)

Bri*_*nas 22

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

  • Extended WPF Toolkit中的RichTextBox非常慢,我不推荐它. (9认同)
  • @ViktorLaCroix你意识到这只是WPF RichTextBox上有一个额外的属性吗? (4认同)
  • (跳到 2017 年...)wpf 工具包 RichTextBox 可以立即使用富文本或纯文本。它似乎也比使用下面的辅助方法快得多(如果你只是复制/粘贴它会抛出异常) (2认同)
  • 他们的免费许可证仅供非商业用途。:/ (2认同)

Szy*_*zga 16

我可以给你一个好的解决方案,你可以使用它,但在我做之前,我将尝试解释为什么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); }
        }
    }
Run Code Online (Sandbox Code Playgroud)

在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>
Run Code Online (Sandbox Code Playgroud)

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

HasDocument hasDocument;
Run Code Online (Sandbox Code Playgroud)

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

hasDocument = new HasDocument();

InitializeComponent();

Binding b = new Binding("Document");
b.Source = richTextBox;
b.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(hasDocument, HasDocument.DocumentProperty, b);
Run Code Online (Sandbox Code Playgroud)

如果您希望能够在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;
Run Code Online (Sandbox Code Playgroud)

  • 好的答案+1,但有一个狡辩:有理由使Document属性成为依赖属性 - 以便于将控件与MVVM模式一起使用. (2认同)

Krz*_*tof 15

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


Fak*_*leb 11

 <RichTextBox>
     <FlowDocument PageHeight="180">
         <Paragraph>
             <Run Text="{Binding Text, Mode=TwoWay}"/>
          </Paragraph>
     </FlowDocument>
 </RichTextBox>
Run Code Online (Sandbox Code Playgroud)

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

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

  • 此解决方案与绑定到 Text 属性的常规 TextBox 有何不同?它违背了使用此代码有效关闭支持格式的富文本框的目的。 (2认同)

pap*_*zzo 9

为什么不使用FlowDocumentScrollViewer?


Arc*_*rus 8

创建一个具有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;
    }
Run Code Online (Sandbox Code Playgroud)

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

HTH


Aje*_*K.P 5

krzysztof的这个答案/sf/answers/209249421/满足了我的大部分需求。但该代码的一个问题(我面临的是),绑定不适用于多个控件。所以我改变了一个基础的实现。所以它也适用于同一窗口中的多个控件。_recursionProtectionGuid

 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));
                            }
                        };
                }
            )
        );
    }
Run Code Online (Sandbox Code Playgroud)

为了完整起见,让我在ray-burns的原始答案/sf/answers/184924211/中添加几行。这就是如何使用助手。

<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />
Run Code Online (Sandbox Code Playgroud)