Monday 21 November 2011

How to get a Bitmap from a Visual

 
public void SaveImage(Visual visual, int width, int height, string filePath)
{
    RenderTargetBitmap bitmap = new RenderTargetBitmap(width, height, 96, 96,
                                                 PixelFormats.Pbgra32);
    bitmap.Render(visual);
 
    PngBitmapEncoder image = new PngBitmapEncoder();
    image.Frames.Add(BitmapFrame.Create(bitmap));
    using (Stream fs = File.Create(filePath))
    {
        image.Save(fs);
    }
} 
 

Images in WPF

How to set the Icon of a Window

If you try to set the Window.Icon from Codebehind, you probably get the error ImageSource for Icon property must be an icon file.. You can use the following snippet to do the trick:
 
var icon = BitmapFrame.Create(Application.GetResourceStream(
   new Uri("MyAppIcon.ico", UriKind.RelativeOrAbsolute)).Stream);
 
 
Note: You can get an "Image format is unrecognized" exception under XP, if the icon only contains compressed PNG files. In this case you have to recreate the icon without compression.

How to create a Thumbnail of an Image

 
private ImageSource GetThumbnail( string fileName )
{
   byte[] buffer = File.ReadAllBytes(fileName);
   MemoryStream memoryStream = new MemoryStream(buffer);
 
   BitmapImage bitmap = new BitmapImage();
   bitmap.BeginInit();
   bitmap.DecodePixelWidth = 80;
   bitmap.DecodePixelHeight = 60;
   bitmap.StreamSource = memoryStream;
   bitmap.EndInit();
   bitmap.Freeze();
 
   return bitmap;
}
 
 

How to automatically crop an image

The following method allows you to automatically crop an image to it's content.
 
public static ImageSource AutoCropBitmap(BitmapSource source)
{
    if (source == null)
        throw new ArgumentException("source");
 
    if (source.Format != PixelFormats.Bgra32)
        source = new FormatConvertedBitmap(source, 
                            PixelFormats.Bgra32, null, 0);
 
    int width = source.PixelWidth;
    int height = source.PixelHeight;
    int bytesPerPixel = source.Format.BitsPerPixel / 8;
    int stride = width * bytesPerPixel;
 
    var pixelBuffer = new byte[height * stride];
    source.CopyPixels(pixelBuffer, stride, 0);
 
    int cropTop = height, cropBottom = 0, cropLeft = width, cropRight = 0;
 
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            int offset = (y * stride + x * bytesPerPixel);
            byte blue = pixelBuffer[offset];
            byte green = pixelBuffer[offset + 1];
            byte red = pixelBuffer[offset + 2];
            byte alpha = pixelBuffer[offset + 3];
 
            //TODO: Define a threshold when a pixel has a content
            bool hasContent = alpha > 10;
 
            if (hasContent)
            {
                cropLeft = Math.Min(x, cropLeft);
                cropRight = Math.Max(x, cropRight);
                cropTop = Math.Min(y, cropTop);
                cropBottom = Math.Max(y, cropBottom);
            }
        }
    }
 
    return new CroppedBitmap(source, 
             new Int32Rect(cropLeft, cropTop, cropRight - cropLeft, 
                           cropBottom - cropTop));
}
 
 

How to flip an image horizontally or vertically

The easiest way to flip an image in WPF is to use a TransformedBitmap. It's a wrapper around a BitmapImage that takes a generic Transform object.
 
var flippedImage = new TransformedBitmap(originalImage, new ScaleTransform( -1,1));
 
 

WPF Geometry Transformation Tool

The geometry transformer is a simple tool I wrote to scale-, translate- and rotate-transform a geometry in the path mini language.
Simply paste the geometry string into the input textbox and specify the transform parameters. After pressing "Transform" the output textbox shows the translated geometry string. This is very useful to transform simple geometries like an expanded/collapsed chevron used in an expander template.
Click here to download the Geometry Transformer


Draw lines excactly on physical device pixels

Why do my lines appear so blurry?

