[c#] Richtextbox wpf binding

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

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 TextBox is not an alternative, since we need text formatting.

Any idea?

This question is related to c# wpf data-binding richtextbox

The answer is


Most of my needs were satisfied by this answer https://stackoverflow.com/a/2989277/3001007 by krzysztof. But one issue with that code (i faced was), the binding won't work with multiple controls. So I changed _recursionProtection with a Guid based implementation. So it's working for Multiple controls in same window as well.

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

For completeness sake, let me add few more lines from original answer https://stackoverflow.com/a/2641774/3001007 by ray-burns. This is how to use the helper.

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

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 Autobiography is a string property in your data model:

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

Voila! Fully bindable RichTextBox data!

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

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.

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.

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


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 not a DependencyProperty to begin with.

During the lifetime of a RichTextBox control, the Document property generally doesn't change. The RichTextBox is initialized with a FlowDocument. That document is displayed, can be edited and mangled in many ways, but the underlying value of the Document property 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.

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

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

Create a class that will provide the FlowDocument. Binding requires the existence of a DependencyProperty, so this class inherits from 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 Window with a rich text box in 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 Window a field of type 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 HasDocument class derive from FrameworkElement so that it can be inserted into the logical tree.

Now, if you were to change the Document property on HasDocument, the rich text box's Document will also change.

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;

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.

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

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.

    ' 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

Why not just use a FlowDocumentScrollViewer ?


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.

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

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

    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...

HTH


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 not a DependencyProperty to begin with.

During the lifetime of a RichTextBox control, the Document property generally doesn't change. The RichTextBox is initialized with a FlowDocument. That document is displayed, can be edited and mangled in many ways, but the underlying value of the Document property 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.

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

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

Create a class that will provide the FlowDocument. Binding requires the existence of a DependencyProperty, so this class inherits from 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 Window with a rich text box in 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 Window a field of type 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 HasDocument class derive from FrameworkElement so that it can be inserted into the logical tree.

Now, if you were to change the Document property on HasDocument, the rich text box's Document will also change.

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;

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

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

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

    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...

HTH


Why not just use a FlowDocumentScrollViewer ?


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

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


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.

    ' 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

 <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 Text variable.


Most of my needs were satisfied by this answer https://stackoverflow.com/a/2989277/3001007 by krzysztof. But one issue with that code (i faced was), the binding won't work with multiple controls. So I changed _recursionProtection with a Guid based implementation. So it's working for Multiple controls in same window as well.

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

For completeness sake, let me add few more lines from original answer https://stackoverflow.com/a/2641774/3001007 by ray-burns. This is how to use the helper.

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

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

    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...

HTH


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 not a DependencyProperty to begin with.

During the lifetime of a RichTextBox control, the Document property generally doesn't change. The RichTextBox is initialized with a FlowDocument. That document is displayed, can be edited and mangled in many ways, but the underlying value of the Document property 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.

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

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

Create a class that will provide the FlowDocument. Binding requires the existence of a DependencyProperty, so this class inherits from 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 Window with a rich text box in 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 Window a field of type 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 HasDocument class derive from FrameworkElement so that it can be inserted into the logical tree.

Now, if you were to change the Document property on HasDocument, the rich text box's Document will also change.

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;

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 Autobiography is a string property in your data model:

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

Voila! Fully bindable RichTextBox data!

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

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.

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.

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


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

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


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

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

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

    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...

HTH


Examples related to c#

How can I convert this one line of ActionScript to C#? Microsoft Advertising SDK doesn't deliverer ads How to use a global array in C#? How to correctly write async method? C# - insert values from file into two arrays Uploading into folder in FTP? Are these methods thread safe? dotnet ef not found in .NET Core 3 HTTP Error 500.30 - ANCM In-Process Start Failure Best way to "push" into C# array

Examples related to wpf

Error: the entity type requires a primary key Reportviewer tool missing in visual studio 2017 RC Pass command parameter to method in ViewModel in WPF? Calling async method on button click Setting DataContext in XAML in WPF How to resolve this System.IO.FileNotFoundException System.Windows.Markup.XamlParseException' occurred in PresentationFramework.dll? Binding an Image in WPF MVVM How to bind DataTable to Datagrid Setting cursor at the end of any text of a textbox

Examples related to data-binding

Angular 2 Checkbox Two Way Data Binding Get user input from textarea Launch an event when checking a checkbox in Angular2 Angular 2 two way binding using ngModel is not working Binding ComboBox SelectedItem using MVVM Implement Validation for WPF TextBoxes Use StringFormat to add a string to a WPF XAML binding how to bind datatable to datagridview in c# How to format number of decimal places in wpf using style/template? AngularJS - Binding radio buttons to models with boolean values

Examples related to richtextbox

Rich Text box scroll to the bottom when new data is written to it Color different parts of a RichTextBox string Textarea that can do syntax highlighting on the fly? RichTextBox (WPF) does not have string property "Text" Displaying tooltip on mouse hover of a text Richtextbox wpf binding