WPF Simplified Part 13: Value Converters

In WPF, when you data bind an element’s property to a data source, if the source property type and target property type don’t match you’ll need a value converter to hook them up correctly.

Consider the following data bound TextBlock,

    <TextBlock x:Name="MyText" Text="{Binding Name}" Foreground="{Binding DisplayColor}" />

and the code behind,

    this.MyText.DataContext = new { Name = "John", DisplayColor = "BlackSmoke" };

If you try out the code and XAML above you’ll see the following error in VisualStudio’s ‘Output’ window,

System.Windows.Data Error: 6 : ‘TargetDefaultValueConverter’ converter failed to convert value ‘BlackSmoke’ (type ‘String’); fallback value will be used, if available. BindingExpression:Path=DisplayColor; DataItem='<>f__AnonymousType0`2′ (HashCode=-850673272); target element is ‘TextBlock’ (Name=’MyText’); target property is ‘Foreground’ (type ‘Brush’) FormatException:’System.FormatException: Token is not valid.

The error is that TextBlock’s Foreground accepts a Brush type, but we bound the Foreground to a String type, hence the binding failed. What we need is a value converter that will convert a String type into a Brush type.

    [ValueConversion(typeof(string), typeof(Brush))]
    public class StringToBrushConverter : IValueConverter 
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
            if (value is string) {
                string source = value as string;
                if (source == "BlackSmoke") return new SolidColorBrush(Colors.DimGray);
            }

            // Default color
            return Colors.Black;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
            // To keep it simple, no need to convert back 
            return null;
        }
    }

As you can see, we’ve hand coded how a String will convert to a Brush type for our application – when the string is “BlackSmoke” we’d like the Brush to be “DimGray”. Applying this converter in XAML is simple enough,

<Grid>
    <Grid.Resources>
        <local:StringToBrushConverter x:Key="StringToBrushConverter" />
    </Grid.Resources>
       <TextBlock x:Name="MyText" Text="{Binding Name}" 
       Foreground="{Binding DisplayColor, Converter={StaticResource StringToBrushConverter}}" />
</Grid>

Thus, even though the TextBlock’s Foreground is still bound to a String type, our converter will provide the required type (Brush) to the Foreground dependency property.

WPF also has some built-in value converters, in fact, it has a built in String to Brush converter! To see this, try the following XAML,

    <TextBlock x:Name="MyText" Text="{Binding Name}" Foreground="{Binding DisplayColor}" />

with the following code behind,

    this.MyText.DataContext = new { Name = "John", DisplayColor = "Red" }; 

The text displays in red because of WPF’s built in String to Brush converter – it recognized the string ‘Red’ and converted it to the corresponding BrushColors.Red. In fact the built-in converter will recognize any of the colors in System.Windows.Media.Colors as strings, and will convert them to their corresponding Brushes. The code probably does the equivalent of the following,

    if (source == "Red") return new SolidColorBrush(Colors.Red);
    if (source == "Yellow") return new SolidColorBrush(Colors.Yellow);

    // etc.

Instantiating the converter in XAML as a resource can be cumbersome, so there is another way to use value converters. By deriving from MarkupExtension, you can use the converter directly in XAML, without using StaticResource.

    [ValueConversion(typeof(string), typeof(Brush))]
    public class StringToBrushConverter : MarkupExtension, IValueConverter 
    {
        public override object ProvideValue(IServiceProvider serviceProvider) {
            return this;
        }

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
            if (value is string) {
                string source = value as string;
                if (source == "BlackSmoke") return new SolidColorBrush(Colors.DimGray);
            }

            // Default color
            return Colors.Black;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
            // To keep it simple, no need to convert back 
            return null;
        }
    }

and now you can use the converter like this,

    <TextBlock x:Name="MyText" Text="{Binding Name}" 
               Foreground="{Binding DisplayColor, Converter={local:StringToBrushConverter}}" />

WPF Simplified Part 12: Adorners

An Adorner is a new WPF construct that helps us add visual features to a UIElement. The Adorner class derives directly from FrameworkElement. So, why would you need another class to add visual effects when you can pretty much customize any WPF control using control templates and data templates? The reason is that the adorner layer lies on top of the UI element in Z-order and is helpful in some interactive scenarios to display handles and outlines.