When you draw a line in WPF you will experience that they often appear blurry. The reason for this is the antialiasing system that spreads the line over multiple pixels if it doesn't align with physical device pixels.
The following example shows a usercontrol that overrides the OnRender method for custom drawing a rectange to the drawingContext. Even if all points are integer values and my screen has a resolution of 96dpi the lines appear blurry. Why?
 
protected override void OnRender(DrawingContext drawingContext)
{
    Pen pen = new Pen(Brushes.Black, 1);
    Rect rect = new Rect(20,20, 50, 60);
 
    drawingContext.DrawRectangle(null, pen, rect);
}
 
 

Resolution independence

WPF is resoultion independent. This means you specify the size of an user interface element in inches, not in pixels. A logical unit in WPF is 1/96 of an inch. This scale is chosen, because most screens have a resolution of 96dpi. So in most cases 1 logical unit maches to 1 physical pixel. But if the screen resolution changes, this rule is no longer valid.

Align the edges not the center points

The reason why the lines appear blurry, is that our points are center points of the lines not edges. With a pen width of 1 the edges are drawn excactly between two pixels.
A first approach is to round each point to an integer value (snap to a logical pixel) an give it an offset of half the pen width. This ensures, that the edges of the line align with logical pixels. But this assumes, that logical and physical device pixels are the same. This is only true if the screen resolution is 96dpi, no scale transform is applied and our origin lays on a logical pixel.

Using SnapToDevicePixels for controls

All WPF controls provide a property SnapToDevicePixels. If set to true, the control ensures the all edges are drawn excactly on physical device pixels. But unfortunately this feature is only available on control level.

Using GuidelineSets for custom drawing

