Dynamic content in RichTextBox

A short context: my application retrieves some content from Internet ans social media, and displays it to the user.To make it simple, let’s say I receive a string that I need to put in a user interface.

To do this, I was using a TextBlock to display the message content but, as you can guess, if the message contains a url it would have been displayed as text and not as a hyperlink.

So I though of implementing a function to extract text and urls and generate my own Xaml code. Of course, the RichTextBox seems the perfect control to use for that. In the end, yes it is, but it didn’t go as smoothly as I expected…

I assume you’re using a MVVM pattern, and if not, you should! ;)

First things first, the model! Quite simple:

public class ContentModel : INotifyPropertyChanged
{
/// <summary>
/// The message content
/// </summary>
private string content;
public string Content { get { return content; } set { content = value; OnPropertyChanged("Content"); } }

/// <summary>
/// Formatted content in Xaml code
/// </summary>
public string FormattedXamlContent
{
get { return this.ParseContent(this.Content); }
}

public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}

We also need a method to extract urls from a text. I won’t describe it since it is pretty straight forward but note that unlike in the UI you do need the <Section> tag or you’ll end up with an exception.

const string pattern = "http://([\\w+?\\.\\w+])+([a-zA-Z0-9\\~\\!\\@\\#\\$\\%\\^\\&amp;\\*\\(\\)_\\-\\=\\+\\\\\\/\\?\\.\\:\\;\\'\\,]*)?";
private string ParseContent(string content)
{
string xaml = "<Section xml:space=\"preserve\" HasTrailingParagraphBreakOnPaste=\"False\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"><Paragraph>";

// Extract all Urls in the content
List<string> urls = new List<string>();
foreach (Match match in Regex.Matches(content, pattern, RegexOptions.IgnoreCase))
{
urls.Add(match.Value);
}

// Display formatted message content
foreach (string url in urls)
{
int urlIdx = content.IndexOf(url);

if (urlIdx > 0)
{
xaml += "<Run>" + content.Substring(0, urlIdx) + "</Run>";
}

// Insert link
string link = content.Substring(urlIdx, url.Length);
xaml += "<Hyperlink NavigateUri=\"" + link + "\" TargetName=\"_blank\">" + link + "</Hyperlink>";

// Remove the previous part to move to the next url
content = content.Remove(0, urlIdx + url.Length);
}

// Add the rest of the message, amd close the section
xaml += "<Run>" + content + "</Run></Paragraph></Section>";

return xaml;
}

We have our model, plus the method to extract the urls and generate Xaml code, and a property FormattedXamlContent to be binded to the Xaml property of the RichTextBox so, now let’s put a RichTextBox in our interface:

<RichTextBox Xaml="{Binding FormattedXamlContent}" IsReadOnly="True" Name="richTextBox1" />

Ok it wasn’t too hard! … Except we’re living in a cruel world and this is too good to be true! Indeed, the Xaml property is not bindable. It is possible to assign it in the code behind:

richTextBox1.Xaml = ParseContent(content);

However this works, there are some solutions that requires binding, so how to do it? Well I didn’t figure it out by myself, but thanks to Adam Sills and his solution, I got it working. Please read his article before to go further, it is not long and very well explained.

As he did, I implemented a new class with an attached property (use the snippet “propa”, it is very handy):

public class RichTextXaml
{
public static string GetBindableXaml(RichTextBox obj)
{
return (string)obj.GetValue(BindableXamlProperty);
}

public static void SetBindableXaml(RichTextBox obj, string value)
{
obj.SetValue(BindableXamlProperty, value);
}

// Using a DependencyProperty as the backing store for BindableXaml.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty BindableXamlProperty =
DependencyProperty.RegisterAttached("BindableXaml", typeof(string), typeof(RichTextXaml), new PropertyMetadata(OnBindableXamlChanged));

private static void OnBindableXamlChanged(object sender, DependencyPropertyChangedEventArgs e)
{
RichTextBox rtb = (RichTextBox)sender;
if (string.IsNullOrWhiteSpace((string)e.NewValue))
{
rtb.Xaml = null;
}
else
{
rtb.Xaml = (string)e.NewValue;
}
}
}

Then, we have to bind this to the RichTextBox:

<RichTextBox my:RichTextXaml.BindableXaml="{Binding FormattedXamlContent}" IsReadOnly="True" Name="richTextBox1" />

