82

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

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

任何的想法?

4

11 回答 11

112

有一个更简单的方法!

您可以轻松创建附加DocumentXaml(或DocumentRTF)属性,该属性将允许您绑定RichTextBox's 文档。它是这样使用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,您将拥有 type 的属性,byte[]而不是string.

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

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

于 2010-04-14T23:35:12.740 回答
25

我知道这是一篇旧文章,但请查看Extended WPF Toolkit。它有一个 RichTextBox 支持你正在尝试做的事情。

于 2010-11-15T23:02:34.020 回答
19

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

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

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

话虽这么说,这里有一个解决方案。在开始之前,由于RichTextBox没有实现INotifyPropertyChanged且 Document 不是 a DependencyProperty,所以当 Document 的属性发生变化时我们没有通知RichTextBox,因此绑定只能是 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一个类型的字段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;
于 2008-12-05T21:45:34.003 回答
17

我已经稍微调整了以前的代码。首先 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));
                    }
                };
            }
        )
    );
}
于 2010-06-07T12:08:33.080 回答
14
 <RichTextBox>
     <FlowDocument PageHeight="180">
         <Paragraph>
             <Run Text="{Binding Text, Mode=TwoWay}"/>
          </Paragraph>
     </FlowDocument>
 </RichTextBox>

这似乎是迄今为止最简单的方法,并且没有显示在任何这些答案中。

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

于 2017-11-01T12:11:46.543 回答
10

创建一个 UserControl,它有一个名为 RTB 的 RichTextBox。现在添加以下依赖属性:

    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 ...因此您必须以另一种方式执行此操作...

高温高压

于 2008-12-05T13:50:28.343 回答
3

krzysztof的这个答案https://stackoverflow.com/a/2989277/3001007满足了我的大部分需求。但是该代码的一个问题(我面临的是),绑定不适用于多个控件。所以我改变了一个基于的实现。所以它也适用于同一窗口中的多个控件。_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));
                            }
                        };
                }
            )
        );
    }

为了完整起见,让我从ray-burns的原始答案https://stackoverflow.com/a/2641774/3001007添加几行。这是使用助手的方法。

<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />
于 2018-02-21T15:35:45.500 回答
1

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

结束类

于 2010-09-07T14:58:02.883 回答
1

这是我基于 Ray Burns 回答的解决方案,其中包含 DataBinding 以及将 XAML 字符串转换为 RichTextBox 文档:

视图模型

    TestText = @"<FlowDocument xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""><Paragraph><Bold>Hello World!</Bold></Paragraph></FlowDocument>";

看法

<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding TestText}"/>

富文本框助手

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;
                var    xaml        = GetDocumentXaml(richTextBox);
                Stream sm          = new MemoryStream(Encoding.UTF8.GetBytes(xaml));
                richTextBox.Document = (FlowDocument) XamlReader.Load(sm);
                sm.Close();
            }
        }
    );
}
于 2021-03-03T14:33:16.560 回答
0

这个 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
于 2010-09-09T09:47:02.090 回答
0

伙计们,为什么要打扰所有的faff。这完美地工作。无需代码

<RichTextBox>
    <FlowDocument>
        <Paragraph>
            <Run Text="{Binding Mytextbinding}"/>
        </Paragraph>
    </FlowDocument>
</RichTextBox>
于 2014-09-20T11:48:12.570 回答