Our first approach to snap all points to logical pixels is easy but it has a lot of assumptions that must be true to get the expected result. Fortunately the developers of the milcore (MIL stands for media integration layer, that's WPFs rendering engine) give us a way to guide the rendering engine to align a logical coordinate excatly on a physical device pixels. To achieve this, we need to create a GuidelineSet. The GuidelineSet contains a list of logical X and Y coordinates that we want the engine to align them to physical device pixels.
If we look at the implementation of SnapToDevicePixels we see that it does excatly the same.

 
protected override void OnRender(DrawingContext drawingContext)
{
    Pen pen = new Pen(Brushes.Black, 1);
    Rect rect = new Rect(20,20, 50, 60);
 
    double halfPenWidth = pen.Thickness / 2;
 
    // Create a guidelines set
    GuidelineSet guidelines = new GuidelineSet();
    guidelines.GuidelinesX.Add(rect.Left + halfPenWidth);
    guidelines.GuidelinesX.Add(rect.Right + halfPenWidth);
    guidelines.GuidelinesY.Add(rect.Top + halfPenWidth);
    guidelines.GuidelinesY.Add(rect.Bottom + halfPenWidth);
 
    drawingContext.PushGuidelineSet(guidelines);
    drawingContext.DrawRectangle(null, pen, rect);
    drawingContext.Pop();
}
 
 
The example above is the same as at the beginning of the article. But now we create a GuidelinesSet. To the set we add a horizontal or vertical guidelines for each logical coordinate that we want to have aligned with physical pixels. And that is not the center point, but the edge of our lines. Therefore we add half the penwidth to each point.
Before we draw the rectange on the DrawingContext we push the guidelines to the stack. The result are lines that perfecly match to our physical device pixels

Adjust the penwidth to the screen resolution

The last thing we need to consider is that the width of the pen is still defined in logical units. If we want to keep the pen width to one pixel (think a moment if you really want to have this) you can scale the pen width with the ration between your screen resolution and WPF's logical units which is 1/96. The following sample shows you how to do this.
Matrix m = PresentationSource.FromVisual(this)
                .CompositionTarget.TransformToDevice;
double dpiFactor = 1/m.M11;
 
Pen scaledPen = new Pen( Brushes.Black, 1 * dpiFactor );
 
 

DrawRoundedRectangle with individual radius for each corner


drawingContext.DrawRoundedRectangle() has an ugly limitation, that you can set the corner radius only for all four corners at the time. This function extends the drawing context by a new overload of the DrawRoundedRectangle() method, that allows it to set the corner radius for each corner individually.
 
/// <summary>
/// Draws a rounded rectangle with four individual corner radius
/// </summary>
public static void DrawRoundedRectangle(this DrawingContext dc, Brush brush,
    Pen pen, Rect rect, CornerRadius cornerRadius)
{
    var geometry = new StreamGeometry();
    using (var context = geometry.Open())
    {
        bool isStroked = pen != null;
        const bool isSmoothJoin = true;
 
        context.BeginFigure(rect.TopLeft + new Vector(0, cornerRadius.TopLeft), brush != null, true);
        context.ArcTo(new Point(rect.TopLeft.X + cornerRadius.TopLeft, rect.TopLeft.Y), 
            new Size(cornerRadius.TopLeft, cornerRadius.TopLeft),
            90, false, SweepDirection.Clockwise, isStroked, isSmoothJoin);
        context.LineTo(rect.TopRight - new Vector(cornerRadius.TopRight, 0), isStroked, isSmoothJoin);
        context.ArcTo(new Point(rect.TopRight.X, rect.TopRight.Y + cornerRadius.TopRight), 
            new Size(cornerRadius.TopRight, cornerRadius.TopRight),
            90, false, SweepDirection.Clockwise, isStroked, isSmoothJoin);
        context.LineTo(rect.BottomRight - new Vector(0, cornerRadius.BottomRight), isStroked, isSmoothJoin);
        context.ArcTo(new Point(rect.BottomRight.X - cornerRadius.BottomRight, rect.BottomRight.Y), 
            new Size(cornerRadius.BottomRight, cornerRadius.BottomRight),
            90, false, SweepDirection.Clockwise, isStroked, isSmoothJoin);
        context.LineTo(rect.BottomLeft + new Vector(cornerRadius.BottomLeft, 0), isStroked, isSmoothJoin);
        context.ArcTo(new Point(rect.BottomLeft.X, rect.BottomLeft.Y - cornerRadius.BottomLeft), 
            new Size(cornerRadius.BottomLeft, cornerRadius.BottomLeft),
            90, false, SweepDirection.Clockwise, isStroked, isSmoothJoin);
 
        context.Close();
    }
    dc.DrawGeometry(brush, pen, geometry);
}
 


How to assign a dynamic resource link programmatically

Normally you assign resources in XAML by using the StaticResource or DynamicResource extension. But how to do it programmatically?
The static behavior is simple. Just find the resource by using the TryFindResource() method and set it to the property of your choice. But if you want to have it dynamically updated, the following code snipped is your solutions:

 
frameworkElement.SetResourceReference(dependencyProperty, resourceKey);
 

How to load BAML resources

What is BAML?

WPF is based on XAML an XML dialect to describe object graphs. This is very powerful but parsing an XAML file at runtime is quite expensive. So the MarkupCompiler converts the XAML file to a more compact binary version called BAML. The BAML stream then is stored to the resources of the assembly and loaded within the InitializeComponent method.
The BAML API is something unique of WPF and was not public until .NET 4.0. Now it's available through the Baml2006Reader implementation. The following code shows how to load a BAML stream in .NET 3.5 and 4.0

Load an object from a BAML stream

Load a BAML stream in .NET 3.5 (by using reflection):
 
var pc = new ParserContext();
var readerType = presentationFrameworkAssembly
                  .GetType("System.Windows.Markup.XamlReader");
var method = readerType.GetMethod("LoadBaml", 
                  BindingFlags.NonPublic | BindingFlags.Static);
return method.Invoke(null, new object[] {stream, pc, null, false});
 
 
Load a BAML stream in .NET 4.0:
 
var reader = new Baml2006Reader(stream);
var writer = new XamlObjectWriter(reader.SchemaContext);
while(reader.Read())
{
    writer.WriteNode(reader);
}
return writer.Result;
 
 

Merged ResourceDictionaries

Problems loading merged resource dictionaries in .NET 4.0

I experienced a problem, when I am trying to merge resource dictionaries at app-level. As soon as I changed the target framework from 3.5 to 4.0 they don't get loaded anymore. It seems as if they have changed something in the behavior of MergedResourceDictionaries in .NET 4.0.

I found a way how to fix this problem. Just add a dummy default style in the resource dictionary where you merge all resources together. See the following example:

This is the official bug description from Microsoft:
On the creation of every object in XAML, if a default style is present (i.e. style w/ a key of Type) that style should be applied. As you can imagine there are several performance optimizations to make that (implied) lookup a light weight as possible. One of them is that we don’t look inside Resource Dictionaries unless they are flagged as “containing default Styles”. There is a bug: if all your default styles are nested in merged dictionaries three levels deep (or deeper) the top dictionary does not get flagged so the search skips it. The work around is to put a default Style to something, anything, <Style TargetType="x:Dummy" /> in the root Dictionary.
Bug entry on Microsoft Connect
 
<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Styles/DefaultStyle.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>
 
 
 
<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="DefaultStyle/Button.xaml"/>
    </ResourceDictionary.MergedDictionaries>
 
    <Style TargetType="{x:Type Rectangle}" />
 
</ResourceDictionary>
 
 

Improve the performance of merged ResourceDictionaries

Each time a control references a ResourceDictionary XAML creates a new instance of it. So if you have a custom control library with 30 controls in it and each control references a common dictionary you create 30 identical resource dictionaries!
 
<ResourceDictionary.MergedDictionaries>
   <SharedResourceDictionary Source="/MyControlLibrary;component/Themes/Brushes.xaml"  />
</ResourceDictionary.MergedDictionaries>
 
 
To get rid of this problem, I created the SharedResourceDictionary. You can use it the same way as a conventional ResourceDictionary. The only suptile difference is, that if it is instanced multiple times, the resources are loaded only once.
 
 
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", 
"WPFTutorial.Utils")]
 
