C# Richtextbox wpf 绑定

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/343468/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 23:55:10  来源:igfitidea点击:

Richtextbox wpf binding

c#wpfdata-bindingrichtextbox

提问by Alex Maker

To do DataBinding of the Documentin a WPF RichtextBox, I saw 2 solutions so far, which are to derive from the RichtextBoxand add a DependencyProperty, and also the solution with a "proxy".

为了Document在 WPF 中进行数据绑定,RichtextBox到目前为止我看到了 2 个解决方案,它们是从 派生RichtextBox并添加一个DependencyProperty,以及带有“代理”的解决方案。

Neither the first or the second are satisfactory. Does somebody know another solution, or instead, a commercial RTF control which is capable of DataBinding? The normal TextBoxis not an alternative, since we need text formatting.

第一个和第二个都不令人满意。有人知道另一种解决方案,或者是能够进行DataBinding的商业 RTF 控件吗?正常TextBox不是替代方案,因为我们需要文本格式。

Any idea?

任何的想法?

采纳答案by Brian Lagunas

I know this is an old post, but check out the Extended WPF Toolkit. It has a RichTextBox that supports what you are tryign to do.

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

回答by Arcturus

Create a UserControl which has a RichTextBox named RTB. Now add the following dependency property:

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

This solution is probably that "proxy" solution you saw somewhere.. However.. RichTextBox simply does not have Document as DependencyProperty... So you have to do this in another way...

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

HTH

HTH

回答by Szymon Rozga

I can give you an ok solution and you can go with it, but before I do I'm going to try to explain why Document is nota DependencyPropertyto begin with.

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

During the lifetime of a RichTextBoxcontrol, the Documentproperty generally doesn't change. The RichTextBoxis initialized with a FlowDocument. That document is displayed, can be edited and mangled in many ways, but the underlying value of the Documentproperty remains that one instance of the FlowDocument. Therefore, there is really no reason it should be a DependencyProperty, ie, Bindable. If you have multiple locations that reference this FlowDocument, you only need the reference once. Since it is the same instance everywhere, the changes will be accessible to everyone.

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

I don't think FlowDocumentsupports document change notifications, though I am not sure.

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

That being said, here's a solution. Before you start, since RichTextBoxdoesn't implement INotifyPropertyChangedand Document is not a DependencyProperty, we have no notifications when the RichTextBox's Document property changes, so the binding can only be OneWay.

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

Create a class that will provide the FlowDocument. Binding requires the existence of a DependencyProperty, so this class inherits from DependencyObject.

创建一个将提供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); }
    }
}

Create a Windowwith a rich text box in XAML.

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>

Give the Windowa field of type HasDocument.

Window一个类型的字段HasDocument

HasDocument hasDocument;

Window constructor should create the binding.

窗口构造函数应该创建绑定。

hasDocument = new HasDocument();

InitializeComponent();

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

If you want to be able to declare the binding in XAML, you would have to make your HasDocumentclass derive from FrameworkElementso that it can be inserted into the logical tree.

如果您希望能够在 XAML 中声明绑定,则必须使您的HasDocument类派生自,FrameworkElement以便它可以插入到逻辑树中。

Now, if you were to change the Documentproperty on HasDocument, the rich text box's Documentwill also change.

现在,如果您要更改 上的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;

回答by Ray Burns

There is a much easier way!

有一个更简单的方法!

You can easily create an attached DocumentXaml(or DocumentRTF) property which will allow you to bind the RichTextBox's document. It is used like this, where Autobiographyis a string property in your data model:

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

<TextBox Text="{Binding FirstName}" />
<TextBox Text="{Binding LastName}" />
<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />

Voila! Fully bindable RichTextBoxdata!

瞧!完全可绑定的RichTextBox数据!

The implementation of this property is quite simple: When the property is set, load the XAML (or RTF) into a new FlowDocument. When the FlowDocumentchanges, update the property value.

此属性的实现非常简单:设置属性后,将 XAML(或 RTF)加载到新的FlowDocument. 当FlowDocument发生变化时,更新属性值。

This code should do the trick:

这段代码应该可以解决问题:

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()));
                        }
                    };
                }
            });
}

The same code could be used for TextFormats.RTF or TextFormats.XamlPackage. For XamlPackage you would have a property of type byte[]instead of string.

相同的代码可用于 TextFormats.RTF 或 TextFormats.XamlPackage。对于 XamlPackage,您将拥有 typebyte[]而不是string.

The XamlPackage format has several advantages over plain XAML, especially the ability to include resources such as images, and it is more flexible and easier to work with than RTF.

与普通 XAML 相比,XamlPackage 格式有几个优点,尤其是能够包含图像等资源,并且比 RTF 更灵活、更易于使用。

It is hard to believe this question sat for 15 months without anyone pointing out the easy way to do this.

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

回答by Krzysztof

I have tuned up previous code a little bit. First of all range.Changed hasn't work for me. After I changed range.Changed to richTextBox.TextChanged it turns out that TextChanged event handler can invoke SetDocumentXaml recursively, so I've provided protection against it. I also used XamlReader/XamlWriter instead of TextRange.

我已经稍微调整了以前的代码。首先 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));
                    }
                };
            }
        )
    );
}

回答by BSalita

Here's a VB.Net version of Lolo's answer:

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

End Class

结束类

回答by BSalita

This VB.Net version works for my situation. I removed thread collection semaphore, instead using RemoveHandler and AddHandler. Also, since a FlowDocument can only be bound to one RichTextBox at a time, I put in a check that the RichTextBox's IsLoaded=True. Let's begin with how I used the class in a MVVM app which uses ResourceDictionary instead of Window.

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

回答by paparazzo

Why not just use a FlowDocumentScrollViewer ?

为什么不直接使用 FlowDocumentScrollViewer ?

回答by dHyman109

Guys why bother with all the faff. This works perfectly. No code required

伙计们为什么要费心去处理所有的事情。这完美地工作。无需代码

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

回答by FakeCaleb

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

This seems to be the easiest way by far and isn't displayed in any of these answers.

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

In the view model just have the Textvariable.

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