WPF Localization at run-time
I needed a better WPF Localization solution and I came up with one and posted it over on my WPF blog.
I would love some feed back, so if you are interested, check it out.
Archive for the ‘WPF’ Category.
I needed a better WPF Localization solution and I came up with one and posted it over on my WPF blog.
I would love some feed back, so if you are interested, check it out.
Hey all, here is my WPF MVVM knowledge. You should read these articles, whether mine or sites I link to.
However, you may want to skip my stuff and go straight to this nice little training by Karl Shifflett.
In the Box – MVVM Training
Visual Studio Helps
WPF and MVVM Problems
According to Indeed.com the amount of WPF job trends up rapidly.
Since XAML is going to be used in Windows 8 as well, it is not going away any time in the next decade. Also, declarative UI will improve in the future, not disappear. So there is job security in this.
If you are using MVVM you probably should create a snippet very similar to the following to save time.
C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC#\Snippets\1033\Visual C#\propvm.snippet
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>propvm</Title>
<Shortcut>propvm</Shortcut>
<Description>Code snippet for property and backing field for a ViewModel that calls NotifyPropertyChanged.</Description>
<Author>Jared Barneck</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>type</ID>
<ToolTip>Property type</ToolTip>
<Default>int</Default>
</Literal>
<Literal>
<ID>property</ID>
<ToolTip>Property name</ToolTip>
<Default>MyProperty</Default>
</Literal>
</Declarations>
<Code Language="csharp"><![CDATA[public $type$ $property$
{
get { return _$property$;}
set
{
_$property$ = value;
NotifyPropertyChanged("$property$");
}
} private $type$ _$property$;
$end$]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
Binding to a property of a static class is quite straight forward.
The binding string looks like this:
{x:Static s:MyStaticClass.StaticValue2}
For an example do this:
using System;
namespace BindingToStaticClassExample.Statics
{
public static class MyStaticClass
{
static MyStaticClass()
{
Title = "Binding to properties of Static Classes";
StaticValue1 = "Test 1";
StaticValue2 = "Test 2";
StaticValue3 = "Test 3";
}
public static String Title { get; set; }
public static String StaticValue1 { get; set; }
public static String StaticValue2 { get; set; }
public static String StaticValue3 { get; set; }
}
}
<Window x:Class="BindingToStaticClassExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:BindingToStaticClassExample.Statics"
Title="{x:Static s:MyStaticClass.Title}" Height="350" Width="525">
<StackPanel>
<TextBox Text="{x:Static s:MyStaticClass.StaticValue1}" />
<TextBox Text="{x:Static s:MyStaticClass.StaticValue2}" />
<!-- Not supported and won't work
<TextBox>
<TextBox.Text>
<Binding Source="{x:Static s:MyStaticClass.StaticValue3}" />
</TextBox.Text>
</TextBox>
-->
</StackPanel>
</Window>
You now know how to bind to properties of a static class.
When you bind a WPF Control’s property to another object, you have data bound that object.
Only Dependency Properties of a WPF Control can be data bound.
For more information go here:
Have you ever wanted to have a TextBox that requires data or specifically formatted data and you want to enforce this validation and display this to the user.
Well, I figured this out, with a great deal of pain, but hey, it works.
You can download this project in it’s entirety here:
I started with a new WPF Project in Visual Studio. Here is a general overview of the steps I took to make this example project happen.
MainWindow.xaml
<Window x:Class="WpfTextBoxValidation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTextBoxValidation"
Title="MainWindow" Height="350" Width="525">
<Grid Name="MainGrid">
<Grid.Resources>
<Style TargetType="TextBox">
<Setter Property="MaxWidth" Value="200" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" MinWidth="200" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Labels -->
<TextBlock Text="FirstName" />
<TextBlock Text="LastName" Grid.Row="1" />
<TextBlock Text="Age" Grid.Row="2" />
<TextBlock Text="Phone" Grid.Row="3" />
<!-- TextBlocks -->
<TextBox Name="TextBoxFirstName" Grid.Column="1">
<TextBox.Text>
<Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:TextBoxNotEmptyValidationRule x:Name="FirstNameValidation" ValidatesOnTargetUpdated="True"
Message="You must enter a first name."/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox Name="TextBoxLastName" Grid.Row="1" Grid.Column="1">
<TextBox.Text>
<Binding Path="LastName" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:TextBoxNotEmptyValidationRule x:Name="LastNameValidation" ValidatesOnTargetUpdated="True"
Message="You must enter a last name."/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox Name="TextBoxAge" Grid.Row="2" Grid.Column="1">
<TextBox.Text>
<Binding Path="Age" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:OverThirteenValidationRule x:Name="AgeValidation" ValidatesOnTargetUpdated="True"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox Name="TextBoxPhone" Grid.Row="3" Grid.Column="1">
<TextBox.Text>
<Binding Path="Phone" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:TextBoxNotEmptyValidationRule x:Name="PhoneValidation" ValidatesOnTargetUpdated="True"
Message="You must enter a phone number."/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<!-- Validation List -->
<StackPanel Grid.Row="4" Grid.ColumnSpan="2">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Red" />
</Style>
<local:ErrorCollectionToVisibility x:Key="ToVisibility" />
</StackPanel.Resources>
<TextBlock Visibility="{Binding ElementName=TextBoxFirstName, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}">
<TextBlock.Text>
<MultiBinding StringFormat="FirstName - {0}">
<Binding ElementName="TextBoxFirstName" Path="(Validation.Errors)[0].ErrorContent"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Visibility="{Binding ElementName=TextBoxLastName, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}">>
<TextBlock.Text>
<MultiBinding StringFormat="LastName - {0}">
<Binding ElementName="TextBoxLastName" Path="(Validation.Errors)[0].ErrorContent"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Visibility="{Binding ElementName=TextBoxAge, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}">>
<TextBlock.Text>
<MultiBinding StringFormat="Age - {0}">
<Binding ElementName="TextBoxAge" Path="(Validation.Errors)[0].ErrorContent"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Visibility="{Binding ElementName=TextBoxPhone, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}">>
<TextBlock.Text>
<MultiBinding StringFormat="Phone - {0}">
<Binding ElementName="TextBoxPhone" Path="(Validation.Errors)[0].ErrorContent"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!--Text="{Binding ElementName=DirectoryBox, Path=(Validation.Errors)[0].ErrorContent}"-->
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
To this file we add a Person class and then create and instance of it and set it the instance as the DataContext.
using System;
using System.Windows;
using System.ComponentModel;
namespace WpfTextBoxValidation
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Person p = new Person();
MainGrid.DataContext = p;
}
}
public class Person
{
public String FirstName { get; set; }
public String LastName { get; set; }
public int Age { get; set; }
public String Phone { get; set; }
}
}
ErrorCollectionToVisibility.cs
This class is used to convert the Validation.Error collection to a Visibility.
There is a property Validation.HasError but for some reason it is not available to bind to. If it were, I would bind to it and use the built-in BooleanToVisibility converter. But since, I can’t, I used this ErrorCollectionToVisibility converter which simply returns Visible if there is at least one item in the collection or Collapse if the Collection is null or empty.
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfTextBoxValidation
{
class ErrorCollectionToVisibility : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ReadOnlyCollection<ValidationError> collection = value as ReadOnlyCollection<ValidationError>;
if (collection != null && collection.Count > 0)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new object();
}
}
}
OverThirteenValidationRule.cs
Here we check if the value is greater than 13 and if not, we return the false ValidationResult.
using System;
using System.Windows.Controls;
namespace WpfTextBoxValidation
{
public class OverThirteenValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value != null)
{
int age = 0;
try
{
age = Convert.ToInt32(value);
}
catch
{
return new ValidationResult(false, "You must be older than 13!");
}
if (age > 13)
return ValidationResult.ValidResult;
}
return new ValidationResult(false, "You must be older than 13!");
}
}
}
TextBoxNotEmptyValidationRule.cs
This validation just makes sure there is at least one character in a TextBlock.
using System;
using System.Windows.Controls;
namespace WpfTextBoxValidation
{
public class TextBoxNotEmptyValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
string str = value as string;
if (str != null)
{
if (str.Length > 0)
return ValidationResult.ValidResult;
}
return new ValidationResult(false, Message);
}
public String Message { get; set; }
}
}
You have now learned to bind to Validation.ErrorContent.
The problem with Canvas is that when you click on it, you don’t actually get the click event to occur unless you have a background that is not white.
One trick if you want white is to use white -1 or #FFFFFE or possibly Transparent (unless the parent is not white). So no one can tell it isn’t white, because it is as close to white as can be without actually being white.
Now your click event can occur.
Also you need to make the Canvas focusable.
Here is how you make this happen.
MainWindow.xaml
<Window x:Class="TextBoxInCanvas.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid Name="MainGrid">
<canvas name="<span class=" span="" class="hiddenSpellError" pre="class ">hiddenspellerror="" pre="">canvas1" Focusable="True" Background="#FFFFFE" MouseDown="canvas1_MouseDown"></canvas>
<TextBox Height="23" Name="textBox1" Width="120" IsEnabled="True" Canvas.Left="81" Canvas.Top="115"
PreviewKeyDown="textBox1_PreviewKeyDown"/>
</Canvas>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Input;
namespace TextBoxInCanvas
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void canvas1_MouseDown(object sender, MouseButtonEventArgs e)
{
Keyboard.Focus(canvas1);
}
private void textBox1_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (Key.Enter == e.Key)
Keyboard.Focus(canvas1);
}
}
}
Now your click event occurs when the Canvas is clicked and keyboard focus is taken from the TextBox.
Previously, I discussed loading a rich text file in a regular WPF Application in this post:
Loading a RichTextBox from an RTF file using binding or a RichTextFile control
The following use cases must be met:
We created a RichTextFile control that inherits RichTextBox and configured it to support binding. Now we are going to use this same object in a WPF Navigation Application.
A WPF Navigation Application is going to react differently. Links are going work by default, so the Hyperlink.Click event doesn’t have to be used. However, there is a problem, the links open in the actual application’s window and not in a browser. Lets fix this.
Create a new WPF Application in Visual Studio.
Add a Frame element and set the source to RTF.xaml, which we will create next.
<Window x:Class="RichTextFileTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Frame Source="RTF.xaml" />
</Grid>
</Window>
There is no code behind for this, yet.
Create a new WPF Page called RTF.xaml.
Add the above RichTextFile object to the project.
Add an xmlns reference to our new object. Then add our new object. Notice in our new object that we set IsReadOnly=”True” but we also set IsDocumentEnabled=”True”. This allows for clicking a link even though the document is read only.
<Page x:Class="RichTextFileTest.RTF"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="RTF"
xmlns:controls="clr-namespace:System.Windows.Controls">
<Grid>
<controls:RichTextFile File="{Binding File}" IsReadOnly="True" IsDocumentEnabled="True"/>
</Grid>
</Page>
Code Behind
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
namespace RichTextFileTest
{
/// <summary>
/// Interaction logic for RTF.xaml
/// </summary>
public partial class RTF : Page
{
public RTF()
{
InitializeComponent();
Example example = new Example() { File = "File.rtf" };
DataContext = example;
}
}
public class Example
{
public String File { get; set; }
}
}
Now the first use case is complete, the rich text file is loading into the RichTextFile control and is visible in the application. However, the second use case is incomplete, but not because the links aren’t loading, but because they are not loading in a browser. Instead they are loading inside the Frame element.
Getting the links to open in a browser is not straight forward and it took me quite some time to find an easy solution. Somehow, the link must be open in a browser and then the Navigation event must be canceled.
The easiest way to do this is to implement the Nagivating function on the Frame element in our MainWindow.
<Window x:Class="RichTextFileTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Frame Source="RTF.xaml" Navigating="Frame_Navigating"/>
</Grid>
</Window>
Now implement the code behind for the Frame_Navigating method.
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
namespace RichTextFileTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Frame_Navigating(object sender, System.Windows.Navigation.NavigatingCancelEventArgs e)
{
Frame frame = sender as Frame;
if (frame != null && frame.Source != null)
{
// See if we are hitting a link using HTTP or HTTPS
if (frame.Source.ToString().StartsWith("http://", System.StringComparison.CurrentCultureIgnoreCase)
|| frame.Source.ToString().StartsWith("https://", System.StringComparison.CurrentCultureIgnoreCase))
{
// Open the URL in a browser
Process.Start(frame.Source.ToString());
// Cancel the Navigation event
e.Cancel = true;
}
}
}
}
}
The links should now be opening in your browser and not in your application.
Here is the sample project that demonstrates this.
RichTextFileNavigation.zip
Sometimes you have a rich text document that exists as an actual file and you simply want to load the file and display it.
My exact use cases are these:
I like to use MVVM, so my goal is to use binding to pass in the file name. Unfortunately the RichTextBox class is not designed to bind to a file name. However, extending this control to have this functionality is quite easy.
Here is my new class. I have extended RichTextBox with four additional items in a new derived class called RichTextFile.
RichTextFile.cs
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
namespace System.Windows.Controls
{
class RichTextFile : RichTextBox
{
#region Properties
public String File
{
get { return (String)GetValue(FileProperty); }
set { SetValue(FileProperty, value); }
}
public static DependencyProperty FileProperty =
DependencyProperty.Register("File", typeof(String), typeof(RichTextFile),
new PropertyMetadata(new PropertyChangedCallback(OnFileChanged)));
private static void OnFileChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RichTextFile rtf = d as RichTextFile;
if (d == null)
return;
ReadFile(rtf.File, rtf.Document);
}
#endregion
#region Functions
private static void ReadFile(string inFilename, FlowDocument inFlowDocument)
{
if (System.IO.File.Exists(inFilename))
{
TextRange range = new TextRange(inFlowDocument.ContentStart, inFlowDocument.ContentEnd);
FileStream fStream = new FileStream(inFilename, FileMode.Open, FileAccess.Read, FileShare.Read);
range.Load(fStream, DataFormats.Rtf);
fStream.Close();
}
}
#endregion
}
}
You can use this new object to load a .rtf file quite easily. I am going to show you how I succeeding in my two use cases using both a regular WPF Application and a WPF Navigation Application.
Create a new WPF Application in Visual Studio.
Add the above RichTextFile object to the project.
Add an xmlns reference to our new object. Then add our new object. Notice in our new object that we set IsReadOnly=”True” but we also set IsDocumentEnabled=”True”. This allows for clicking a link even though the document is read only.
<Window x:Class="RichTextFileTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:controls="clr-namespace:System.Windows.Controls">
<Grid>
<controls:RichTextFile File="{Binding File}" IsReadOnly="True" IsDocumentEnabled="True" />
</Grid>
</Window>
Code Behind
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Documents;
namespace RichTextFileTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Example example = new Example() { File = "File.rtf" };
DataContext = example;
}
}
public class Example
{
public String File { get; set; }
}
}
Now the first use case is complete, the rich text file is loading into the RichTextFile control and is visible in the application. However, the second use case is incomplete as clicking the link does nothing.
The links can easily made to open in a browser by implementing the Hyperlink.Click event on the RichTextFile. Here is how this is done.
<Window x:Class="RichTextFileTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:controls="clr-namespace:System.Windows.Controls">
<Grid>
<controls:RichTextFile File="{Binding File}" IsEnabled="True" IsReadOnly="True" IsDocumentEnabled="True" Hyperlink.Click="RichTextFile_Click"/>
</Grid>
</Window>
Then define the event function in the code behind.
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Documents;
namespace RichTextFileTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Example example = new Example() { File = "File.rtf" };
DataContext = example;
}
private void RichTextFile_Click(object sender, RoutedEventArgs e)
{
Hyperlink link = e.OriginalSource as Hyperlink;
if (link != null)
{
Process.Start(link.NavigateUri.ToString());
}
}
}
public class Example
{
public String File { get; set; }
}
}
The links should now be opening in your browser.
Here is the sample project that demonstrates this.
RichTextFileTest.zip