/// <summary>
/// The shared resource dictionary is a specialized resource dictionary
/// that loads it content only once. If a second instance with the same source
/// is created, it only merges the resources from the cache.
/// </summary>
public class SharedResourceDictionary : ResourceDictionary
{
    /// <summary>
    /// Internal cache of loaded dictionaries 
    /// </summary>
    public static Dictionary<Uri, ResourceDictionary> _sharedDictionaries =
        new Dictionary<Uri, ResourceDictionary>();
 
    /// <summary>
    /// Local member of the source uri
    /// </summary>
    private Uri _sourceUri;
 
    /// <summary>
    /// Gets or sets the uniform resource identifier (URI) to load resources from.
    /// </summary>
    public new Uri Source
    {
        get { return _sourceUri; }
        set
        {
            _sourceUri = value;
 
            if (!_sharedDictionaries.ContainsKey(value))
            {
                // If the dictionary is not yet loaded, load it by setting
                // the source of the base class
                base.Source = value;
 
                // add it to the cache
                _sharedDictionaries.Add(value, this);
            }
            else
            {
                // If the dictionary is already loaded, get it from the cache
                MergedDictionaries.Add(_sharedDictionaries[value]);
            }
        }
    }
}
 
 

How to read WPF ResourceDictionaries from WinForms

When you add resources dictionaries to an WPF project the build action is automatically set to Page. This means that the compiler generates a BAML stream and adds it to the resources of the assembly.
Since WPF has the built-in functionality to read a BAML stream but its API is not public, we have to write a little helper class that access the internal method by using reflection.
 
public static class BamlReader
{
    public static object Load(Stream stream)
    {
        ParserContext pc = new ParserContext();
        MethodInfo loadBamlMethod = typeof(XamlReader).GetMethod("LoadBaml", 
            BindingFlags.NonPublic | BindingFlags.Static)
        return loadBamlMethod.Invoke(null, new object[] { stream, pc, null, false });
    }
}
 
 
 
StreamResourceInfo sri = System.Windows.Application.GetResourceStream(
    new Uri("/MyAssemblyName;component/MyResourceDict.xaml", UriKind.Relative));
    ResourceDictionary resources = (ResourceDictionary)BamlReader.Load(sri.Stream);
 
 

Behaviors

Introduction

