Richtextbox WPF绑定


77

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

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

任何的想法?

Answers:


25

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


10
来自Extended WPF Toolkit的RichTextBox确实很慢,我不推荐这样做。
卡皮坦·姆利科

6
@ViktorLaCroix您确实意识到这只是WPF RichTextBox,上面有一个额外的属性,对吗?
2014年

2
(跳到2017年...)wpf工具包RichTextBox可以直接使用富文本格式或纯文本格式。它似乎也比使用下面的帮助器方法快得多(如果仅复制/粘贴它会抛出异常)
DanW

2
他们的免费许可证仅用于非商业用途。:/
埃里克

103

有一种更简单的方法!

您可以轻松创建一个附加的DocumentXaml(或DocumentRTF)属性,该属性可让您绑定RichTextBox的文档。它的用法如下,Autobiography数据模型中的string属性为:

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

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

此属性的实现非常简单:设置该属性后,将XAML(或RTF)加载到new中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个月,而没有人指出这样做的简单方法。


6
@Kelly,使用DataFormats.Rtf,可以解决多个Richtextboxes问题。
CharlieShi 2012年

16
双向不适合我(使用Rtf)。该range.Changed事件永远不会被调用。
帕特里克

1
@FabianBigler-嗨,如果有人遇到同样的问题,您需要在xml文件中添加xmlns:local声明,该声明将指向可用的命名空间
Bartosz 2015年

2
有人可以举例说明自传的价值吗?
longlostbro

1
@AntonBakulev谢谢!
longlostbro

17

我可以给你一个确定的解决方案,你可以用它去,但之前,我做我要去尝试解释为何文档是不是一个DependencyProperty开始。

RichTextBox控件的生存期内,Document属性通常不会更改。用RichTextBox初始化FlowDocument。该文档将显示出来,可以通过多种方式进行编辑和修改,但是该Document属性的基本值仍然是的一个实例FlowDocument。因此,实际上没有理由应将其设为DependencyProperty,即Bindable。如果您有多个引用此的位置,则FlowDocument只需引用一次。由于到处都是同一实例,因此所有人都可以访问更改。

我并不认为FlowDocument支持文件更改通知,但我不知道。

话虽如此,这是一个解决方案。在开始之前,由于RichTextBox未实现,INotifyPropertyChanged并且Document不是a DependencyProperty,所以当RichTextBoxDocument属性更改时,我们没有任何通知,因此绑定只能是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一个type字段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;

3
+1是一个很好的答案,但有一个疑问:有一个使Document属性成为依赖项属性的原因-便于将控件与MVVM模式一起使用。
David Veeneman

1
公平的观点,但我不同意。仅仅因为MVVM在WPF应用程序中得到了广泛使用,并不意味着WPF的API应该更改以适应它。我们将尽一切可能解决它。这是一种解决方案。我们还可以选择将Rich Text Box封装在用户控件中,并在UserControl上定义一个依赖项属性。
Szymon Rozga,2010年

16

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

谢谢罗洛!我原来的课也有问题。这为我解决了。节省大量时间!
Mark Bonafe 2015年

我发现此解决方案有一个小问题。如果未关闭视图并在两次调用之间重新创建视图,则可能会多次设置TextChanged的钩子。我一次创建一个视图并通过列表选择加载。为了解决这个问题,我创建了一个更典型的方法来挂接TextChanged事件。然后,我只是在钩住该方法之前先将其解钩。这样可以确保它仅被钩过一次。没有更多的内存泄漏(也没有运行缓慢的代码)。
Mark Bonafe 2015年

这是一个不错的解决方案,但是根据我的经验,它不能与多个控件一起使用,请参见我的回答
Ajeeb.KP,

太好了,谢谢。但是,如果您要以编程方式设置绑定,则无法使用,我想是因为设置绑定的线程与通过XAML设置的线程不同。因此,我必须添加一个SetDocumentXamlFirst方法,该方法不使用递归保护,只有在您第一次要设置该值时才手动调用它。
stuzor

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

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

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


1
在我的情况下,此解决方案在垂直视图中显示了View模型中Text属性的文本,即每行一个。
kintela

这就是我所需要的。谢谢!
克里斯托弗·画家

此解决方案与绑定到Text属性的常规TextBox有何不同?它违反了支持格式的富文本框的目的,该格式使用此代码可以有效关闭。
Daap

这对我来说是完美的。越简单越好!
gcdev

9

为什么不只使用FlowDocumentScrollViewer?


1
这是实际答案。

您无法在FlowDocumentScrollViewer中编辑文本
Eric

9

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

该解决方案可能是您在某处看到的“代理”解决方案。..但是,。RichTextBox根本没有Document作为DependencyProperty ...因此您必须以另一种方式来实现...

高温超导


在最后一行中,您使用“文档”,这在我的代码中引发了错误。由于是静态方法,它必须是Document的实例。但是实例是什么?我正在设置通过DependencyProperty获得的文档,即“文档”。删除“静态”将破坏DependencyProperty的最后一个参数。所以在这里我被卡住了。上面的帮助程序类也不显示任何文本:(
ecth

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

末级


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

0

我的大部分需求被这个答案满意https://stackoverflow.com/a/2989277/3001007克日什托夫。但是该代码的一个问题(我面对的是),绑定不适用于多个控件。所以我改变_recursionProtection了一个Guid基础的实现。因此,它也适用于同一窗口中的多个控件。

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

为了完整起见,让我补充从原来的答案几行https://stackoverflow.com/a/2641774/3001007通过射线灼伤。这是使用助手的方法。

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

-1

伙计们,为什么还要打扰所有人呢?这很完美。无需代码

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

1
就我而言,没有'FlowDocument'标签是行不通的。
Klaonis 2014年

Text的属性Run不是依赖项属性,因此甚至无法编译。只有依赖项属性才支持这种绑定。
充满希望的2015年

这正是我所需要的。
克里斯托弗·画家
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.