Finally it’s ready to work! Here is an example:

public partial class MainPage : UserControl
{
ContentModel model = new ContentModel();

public MainPage()
{
this.DataContext = this.model;
this.model.Content = "This is a simple test with a url: click http://www.google.com to search on Internet, or http://www.youtube.com to watch some lolcats.";

InitializeComponent();
}
}

And the result :

Few things to know, the following code is not valid:

string xaml = "<Run Text=\"{Binding Title}\" />";

or

string xaml = string.Format("<Run Text=\"{0}\" />", content);

or again

string xaml = string.Format("<Run<{0}</Run>", content);

Indeed, in Silverlight “{” is an escape character, the trick would be to write =\”{}{0}\” but this doesn’t work for the RichTextBox… this is a known bug of Microsoft…

Silverlight dynamic localization

I’ll use a lot the verb “localize” instead of “translate”, the difference is that it is not just about translating few pieces of text in the application; we also have to respect the culture of the user. For instance, Dutch and British don’t format their numbers, dates, currencies, phone numbers, etc. the same way; even the metric system is different.

Fortunately .NET handles easily cultures. Culture names are formatted following the standard: “<language>-<country>”, where <language> is a lowercase two-letter code and <country> is an uppercase two-letter code. For example, the English language spoken in United States is coded “en-US” and the one spoken in United Kingdom “en-GB”, as for the French of France “fr-FR” and of Belgium “fr-BE”.

So, now that the semantic part is done, let’s go technical! How do you localize a Silverlight application?

Enable the supported cultures

The first thing to do would be to tell your application which cultures are supported, and there starts the first trick… Indeed, there is no option to specify this, we have to manually modify the Silverlight project and add the supported cultures. Here is how it is done:

  • Unload and edit the application project
  • Add the supported cultures (separated by “;”) in the appropriate tag or create it if it doesn’t exist:
<SupportedCultures>nl-NL;en-US;fr-FR;</SupportedCultures>
  • Save and reload the project


Create resources files

Now we can create our resources files that will contain the translated text. For this we’ll create in the project 3 files, a default one (Feeds.resx) and two others per culture (Feeds.nl-NL.resx and Feeds.en-US.resx), note the files name format. If one day I wanted to add the French language, I’d create a new resource file named Feeds.fr-FR.resx.

The default file Feeds.resx contains all the resource names we will have to translate, you can attribute to each resource a default value (English or Dutch for example) or not.

Note the Access Modifier value set to Internal by default when you create a new resource file. To avoid compilation error, let’s set it to Public. However this is not enough to prevent compilation error. We also have to edit Feeds.designer.cs and find the constructor and set it to public:

[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Feeds() {}

However, every time we’ll modify that file, we will have to set the constructor back to public since Visual Studio regenerate it as Internal. This is a known bug that hasn’t been fixed by Microsoft yet.

Finally we can translate the text in Feeds.nl-NL.resx and its English correspondent!

Binding in XAML and code behind

In our XAML file, we add the reference to the resources namespace:

xmlns:language="clr-namespace:MyApplication.Languages"

Then we declare a static resource:

<UserControl.Resources>
<language:Feeds x:Key="Lang" />
</UserControl.Resources>

And finally we bind to the control:

<Button x:Name="CancelButton" Content="{Binding Path=ButtonCancel, Source={StaticResource Lang}}" />

If we want to set the title of a ChildWindow, it is not possible to do it binding the resource since the UserControl resources are declared after.

<controls:ChildWindow x:Class="MyApplication.SomeWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:language="clr-namespace:MyApplication.Languages"
Width="850" Height="550"
Title="GiveMeATitle">

<controls:ChildWindow.Resources>
<language:ConfigureFeed x:Key="Lang" />
</controls:ChildWindow.Resources>

So we can do it from the code behind, in the constructor for example:

public SomeWindow()
{
InitializeComponent();
this.Title = Languages.Feeds.TitleWindow;
}

Also we’d proceed that way for any value we want to set from the code behind.

Change the language in the application

Once everything is ready, we implement a ComboBox with a list of the available languages and when the user changes the language, we update the current culture for his choice and reload the main page of the application.

Create a non movable ChildWindow

Today we’re going to see how to create an immovable ChildWindow, and it’s far more annoying than just an attribute “Movable=false”… the trick is to redefine the style and remove the Chrome attribute corresponding to the child window title bar.

First let’s create a Silverlight Templated Control inherited from ChildWindow that we’ll call ImmovableChildWindow:


public class ImmovableChildWindow : ChildWindow
{
public ImmovableChildWindow()
{
this.DefaultStyleKey = typeof(ImmovableChildWindow);
}
}

Now that this is done, it might (if it didn’t exist already) have created a new Xaml file in the directory Themes of your application : Generic.xaml
In this file you’ll find an initial style for our ImmovableChildWindow; note the
TargetType value for the ImmovableChildWindow.

</pre>
<Style TargetType="local:ImmovableChildWindow">

Go to http://msdn.microsoft.com/en-us/library/dd833070%28VS.95%29.aspx and copy-paste the original ChildWindow style just after the ImmovableChildWindow.

Then replace the TargetType property of the copy with the value of our new object, two lines are to be replaced: the ones concerning the Style and the ControlTemplate.

</pre>
xmlns:local="clr-namespace:MyApplication.Controls">

<Style TargetType="local:ImmovableChildWindow">
 [...]
 <ControlTemplate TargetType="local:ImmovableChildWindow">

Finally find the line defining a Border with the atribute x:Name=”Chrome” and remove that attribute.

</pre>
<Border x:Name="Chrome" Width="Auto" BorderBrush="#FFFFFFFF" BorderThickness="0,0,0,1">

Without that attribute, events allowing the ChildWindow to be moved can’t be attached anymore, and the window remains immovable.

Finally we call our new control the same way we would have called a ChildWindow:

ImmovableWindow iw = new ImmovableWindow();
iw.Show();

Animated Silverlight Sliding Panel

Let’s create a sliding panel in a right column that will ‘push’ the column(s) on the left instead of sliding over…

We have a grid with two columns, the second one is given a size of 100 pixels to be able to work with in the designer (it will be resized to 0 in the control constructor) and contains a canvas. This canvas contains a stackpanel (or whatever we want) placed outside the canvas area (cf. Margin=”100,0,-100,0″) so it doesn’t appear. We will ‘translate’ the stackpanel in the canvas later, but for that we need to add the TranslateTransform property to the canvas.


<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>

<Canvas x:Name="canvasContainer" RenderTransformOrigin="0.5,0.5" Margin="0,30,0,80" Grid.Column="1">
<Canvas.RenderTransform>
<TranslateTransform/>
</Canvas.RenderTransform>

<StackPanel Name="slidingPanel" Orientation="Vertical" Margin="100,0,-100,0" Height="150">
<TextBox Name="myText" Width="45" Margin="0,0,0,10" />
<CheckBox Name="myCheckBox1" Content="Option 1" />
<CheckBox Name="myCheckBox2" Content="Option 2" />
</StackPanel>
</Canvas>

<StackPanel Name="someContentPanel" Background="Orange"></StackPanel>
<Button Name="buttonSlide" Opacity="0.2" Content="&lt;" Click="buttonSlide_Click" Width="64" Height="24" Grid.ColumnSpan="2" HorizontalAlignment="Right" VerticalAlignment="Top" />
</Grid>

Designer view

Now, we need the cool animations. Place the following snippet in your xaml, just after the grid columns definition for example:


<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SlideStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="00:00:00.4" To="ActionSlideIn"/>
<VisualTransition GeneratedDuration="00:00:00.4" To="ActionSlideOut"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="ActionSlideIn">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="buttonSlide" Storyboard.TargetProperty="(UIElement.Opacity)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="0.2"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="slidingPanel" Storyboard.TargetProperty="(UIElement.Opacity)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="ActionSlideOut">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.005" Storyboard.TargetName="buttonSlide" Storyboard.TargetProperty="(UIElement.Opacity)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="canvasContainer" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="-100"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>

Here we declare 2 visual states, each containing a storyboard with actions to perform on the controls properties.

The one that interests us is the stackpanel that we have to move into the canvas. So, in the ActionSlideOut method, we call the canvas TranslateTransform function on the X axis with a value of -100 pixels to translate from right to left. Remember ? We had placed the stackpanel with a right margin +100 pixels to the right of the canvas. Now it will slide from right to left and will become visible.

But for that we still have to display the right column, all that happens in the code behind. Note how we call the VisualState functions to enable the animations:


public partial class MainPage : UserControl
{
private bool slidedOut = false;

public MainPage()
{
InitializeComponent();
this.LayoutRoot.ColumnDefinitions[1].Width = new GridLength(0);
}

private void buttonSlide_Click(object sender, RoutedEventArgs e)
{
if (this.slidedOut)
{
this.SlideIn();
}
else
{
this.SlideOut();
}
}

/// <summary>
/// Show the sliding panel
/// </summary>
private void SlideOut()
{
VisualStateManager.GoToState(this, "ActionSlideOut", true);
this.slidedOut = true;

this.LayoutRoot.ColumnDefinitions[1].Width = new GridLength(100);

this.buttonSlide.Content = ">";
}

/// <summary>
/// Hide the sliding panel
/// </summary>
private void SlideIn()
{
VisualStateManager.GoToState(this, "ActionSlideIn", true);
this.slidedOut = false;

this.LayoutRoot.ColumnDefinitions[1].Width = new GridLength(0);

this.buttonSlide.Content = "<";
}
}

You can create a sliding panel over your page or control by leaving out all the columns part in this code: no right column and no column width changes…

Bind control property to another control

With Silverlight it is possible to bind a control property to another control property using the ElementName parameter, here is simple example of XAML code :

We have a TextBox that gets enabled or disabled depending on the check state of a CheckBox. If we check it, the TextBox is enabled, if not, the TextBox is disabled…

<CheckBox Content="Check me, or don't !" Name="myCheckBox" Height="16" HorizontalAlignment="Left" />
<TextBox Name="textBoxQuery" IsEnabled="{Binding Path=IsChecked, ElementName=myCheckBox}" Height="23" HorizontalAlignment="Left" Width="150" />

Then we still can call Checked and Unchecked event to add more functionalities if necessary …

Reset Id Value in SQL Server

First check the current Id value in use in the table:


/* remove quotes */
DBCC CHECKIDENT ('tablename', NORESEED)

Then reset the value to the number you want to start with (for example, an Id = 1, fill in 0):


/* remove quotes */
DBCC CHECKIDENT ('tablename', RESEED, 'numbertostart-1')

Generate a tag cloud in Silverlight

First we need to define the minima and maxima (font size, and score/weight) .

Let’s say we have a List<T> as a  parameter , with T an object containing a string (the value to display on screen) and a double (the score):


private const int minFontSize = 14;
 private const int maxFontSize = 26;

 private double minScore = double.MaxValue;
 private double maxScore = double.MinValue;

private void DefinesScoreLimits(List<T> wordList)
 {
 foreach (T word in wordList)
 {
 if (word.Score < this.minScore)
 {
 this.minScore = word.Score;
 }

 if (word.Score > this.maxScore)
 {
 this.maxScore = word.Score;
 }
 }
 }

Now, we want to normalize the scores to work with percentages, or with values between 0 and 1:


private double NormalizeScore(double score, double min, double max)
 {
 if (min == max || min > max)
 {
 return 0.0;
 }
 return (score - min) / (max - min);
 }

Once this is in place, all we have to do is iterate our list of words, tags, whatever and display the result :


foreach (T word in wordList)
 {
 Run run = new Run();

 run.FontSize = minFontSize + NormalizeScore(word.Score, this.minScore, this.maxScore) * (maxFontSize - minFontSize);

 run.Text = word.Term + " ";

 cloudTerms.Inlines.Add(run);
 }

And last but not least, the XAML file to display the cloud:


<Grid x:Name="LayoutRoot" MinHeight="500">
 <TextBlock Name="textBlock1" Text="My cloud" FontSize="20" TextWrapping="NoWrap" />
 <TextBlock Name="cloudTerms" TextWrapping="Wrap" Margin="0,20,0,10" Width="500" TextAlignment="Center"></TextBlock>
 </Grid>

Sort a List

Alphabetically ascending  :


List<string> wordList = new List<string>();
 wordList.Sort(new Comparison<string>(delegate(string w1, string w2)
 {
return w1.CompareTo(w2);
 }));

Numerically ascending :


List<int> numberList = new List<int>();
 numberList.Sort(new Comparison<int>(delegate(int n1, int n2)
 {
return n1.CompareTo(n2);
 }));

Follow

Get every new post delivered to your Inbox.