Behaviors are a new concept, introduced with Expression Blend in Version 3, to encapsulate pieces of functionality into a reusable component. These components than can be attached to controls to give them an additional behavior.
The ideas behind behaviors are to give the interaction designer more flexibility to design complex user interactions without writing any code.
Example of a behaviors are drag&drop, input validation, pan and zoom, re-position of elements, etc... The list of possible behaviors is very long.
Imaging an application that has a list of customers and the user can add some of them to subscriber lists. This interaction can be designed by providing an "Add" button next to each subscriber list. But if the interaction designer wants to add drag&drop functionality, he needs to discuss it with the developer and wait until the implementation is done. With behaviors he just drags a drag and drop behavior on each list and we are done.

How to use behaviors in Expression Blend 3

Using behaviors in Expression Blend is as simple as adding an element to the design surface. In the asset library you find a new secion called "Behaviors". It lists all behaviors available within your project. Just grab one of these and drag it onto the element you want to add this behavior and thats it.
The behavior appears as an child element in the visual tree. By clicking on it you can configure the properties of the behavior.


How does it work

To add behaviors to an element you need some kind of an extension point. This is an attached property called Interaction.Behaviors.
This attached property holds the list of behaviors for that element and pass a reference to the element into the behavior. The behavior than can register itself to events and property changes and so extend the functionality of the element.

The idea is simple, but very clever. They don't need any new infrastructure, they just reuse the existing one.
 
<Border Background="LightBlue" >
 <e:Interaction.Behaviors>
  <b:DragBehavior/>
 </e:Interaction.Behaviors>
 <TextBlock Text="Drag me around!" />
</Border>
 
 

How to implement your own behavior

The following example shows the implementation of the drag behavior we used above. Just derive from Behavior<T;gt; and override the OnAttached() method.
 
public class DragBehavior : Behavior<UIElement>
{
    private Point elementStartPosition;
    private Point mouseStartPosition;
    private TranslateTransform transform = new TranslateTransform();
 
    protected override void OnAttached()
    {
        Window parent = Application.Current.MainWindow;
        AssociatedObject.RenderTransform = transform;
 
        AssociatedObject.MouseLeftButtonDown += (sender, e) => 
        {
            elementStartPosition = AssociatedObject.TranslatePoint( new Point(), parent );
            mouseStartPosition = e.GetPosition(parent);
            AssociatedObject.CaptureMouse();
        };
 
        AssociatedObject.MouseLeftButtonUp += (sender, e) =>
        {
            AssociatedObject.ReleaseMouseCapture();
        };
 
        AssociatedObject.MouseMove += (sender, e) =>
        {
            Vector diff = e.GetPosition( parent ) - mouseStartPosition;
            if (AssociatedObject.IsMouseCaptured)
            {
                transform.X = diff.X;
                transform.Y = diff.Y;
            }
        };
    }
}
 
 

List of some popular behaviors

Since its so cool and easy to create your own pice of interactivity, I am sure that we will find hunderts of behaviors available soon. I tried to make a list of some popular ones.

Drag and Drop in WPF

Introduction

Drag&Drop can drasticly improve the productiviy and user experience of a software. But only a few programmers provide drag and drop functionality in their applications, because they think its much more dificult than it really is. This article shows how simple drag and drop can be implemented in WPF.

Drag&Drop in 6 Steps

  1. Detect a drag as a combinatination of MouseMove and MouseLeftButtonDown
  2. Find the data you want to drag and create a DataObject that contains the format, the data and the allowed effects.
  3. Initiate the dragging by calling DoDragDrop()
  4. Set the AllowDrop property to True on the elements you want to allow dropping.
  5. Register a handler to the DragEnter event to detect a dragging over the drop location. Check the format and the data by calling GetDataPresent() on the event args. If the data can be dropped, set the Effect property on the event args to display the appropriate mouse cursor.
  6. When the user releases the mouse button the DragDrop event is called. Get the data by calling the GetData() method on the Data object provided in the event args.
...and that's all the magic.

Drag