Creating an adorner is simple, consider a simple button,

    <Button Height="30" Width="110" x:Name="MyButton" Content="Adorned Button" />

and the code behind,

    public class MyAdorner : Adorner {
        public MyAdorner(UIElement targetElement) : base(targetElement) { }

        protected override void OnRender(DrawingContext drawingContext) {
            Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
            drawingContext.DrawRectangle(null, new Pen(Brushes.Blue, 1), adornedElementRect);
        }

    }

We have created a simple adorner that will create a border around an UIElement. The adorner constructor requires the target UIElement and will draw on top of this element. To hook up the adorner we need some more code,

    void Window1_Loaded(object sender, RoutedEventArgs e) {
        AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.MyTextBlock);
        MyAdorner myAdorner = new MyAdorner(this.MyTextBlock);
        adornerLayer.Add(myAdorner);
    }

Note that since the adorner layer is not available until the window is loaded, we’ve hooked up the code in the window’s Loaded event handler. GetAdornerLayer walks up the visual tree to find the first AdornerLayer which then can be used to render our custom adorner. The adorner layer’s location in the visual tree is determined by the AdornerDecorator element, the Window element provides an AdornerDecorator by default, which is what we added our custom adorner to. Note that this layer fills the entire window. If we want we can supply our own AdornerDecorator,

    <AdornerDecorator>
        <TextBlock x:Name="MyTextBlock" Text="Cancel" />
    </AdornerDecorator>

The adorner receives focus and input before the adorned UIElement. Also, just like we added the custom adorner to the AdornerLayer, we can remove it too, simply by,

    adornerLayer.Remove(myAdorner);

Another way to create Adorners is by providing a VisualCollection,

    public class MyAdorner : Adorner {
        // To store and manage the adorner's visual children
        VisualCollection visualChildren;

        // Custom UIElement to add to the target UIElement
        Button button = new Button();

        // Initialize the custom Adorner
        public MyAdorner(UIElement targetElement) : base(targetElement) {
            visualChildren = new VisualCollection(this);

            // Set the button properties
            button.Height = 20.0;
            button.Width = 20.0;
            button.Content = "?";

            visualChildren.Add(button);
        }

        protected override int VisualChildrenCount { get { return visualChildren.Count; } }
        protected override Visual GetVisualChild(int index) { return visualChildren[index]; }

        // Arrange the Adorner
        protected override Size ArrangeOverride(Size finalSize) {
            double desiredWidth = AdornedElement.DesiredSize.Width;
            double desiredHeight = AdornedElement.DesiredSize.Height;

            button.Arrange(new Rect((button.Width + desiredWidth) / 2, -desiredHeight, 
                                 desiredWidth, desiredHeight));
            return finalSize;
        }
    }

Combined with this xaml,

    <Grid>
        <Button Height="30" Width="110" x:Name="MyButton" Content="Adorned Button" />
    </Grid>

should render,

image

Digg This

WPF Simplified Part 11: XAML Tricks

The Extensible Application Markup Language (aka XAML) is a declarative programming language that was specifically built for WPF to encourage separation of front-end appearance and back-end logic.

While a full explanation of XAML is beyond the scope of this post (see here for an introduction), we’ll focus on some uncommon features of XAML,

1. Escaping curly braces: Say you want to display curly braces in XAML,

        <!-- Error! -->
        <TextBlock Text="{test string}" />

will fail because the parser thinks that the string is a markup extension. There are two ways to fix this,

    a. Escape the curly braces:

    <TextBlock Text="{}{test string}" />

    b. Use the property element syntax:

    <TextBlock>{test string}</TextBlock>

2. Procedural code inside XAML: You can embed code inside your XAML file, though its not recommended.

    <Button x:Name="MyButton" Height="100" Width="100" Click="MyButton_Click" />
    <x:Code>
        <![CDATA[ 
            private void MyButton_Click(Object sender, RoutedEventArgs e) { 
                MessageBox.Show("Button click!"); 
            } 
        ]]>
    </x:Code>

3. Preserve white space: Say you want to preserve white spaces between text,

     <TextBlock>Text     spacing</TextBlock>

