I recently needed to use a RichTextBox for a project, but was disapointed to find there is no way of binding the content of the control. Luckily it's possible to access the XAML property and inject RichText format XAML into it. I decided to create a control derived from RichTextBox with a dependency property used to bind the Rich Text XAML.
The first step is to create the control (I re-exposed the mouse click event as the RichTextBox buries it):
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
namespace RichTextBlock
{
/// <summary>
/// Used for databinding to Xaml property via new XamlSource DP
/// </summary>
public class RichXamlTextBlock : RichTextBox
{
public event MouseButtonEventHandler MouseClicked = null;
private static string _xamlStart = "<Section xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"><Paragraph>";
private static string _xamlEnd = "</Paragraph></Section>";
public RichXamlTextBlock()
: base()
{
base.Cursor = Cursors.Arrow;
}
#region XamlSource
public static readonly DependencyProperty XamlSourceProperty = DependencyProperty.Register("XamlSource", typeof(string), typeof(RichXamlTextBlock),
new PropertyMetadata(null, new PropertyChangedCallback(OnXamlSourcePropertyChanged)));
public string XamlSource
{
get { return (string)GetValue(XamlSourceProperty); }
set
{
SetValue(XamlSourceProperty, value);
}
}
private static void OnXamlSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RichXamlTextBlock rtb = (RichXamlTextBlock)d;
rtb.Xaml = string.Format("{0}{1}{2}", _xamlStart, e.NewValue, _xamlEnd as string);
}
#endregion
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (this.MouseClicked != null)
{
this.MouseClicked(this, e);
}
}
}
}
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
namespace RichTextBlock
{
/// <summary>
/// Used for databinding to Xaml property via new XamlSource DP
/// </summary>
public class RichXamlTextBlock : RichTextBox
{
public event MouseButtonEventHandler MouseClicked = null;
private static string _xamlStart = "<Section xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"><Paragraph>";
private static string _xamlEnd = "</Paragraph></Section>";
public RichXamlTextBlock()
: base()
{
base.Cursor = Cursors.Arrow;
}
#region XamlSource
public static readonly DependencyProperty XamlSourceProperty = DependencyProperty.Register("XamlSource", typeof(string), typeof(RichXamlTextBlock),
new PropertyMetadata(null, new PropertyChangedCallback(OnXamlSourcePropertyChanged)));
public string XamlSource
{
get { return (string)GetValue(XamlSourceProperty); }
set
{
SetValue(XamlSourceProperty, value);
}
}
private static void OnXamlSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RichXamlTextBlock rtb = (RichXamlTextBlock)d;
rtb.Xaml = string.Format("{0}{1}{2}", _xamlStart, e.NewValue, _xamlEnd as string);
}
#endregion
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (this.MouseClicked != null)
{
this.MouseClicked(this, e);
}
}
}
}
The next step is to create a View Model with some XAML rich text. It is important to be careful what you bind as you could end up doing an 'injection attack' on your markup by accident!
using System;
namespace RichTextBlock
{
public class ViewModel
{
private string _xamlSource = "This is a demonstration of the <Run Text=\"RichTextBlock\" FontWeight=\"Bold\" Foreground=\"Green\"/>. The control's XAML can be data bound unlike a normal <Run Text=\"RichTextBox\" FontWeight=\"Bold\" Foreground=\"Red\"/>.";
public string XamlSource
{
get { return this._xamlSource; }
}
}
}
namespace RichTextBlock
{
public class ViewModel
{
private string _xamlSource = "This is a demonstration of the <Run Text=\"RichTextBlock\" FontWeight=\"Bold\" Foreground=\"Green\"/>. The control's XAML can be data bound unlike a normal <Run Text=\"RichTextBox\" FontWeight=\"Bold\" Foreground=\"Red\"/>.";
public string XamlSource
{
get { return this._xamlSource; }
}
}
}
Next the new control needs adding to a view:
<UserControl x:Class="RichTextBlock.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RichTextBlock"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<local:ViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<Style x:Key="RichXamlTextBlockStyle" TargetType="local:RichXamlTextBlock">
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:RichXamlTextBlock">
<Grid x:Name="ContentElement" Background="{TemplateBinding Background}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<local:RichXamlTextBlock XamlSource="{Binding Path=XamlSource, Mode=OneWay}" Style="{StaticResource RichXamlTextBlockStyle}" />
</Grid>
</UserControl>
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RichTextBlock"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<local:ViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<Style x:Key="RichXamlTextBlockStyle" TargetType="local:RichXamlTextBlock">
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:RichXamlTextBlock">
<Grid x:Name="ContentElement" Background="{TemplateBinding Background}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<local:RichXamlTextBlock XamlSource="{Binding Path=XamlSource, Mode=OneWay}" Style="{StaticResource RichXamlTextBlockStyle}" />
</Grid>
</UserControl>
I also added a stripped down template to remove the TextBox type appearance to get it looking like a TextBlock.
namespace SilverlightApplication1
ReplyDelete{
public class RichTextXaml
{
// Start and end sections
private static string _xamlStart = "<Section xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"><Paragraph>";
private static string _xamlEnd = "</Paragraph></Section>";
// Xaml Attaced Property
public static readonly DependencyProperty XamlProperty =
DependencyProperty.RegisterAttached("Xaml", typeof(string), typeof(RichTextXaml), new PropertyMetadata(null, XamlPropertyChanged));
public static void SetXaml(DependencyObject attached, string value)
{
attached.SetValue(XamlProperty, value);
}
public static string GetXaml(DependencyObject attached)
{
return (string)attached.GetValue(XamlProperty);
}
private static void XamlPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is RichTextBox)
(d as RichTextBox).Xaml = string.Format("{0}{1}{2}", _xamlStart, e.NewValue, _xamlEnd as string);
}
}
}
Have you tried to do something similar in a Windows Store app yet? RichTextBox doesn't seem to exist and RichTextBlock is not inheritable.
ReplyDeleteNo, has the richtextblock got a xaml property?
ReplyDeleteI don't see a xaml property.
ReplyDeleteMight be tricky, I'll try and have a look later
ReplyDeleteThere is a Blocks property that contains the contents of the RichTextBlock, but it is read only. Seems like data binding rich text is something lots of people would want to do.
ReplyDeleteI found something that seems to work. The RichTextColumns control that is added with some Visual Studio templates does have a RichTextContent property that I was able to bind to. I'm creating a RichTextBlock control in my ViewModel via XamlReader.Load() that I use as the bound property, but I can live with that. This post helped me figure that out http://www.lhotka.net/weblog/SetRichTextIntoRichTextBlockControl.aspx
ReplyDeleteThank you very much! You save me!
ReplyDelete