To start the drag operation, we have to detect a mouse move while the left mouse button is pressed. To do this we have to hook up handlers on the PreviewMouseMove and PreviewMouseLeftButtonDown events.
To prevent occasionally drags, its a good design to not start the drag operation until the user has moved the mouse cursor by a couple of pixels. WPF provides a constant that contains the amount of pixel that Windows uses.
When the drag is initiated, we need to specify the data we want to drag. In our case its the data of the ListViewItem we dragged. We find the ListViewItem in the OriginalSource of the mouse event args. By calling ItemContainerGenerator.ItemFromContainer we get the data behind the ListViewItem.
Create a DataObject to transport the data to the drop location. The constructor takes two arguments. A string that describes the format and the data we want to drag.

 
<ListView x:Name="DragList" 
          PreviewMouseLeftButtonDown="List_PreviewMouseLeftButtonDown" 
          PreviewMouseMove="List_MouseMove"/>
 
 
 
private void List_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    // Store the mouse position
    startPoint = e.GetPosition(null);
}
 
 
 
private void List_MouseMove(object sender, MouseEventArgs e)
{
    // Get the current mouse position
    Point mousePos = e.GetPosition(null);
    Vector diff = startPoint - mousePos;
 
    if (e.LeftButton == MouseButtonState.Pressed &&
        Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
        Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance )
    {
        // Get the dragged ListViewItem
        ListView listView = sender as ListView;
        ListViewItem listViewItem = 
            FindAnchestor<ListViewItem>((DependencyObject)e.OriginalSource);
 
        // Find the data behind the ListViewItem
        Contact contact = (Contact)listView.ItemContainerGenerator.
            ItemFromContainer(listViewItem);
 
        // Initialize the drag & drop operation
        DataObject dragData = new DataObject("myFormat", contact );
        DragDrop.DoDragDrop(listViewItem, dragData, DragDropEffects.Move);
    } 
}
 
 
 
// Helper to search up the VisualTree
private static T FindAnchestor<T>(DependencyObject current)
    where T : DependencyObject
{
    do
    {
        if( current is T )
        {
            return (T)current;
        }
        current = VisualTreeHelper.GetParent(current);
    }
    while (current != null);
    return null;
}
 
 

Drop

To make an element be a drop location, set the AllowDrop property to true. When the user drags an item over the element, the DragEnter event is called. In this event you can analyze the data and decide if a drop is allowed or not.
When the user releases the mouse button the Drop event is called. The data is available in the DataObject provided in the DragEventArgs.

 
<ListView x:Name="DropList" 
          Drop="DropList_Drop" 
          DragEnter="DropList_DragEnter" 
          AllowDrop="True" />
 
 
 
private void DropList_DragEnter(object sender, DragEventArgs e)
{
    if (!e.Data.GetDataPresent("myFormat") ||
        sender == e.Source)
    {
        e.Effects = DragDropEffects.None;
    }
}
 
 
 
private void DropList_Drop(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent("myFormat"))
    {
        Contact contact = e.Data.GetData("myFormat") as Contact;
        ListView listView = sender as ListView;
        listView.Items.Add(contact);
    }
}
 
 

Localization of a WPF application using a custom MarkupExtension

The Idea

A simple and effective way to localize application resources is to write a custom MarkupExtension that provides a localized value. The extension takes a parameter in the constructor that is the unique resource key. When the DepdendencyProperty asks for the value, the markup extension looks up the value from a generic resource provider. This gives you the flexibility to reuse the resource management and translation process that is already established within your company.
Using a custom markup extension has some advantages
  • Lightweight and flexible solution
  • Dynamic language switch at runtime
  • Works with any kind of resource providers.

Other Implementations

The idea of using a markup extension for localization is not unique by me. There are also other similar implementations out there. Here you find some links:

How to use it

The usage of the markup extension is very simple. Just replace the string you want to localize by {l:Translate resourceKey}.
 
 <TextBlock Text="{l:Translate CustomerForm.FirstName}" />
 
 

Implementation details of the translation infrastructure

