Richtextbox wpf binding
Solution 1
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}" />
Solution 2
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.
Solution 3
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.
Solution 4
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;
Solution 5
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));
}
};
}
)
);
}
Related videos on Youtube
Alex Maker
Updated on February 19, 2022Comments
-
Alex Maker about 2 years
To do DataBinding of the
Document
in a WPFRichtextBox
, I saw 2 solutions so far, which are to derive from theRichtextBox
and add aDependencyProperty
, 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?
-
Thomas Weller about 2 yearsThe answer to be rewarded: stackoverflow.com/a/48909764/480982
-
-
David Veeneman almost 14 years+1 for the good answer, but one quibble: There is a reason to make the Document property a dependency property--to facilitate using the control with the MVVM pattern.
-
Kelly almost 14 yearsIf you have multiple RichTextBoxes on the same window, this solution will not work.
-
Szymon Rozga almost 14 yearsFair point, but I disagree; just because MVVM is used widely in WPF apps doesn't mean that WPF's API should change just to accomodate it. We work around it in whatever way we can. This is one solution. We may also simply choose to encapsulate our Rich Text Box in a user control and have a dependency property defined on the UserControl.
-
CharlieShi over 11 years@Kelly, use DataFormats.Rtf, this can solve multiple richtextboxes issue.
-
Kapitán Mlíko about 11 yearsRichTextBox from Extended WPF Toolkit is really slow I wouldn't recommend it.
-
Patrick about 11 yearsTwo-way isn't working for me (using Rtf). The
range.Changed
event is never getting called. -
Admin almost 10 years@ViktorLaCroix you do realize this is just the WPF RichTextBox with an extra property on it right?
-
Klaonis over 9 yearsIn my case, it doesn't work without 'FlowDocument' tag.
-
Mark Bonafe over 9 yearsThanks Lolo! I was having problems with the original class, too. This fixed it for me. Huge time saver!
-
Mark Bonafe over 9 yearsI have found a small problem with this solution. It is possible to have the hook for TextChanged get set multiple times if the view is not closed and recreated between calls. I am creating a single view once and loading via a list selection. To fix this, I created a more typical method for hooking the TextChanged event. Then I simply unhook the method before hooking it. This ensures it is only hooked once. No more memory leak (and no more slow running code).
-
Bartosz about 9 years@FabianBigler - Hi - in case anybody has the same problem - you need to add the xmlns:local declaration to your xaml file that will point to the namespace where this is available
-
Hopeless over 8 years
Text
property ofRun
is not dependency property so this does not even compile. Only dependency properties support binding like this. -
ecth almost 8 yearsIn the last line you use "document" which throws an error in my code. It has to be an instance of Document because of the static method. But instance of what? I am setting the document I get through the DependencyProperty, the "Document". Removing the "static" breaks the last argument of the DependencyProperty. So here I'm stuck. The Helper-class from above doesn't show any text either :(
-
longlostbro about 7 yearscan someone give an example of the value of Autobiography?
-
DanW over 6 years(jump ahead to 2017...) the wpf toolkits RichTextBox works with rich text or plain text right out of the box. It also seems a lot faster than using the helper method below (which throws an exception if you just copy/paste it)
-
Ajeeb.K.P about 6 yearsThis is a nice working solution, how ever, in my experience it's not working with multiple controls see my answer.
-
Anton Bakulev over 5 years@longlostbro, Here it is an example of the value: gist.github.com/bakulev/614a665241cc4fd69409e1ab3e03a9ba
-
kintela over 5 yearsIn my case this solution shows the text of the Text property in the View Model in vertical, that is, one for in each row.
-
Christopher Painter about 5 yearsThis was exactly what I needed.
-
Christopher Painter about 5 yearsIt's all I needed. Thanks!
-
longlostbro about 5 years@AntonBakulev Thanks!
-
stuzor almost 5 yearsThis is great, thanks. Doesn't work if you want to set the binding programmatically though, I assume because the thread setting the binding is different than that which sets via XAML. So I had to add a SetDocumentXamlFirst method, which doesn't use the recursion protection and would only be called manually when you first want to set the value.
-
Daap over 4 yearsHow is this solution different from a regular TextBox bound to the Text property? It defeats the purpose of a rich text box supporting formatting that is effectively switched off using this code.
-
gcdev over 4 yearsThis was perfect for me. The simpler the better!
-
Eric about 4 yearsTheir free license is for non-commercial use only. :/
-
Istvan Heckl over 3 yearsThe backing string (
Autobiography
) in the VM must be a FlowDocument in xml format. For example:<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"><Paragraph Foreground="Red"><Bold>Hello</Bold></Paragraph></FlowDocument>
-
Istvan Heckl over 3 yearsThe backing string in the VM must be a FlowDocument in xml format else the Load will fail. For example:
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"><Paragraph Foreground="Red"><Bold>Hello</Bold></Paragraph></FlowDocument>
-
David Rector over 2 yearsThis doesn't allow adding multiple paragraphs or multiple runs. The whole reason to use a rich text box is to get to those features.
-
David Rector over 2 yearsIt won't work if you want to add multiple paragraphs or multiple runs to the rich text box.
-
Thomas Weller about 2 yearsAfter trying 2 highly upvoted suggestions, this finally worked for us.