this will collapse the space between ‘Text’ and ‘spacing’, to preserve the spaces try this,

     <TextBlock xml:space="preserve">Text     spacing</TextBlock>

or simply,

    <TextBlock Text="Text     spacing" /> 

4. Use null in XAML: You can specify null in XAML using the x:Null markup extension,

    <Grid Background="{x:Null}">

5. Use StringFormat in XAML: Very useful for quick formatting,

    <TextBlock Text="{Binding Name, StringFormat=Hello {0}!!}" />
    <TextBlock Text="{Binding Date, StringFormat=Today\'s date is: {0:MM/dd/yyyy}}" />

6. Use MultiBinding with StringFormat:

    <TextBlock> 
        <TextBlock.Text> 
            <MultiBinding StringFormat="Name:{0}, Age:{1}"> 
                <Binding Path="Name" /> 
                <Binding Path="Age" /> 
            </MultiBinding> 
        </TextBlock.Text>
    </TextBlock>

7. Create an array in XAML: You can create an array in XAML,

    <ListBox>
        <ListBox.ItemsSource>
            <x:Array Type="{x:Type sys:String}">
                <sys:String>John</sys:String>
                <sys:String>Paul</sys:String>
                <sys:String>Andy</sys:String>
            </x:Array>
        </ListBox.ItemsSource>
    </ListBox>

where,

    xmlns:sys="clr-namespace:System;assembly=mscorlib"

In fact you can use other data types from the System namespace, like Int32, Boolean, and even DateTime,

    <ListBox>
        <ListBox.ItemsSource>
            <x:Array Type="{x:Type sys:DateTime}">
                <sys:DateTime>1/1/10</sys:DateTime>
                <sys:DateTime>3/3/10 03:21 PM</sys:DateTime>
            </x:Array>
        </ListBox.ItemsSource>
    </ListBox>

8. Specify an empty string in XAML:

    <ListBox>
        <ListBox.ItemsSource>
            <x:Array Type="{x:Type sys:String}">
                <sys:String>John</sys:String>
                <x:Static Member="sys:String.Empty" />
                <sys:String>Andy</sys:String>
            </x:Array>
        </ListBox.ItemsSource>
    </ListBox>
 

where,

    xmlns:sys="clr-namespace:System;assembly=mscorlib"

9. Put constants in XAML: Say you have a constant in code,

    public static string Extension = ".xaml"  //In class Window1
 

You can use this in XAML via the x:Static markup extension, like this,

    <TextBlock Text="{x:Static local:Window1.Extension}" />

where,

    xmlns:local="clr-namespace:WpfApplication1"

In fact, you can assign any static value,

    <Label Content="{x:Static sys:DateTime.Now}" />
    <Label Foreground="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />

10. Instantiate a class in XAML: Say you have the following class in your code,

    public class Person {
        public string Name { get; set; }
        public int Age { get; set; }
    }

You can instantiate an array in XAML, populate it with Person type objects and bind to a listbox,

    <Grid.Resources>
        <!-- Create a array of Person objects -->
        <x:Array x:Key="Office" Type="{x:Type local:Person}">
            <!-- Instantiate a Person and add to the array -->
            <local:Person Name="Michael" Age="40"/>
            <local:Person Name="Jim" Age="30"/>
            <local:Person Name="Dwight" Age="30"/>
        </x:Array>
    </Grid.Resources>
    
    <!-- Bind the array to the listbox -->
    <ListBox ItemsSource="{Binding Source={StaticResource Office}}" DisplayMemberPath="Name" />

If you have a collection,

    public class Family : ObservableCollection<Person> {
        public Family() {
            Add(new Person() { Name = "Jim", Age = 30 });
            Add(new Person() { Name = "Pam", Age = 30 });
        }
    }

we can instantiate the collection in XAML and then bind to it,

    <Grid.Resources>
        <!-- Instantiate the Family class -->
        <local:Family x:Key="Family" />
    </Grid.Resources>
    <!-- Bind to the collection -->
    <ListBox ItemsSource="{Binding Source={StaticResource Family}}" DisplayMemberPath="Name" />

See more XAML tricks here.

Digg This