The translation infrastructure consists of the folling parts
  • Translation manager
    The translation manager is a static class that manages the current language and notifies all markup extensions, to update their values when the language changes. It also provides access to translated resources. The resources itself are provided by a generic translation provider.
  • Translate markup extension
    The tanslate markup extension knows the resource key and provides the translated value. It listens to the LanguageChanged event of the translation manager and updates its value. This event handler is implemented by the weak event pattern to prevent memory leaks.
  • Translation provider
    The translation provider is a class that provides the translated resources. It has to implement the ITranslationProvider and can access any kind of resources you like. For e.g. ResX, XML or text files.
 
public class TranslateExtension : MarkupExtension
{
    private string _key;
 
    public TranslateExtension(string key)
    {
        _key = key;
    }
 
    [ConstructorArgument("key")]
    public string Key
    {
        get { return _key; }
        set { _key = value;}
    }
 
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var binding = new Binding("Value")
              {
                  Source = new TranslationData(_key)
              };
        return binding.ProvideValue(serviceProvider);
    }
}
 
 
 
public class TranslationData : IWeakEventListener, 
                  INotifyPropertyChanged, IDisposable
{
    private string _key;
 
    public TranslationData( string key)
    {
        _key = key;
        LanguageChangedEventManager.AddListener(
                  TranslationManager.Instance, this);
    }
 
    ~TranslationData()
    {
        Dispose(false); 
    }
 
 
    public void Dispose() 
    { 
        Dispose(true); 
        GC.SuppressFinalize(this); 
    } 
 
    protected virtual void Dispose(bool disposing) 
    { 
        if (disposing) 
        { 
          LanguageChangedEventManager.RemoveListener(
                    TranslationManager.Instance, this); 
        } 
    } 
 
 
    public object Value
    {
        get
        {
            return TranslationManager.Instance.Translate(_key);
        }
    }
 
    public bool ReceiveWeakEvent(Type managerType, 
                            object sender, EventArgs e)
    {
        if (managerType == typeof(LanguageChangedEventManager))
        {
            OnLanguageChanged(sender, e);
            return true;
        }
        return false;
    }
 
    private void OnLanguageChanged(object sender, EventArgs e)
    {
        if( PropertyChanged != null )
        {
            PropertyChanged( this, new PropertyChangedEventArgs("Value"));
        }
    }
 
    public event PropertyChangedEventHandler PropertyChanged;
}
 
 
 
public class TranslationManager
{
    private static TranslationManager _translationManager;
 
    public event EventHandler LanguageChanged;
 
    public CultureInfo CurrentLanguage
    {
        get { return Thread.CurrentThread.CurrentUICulture; }
        set
        {
           if( value != Thread.CurrentThread.CurrentUICulture)
           {
               Thread.CurrentThread.CurrentUICulture = value;
               OnLanguageChanged();
           }
        }
    }
 
   public IEnumerable<CultureInfo> Languages
   {
       get
       {
           if( TranslationProvider != null)
           {
               return TranslationProvider.Languages;
           }
           return Enumerable.Empty<CultureInfo>();
       }
    }
 
    public static TranslationManager Instance
    {
        get
        {
            if (_translationManager == null)
                _translationManager = new TranslationManager();
            return _translationManager;
        }
    }
 
    public ITranslationProvider TranslationProvider { get; set; }
 
   private void OnLanguageChanged()
   {
        if (LanguageChanged != null)
        {
            LanguageChanged(this, EventArgs.Empty);
       }
    }
 
   public object Translate(string key)
   {
       if( TranslationProvider!= null)
       {
            object translatedValue =TranslationProvider.Translate(key);
            if( translatedValue != null)
           {
               return translatedValue;
           }
        }
       return string.Format("!{0}!", key);
    }
}

How to evaluate a localization mechanism

Criterias for evaluation

To choose an appropriate localization mechanism you have to find one that meets the requirements and your companys established translation process.
Criterias to compare the localization mechanisms among each others:
  • Dynamic language switching
  • Usage in XAML and Code
  • Types of applicable resources
  • Ease and flexibility of use
  • Translation performance

Different WPF translation mechanisms compared

Data Templates

Introduction

Data Template are a similar concept as Control Templates. They give you a very flexible and powerful solution to replace the visual appearance of a data item in a control like ListBox, ComboBox or ListView. In my opinion this is one of the key success factory of WPF.
If you don't specify a data template, WPF takes the default template that is just a TextBlock. If you bind complex objects to the control, it just calls ToString() on it. Within a DataTemplate, the DataContext is set the data object. So you can easily bind against the data context to display various members of your data object

DataTemplates in Action: Building a simple PropertyGrid

Whereas it was really hard to display complex data in a ListBox with WinForms, its super easy with WPF. The following example shows a ListBox with a list of DependencyPropertyInfo instances bound to it. Without a DataTemplate you just see the result of calling ToString() on the object. With the data template we see the name of the property and a TextBox that even allows us to edit the value.



 
<!-- Without DataTemplate -->
<ListBox ItemsSource="{Binding}" /> 
 
<!-- With DataTemplate -->
<ListBox ItemsSource="{Binding}" BorderBrush="Transparent" 
         Grid.IsSharedSizeScope="True"
         HorizontalContentAlignment="Stretch">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid Margin="4">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="Key" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <TextBlock Text="{Binding Name}" FontWeight="Bold"  />
                <TextBox Grid.Column="1" Text="{Binding Value }" />
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
 

How to use a DataTemplateSelector to switch the Template depending on the data

Our property grid looks nice so far, but it would be much more usable if we could switch the editor depending on the type of the property.
The simplest way to do this is to use a DataTemplateSelector. The DataTemplateSelector has a single method to override: SelectTemplate(object item, DependencyObject container). In this method we decide on the provided item which DataTemplate to choose.
The following exmple shows an DataTemplateSelector that decides between tree data templates:
 
public class PropertyDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate DefaultnDataTemplate { get; set; }
    public DataTemplate BooleanDataTemplate { get; set; }
    public DataTemplate EnumDataTemplate { get; set; }
 
    public override DataTemplate SelectTemplate(object item, 
               DependencyObject container)
    {
        DependencyPropertyInfo dpi = item as DependencyPropertyInfo;
        if (dpi.PropertyType == typeof(bool))
        {
            return BooleanDataTemplate;
        }
        if (dpi.PropertyType.IsEnum)
        {
            return EnumDataTemplate;
        }
 
        return DefaultnDataTemplate;
    }
}
 
 
 
<Window x:Class="DataTemplates.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:l="clr-namespace:DataTemplates"
    xmlns:sys="clr-namespace:System;assembly=mscorlib">
 
    <Window.Resources>
 
        <!-- Default DataTemplate -->
        <DataTemplate x:Key="DefaultDataTemplate">
           ...
        </DataTemplate>
 
        <!-- DataTemplate for Booleans -->
        <DataTemplate x:Key="BooleanDataTemplate">
           ...
        </DataTemplate>
 
        <!-- DataTemplate for Enums -->
        <DataTemplate x:Key="EnumDataTemplate">
            ...
        </DataTemplate>
 
        <!-- DataTemplate Selector -->
        <l:PropertyDataTemplateSelector x:Key="templateSelector"
              DefaultnDataTemplate="{StaticResource DefaultDataTemplate}"
              BooleanDataTemplate="{StaticResource BooleanDataTemplate}" 
              EnumDataTemplate="{StaticResource EnumDataTemplate}"/>
    </Window.Resources>
    <Grid>
        <ListBox ItemsSource="{Binding}" Grid.IsSharedSizeScope="True" 
                 HorizontalContentAlignment="Stretch" 
                 ItemTemplateSelector="{StaticResource templateSelector}"/>
    </Grid>
</Window>
 
 

How to react to IsSelected in the DataTemplate

If you want to change the appearance of a ListBoxItem when it is selected, you have to bind the IsSelected property of the ListBoxItem. But this is a bit tricky, you have to use a relative source with FindAcestor to navigate up the visual tree until you reach the ListBoxItem.
 
<DataTemplate x:Key="DefaultDataTemplate">
    <Border x:Name="border" Height="50">
        ...
    </Border>
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding RelativeSource=
            {RelativeSource Mode=FindAncestor, AncestorType=
                {x:Type ListBoxItem}},Path=IsSelected}" Value="True">
            <Setter TargetName="border" Property="Height" Value="100"/>
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>