Click here to Skip to main content
15,880,972 members
Articles / Programming Languages / C# 3.5

WPF Fundamental Concepts in Easy Samples Part 2 MultiBindings, ControlTemplates, Styles

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
20 Dec 2015CPOL11 min read 21.4K   216   17  
continues explanation of basic WPF concepts - multibinding, control templates and styles

Introduction

WPF is an extremely powerful UI development package. Part of the reason for its power is that WPF architects came up with completely new concepts that give software development a new dimension. These concepts are not used outside of WPF and many people switching to programming WPF from other languages and packages might have difficulty understanding them in the beginning.

The purpose of this series of articles is to teach these concepts and demonstrate their usage via very simple C#/WPF samples.

The topics discussed here might be somewhat disconnected from each other, so, perhaps you should try to read only one topic at a time.

First article in the series covered Dependency Properties (DPs), Attached Properties (APs) and Bindings: WPF Fundamental Concepts in Easy Samples Part 1: Dependency Properties, Attached Properties and Bindings.

In this installment I plan to cover

  1. MultiBindings
  2. ControlTemplates
  3. Styles

 

Other concepts:

  1. DataTemplates
  2. MVVM
  3. Visual Trees
  4. Routed and Attached Events
  5. Behaviors

will be discussed in future installments.

 

My purpose here is not to provide a full description of all the features of the described concepts, rather I'll concentrate on what I consider most important features that I myself use several times per day. The less important features can be learned by the developers on their own as they build the applications using WPF functionality.

This article is aimed at people who have some basic WPF knowledge but are looking to improve their WPF skills and practices. In particular I assume that people reading this article are familiar with XAML and to some degree have come across the above mentioned concepts, e.g. they might not have created any dependency properties themselves, but they are aware that such concept exists.

I also assume the readers read or at least looked through the first article in the series: WPF Fundamental Concepts in Easy Samples Part 1: Dependency Properties, Attached Properties and Bindings and have some understanding of Lookless or Custom WPF Controls explained in WPF Lookless Controls.

MultiBindings

Bindings and MultiBindings

As was explained in WPF Fundamental Concepts in Easy Samples Part 1: Dependency Properties, Attached Properties and Bindings, WPF Binding creates a relationship between two properties (the source and the target property). The source property is taken with respect to the source object (it can be given by a complex path that specifies a property within a property etc... with the root of the path being the source object). The target property is always a dependency or attached property on the target object.

Most often, Binding ensures that the target property value matches that of the source property value - they do not have to be equal, if, e.g. a value converter is used.

Two way bindings, maintain the relationship between source and target values whenever each one of them changes.

Image 1

WPF MultiBinding is very similar to the Binding, but allows to use multiple sources for the same target, so that the target changes whenever each one of the source value changes. These multiple sources are combined into one value via a multi value converter (which implements IMultiValueConverter interface).

Just like Binding, the MultiBinding can work both ways (in TwoWay mode) and from target to multiple sources (in OneWayToSource mode), but these two modes are rarely used because, usually one cannot uniquely recreate multiple sources from a single target.

Image 2

Concatenation MultiBinding Sample

This sample is located within ConcatenationMultiBinding project.

If you run the project you'll see 3 editable TextBoxes at the top row and one TextBlock underneath. After you type some texts in all of these text boxes, the TextBox will display the concatenation of these texts:

Image 3

Here is the relevant XAML code:

XAML
<Grid VerticalAlignment="Center">
    <Grid.Resources>
        <local:ConcatenationMultiValueConverter x:Key="TheConcatenationMultiValueConverter"/>
    </Grid.Resources>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid Margin="0,2">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <TextBox x:Name="Input1"
                 Grid.Column="0"
                 HorizontalAlignment="Stretch"
                 Margin="2,0"/>
        <TextBox x:Name="Input2"
                 Grid.Column="1"
                 HorizontalAlignment="Stretch"
                 Margin="2,0" />
        <TextBox x:Name="Input3"
                 Grid.Column="2"
                 HorizontalAlignment="Stretch"
                 Margin="2,0" />
    </Grid>
    <TextBlock Grid.Row="1"
               HorizontalAlignment="Stretch"
               Background="White"
               Margin="2"
               Height="23">
        <TextBlock.Text>
            <MultiBinding Converter="{StaticResource TheConcatenationMultiValueConverter}">
                <Binding Path="Text" 
                         ElementName="Input1"/>
                <Binding Path="Text"
                         ElementName="Input2" />
                <Binding Path="Text"
                         ElementName="Input3" />
            </MultiBinding>
        </TextBlock.Text>
    </TextBlock>
</Grid>  

The part that interests us most is the part containing the MultiBinding:

XAML
<TextBlock Grid.Row="1"
           HorizontalAlignment="Stretch"
           Background="White"
           Margin="2"
           Height="23">
    <TextBlock.Text>
        <MultiBinding Converter="{StaticResource TheConcatenationMultiValueConverter}">
            <Binding Path="Text" 
                     ElementName="Input1"/>
            <Binding Path="Text"
                     ElementName="Input2" />
            <Binding Path="Text"
                     ElementName="Input3" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

The Text dependency property of the TextBlock is multi-bound to the Text properties of three TextBoxes named "Input1", "Input2" and "Input3". The multi value converter provided by the resource "TheConcatenationMultiValueConverter" combines those texts into the output value by concatenating them.

Let us take a look at ConcatenationMultiValueConverter class:

public class ConcatenationMultiValueConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        string result = "";

        // concatenate all values if they are not null
        foreach(object val in values)
        {
            if (val == null)
                continue;

            result += val.ToString();
        }

        return result;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

You can see that IMultiValueConverter interface is very similar to IValueConverter interface used to convert the source value to the target value by the usual (single value) Binding, explained in WPF Fundamental Concepts in Easy Samples Part 1: Dependency Properties, Attached Properties and Bindings. The only difference is that its Convert(...) method accepts an array of values instead of a single value as the first parameter and also its ConvertBack(...) method returns an array of objects instead of a single object.

Resolving the Problem of Changing Format With MultiBinding

The tremendous power of MultiBinding is also highlighted by an example below, showing how, with its help, one can imitate the binding for a property that is neither dependency nor attached property.

Assume that we want to display some values (date or time or price or integer) using various formats. It would be reasonable to use a WPF Binding to display the value and to use the Binding's StringFormat property to set the format.

Unfortunately the Binding is not a dependency object and correspondingly its StringFormat property is not a dependency property and therefore cannot be a target of another Binding, so once the Binding's StringFormat is set, there is no easy and WPF friendly way to change it. This is where MultiBinding comes to the rescue.

Running FormatMultiBindingSample project will produce a window with a TextBox for entering format. Once you enter a valid C# format e.g. "{0:yyyy/MM/dd}" as shown on the picture below, the January 25th, 2015 date will appear in that format:

Image 4

Changing the format string will result in changed date representation (as long as the format is valid, of course). E.g. using "{0:MMMM/dd, yyyy}", will result in "January/25, 2015" displayed.

Here is the relevant XAML code:

XAML
<Window.Resources>
    <sys:DateTime x:Key="TheDate">2015-01-25</sys:DateTime>
    <local:FormatMultiValueConverter x:Key="TheFormatMultiValueConverter"/>
</Window.Resources>
<Grid VerticalAlignment="Center">
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal"
                HorizontalAlignment="Center">
        <TextBlock Text="Enter Date Format: " />
        <TextBox x:Name="TheFormatTextBox" Width="120"/>
    </StackPanel>

    <TextBlock HorizontalAlignment="Center"
               VerticalAlignment="Center"
               Grid.Row="1"
               Margin="0,5">
        <TextBlock.Text>
            <MultiBinding Converter="{StaticResource TheFormatMultiValueConverter}">
                <Binding Source="{StaticResource TheDate}" />
                <Binding Path="Text"
                         ElementName="TheFormatTextBox" />
            </MultiBinding>
        </TextBlock.Text>
    </TextBlock>
</Grid>  

The date we want to format is defined as a resource.

The MultiBinding binds to the date as the first parameter and to the format (which is editable) as the second. FormatMultiValueConverter is employed to produce the target value. Here is the converter's code:

public class FormatMultiValueConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        // if no values - return null
        if (values.Length == 0)
            return null;

        // this is the formatted value
        object val = values[0];

        // if no format parameter passed,
        // return value
        if (values.Length == 1)
            return val;

        string format = values[1] as string;

        // if the passed format parameter
        // is null or empty, return value
        if (string.IsNullOrEmpty(format))
            return val;

        // format the value and 
        // return the result.
        try
        {
            return string.Format(format, val);
        }
        catch
        {
            // in case of misformatting
            // return the value itself
            return val;
        }
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}  

As explained in the code's comments, the converter interprets the first parameter as the object to format, and the second parameter as the formatting string and uses string.Format(format, val) method to produce the target value.

Because of the MultiBinding, the target value will be updated if either the source object value or the format value changes.

Note that the MultiBinding converter is more generic than the sample and can be used to format any objects, not only the dates.

ControlTemplates

Discussion about ControlTemplates, Styles and DataTemplates

There are several concepts that come with WPF that can and should be employed for XAML code reuse. The most prominent among them are

  • ControlTemplates - used for creating a visual representation for WPF lookless controls.
  • Styles - used for specifying the customization properties for WPF elements and controls.
  • DataTemplates - used for providing visual representation for non-visual, non-WPF objects (including the famous View Models).

 

All three of the constructs have ability to define triggers that can change dependency and attached properties on objects defined within the constructs based on some condition being fulfilled and then revert the property back once the condition is no longer valid.

I already described the working of the ControlTemplates in WPF Lookless Controls article, so here I will only provide a refresher sample and show how to use property triggers in it.

Simple ControlTemplate Sample with Property Triggers

Here is what you see if you run SimpleControlTemplateWithTriggers project, click the check box on the left side and place the mouse over the check box:

Image 5

If you move the mouse not to be on top of the check box, the text's background will change to white, and if you uncheck the check box, the text will not be visible any more.

This is a visual representation of a very simple lookless control SimpleLooklessControl that defines only one property (dependency property) - TheText:

public class SimpleLooklessControl : Control
{
    #region TheText Dependency Property
    public string TheText
    {
        get { return (string)GetValue(TheTextProperty); }
        set { SetValue(TheTextProperty, value); }
    }

    public static readonly DependencyProperty TheTextProperty =
    DependencyProperty.Register
    (
        "TheText",
        typeof(string),
        typeof(SimpleLooklessControl),
        new PropertyMetadata(null)
    );
    #endregion TheText Dependency Property
}  

Here is the relevant XAML code:

XAML
<Grid HorizontalAlignment="Center"
      VerticalAlignment="Center">
    <Grid.Resources>
        <BooleanToVisibilityConverter x:Key="TheBooleanToVisibilityConverter"/>
        <ControlTemplate TargetType="local:SimpleLooklessControl"
                         x:Key="TheSimpleLooklessControlTemplate">
            <Border BorderBrush="Black"
                    BorderThickness="1"
                    Background="{TemplateBinding Background}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <CheckBox x:Name="TheVisibilityCheckBox"
                              Margin="2,0,10,0"
                              VerticalAlignment="Center" />
                    <TextBlock x:Name="TheText"
                               Text="{TemplateBinding TheText}"
                               Grid.Column="1"
                               HorizontalAlignment="Stretch"
                               VerticalAlignment="Center"
                               Visibility="{Binding Path=IsChecked,
                                                        ElementName=TheVisibilityCheckBox,
                                                        Converter={StaticResource TheBooleanToVisibilityConverter}}" />
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <!--the trigger that changes the text's 
                    background to yellow when the mouse is 
                    placed over the check box -->
                <Trigger SourceName="TheVisibilityCheckBox"
                         Property="IsMouseOver"
                         Value="True">
                    <Setter TargetName="TheText"
                            Property="Background"
                            Value="Yellow" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Grid.Resources>
    <local:SimpleLooklessControl Width="200"
                                 Height="25"
                                 TheText="Hello World"
                                 Template="{StaticResource TheSimpleLooklessControlTemplate}"/>
</Grid>  

Note that we use {TemplateBinding TheText} to bind the Text property of the TextBlock within the template to the dependency property TheText of the lookless control which uses the template to visualize itself. As was explained in WPF Lookless Controls, TemplateBinding is just a shorthand for the usual WPF Binding used with RelativeSource mode set to TemplatedParent, so the same binding could have been rewritten as {Binding Path=TheText, RelativeSource={RelativeSource TemplatedParent}}

Let us take a closer look at the trigger code:

XAML
<Trigger SourceName="TheVisibilityCheckBox"
         Property="IsMouseOver"
         Value="True">
    <Setter TargetName="TheText"
            Property="Background"
            Value="Yellow" />
</Trigger>

The trigger condition is specified by the attributes of the Trigger tag: SourceName attribute specifies the name of the element whose property the trigger observes (in our case it is our checkbox). Property attribute specifies the name of the observed dependency or attached property (in our case it is IsMouseOver dependency property defined on any WPF element). Value specifies the observed property's value under which the trigger fires its setter(s). So, in our case, the trigger condition reads - fire setters when IsMouseOver property is true on the element called "TheVisibilityCheckBox".

The setter attributes mirror those of the condition. TargetName - is the name of the affected element within the ControlTemplate. Property is the name of the changed property. Value attribute specifies the new value for the property that the setter sets it to. In our case the setter reads - change the Background property on the element named "TheText" to "Yellow" color.

The SourceName for the trigger condition does not have to be specified. In that case the trigger will be looking for a property defined on the whole control. Analogously, the TargetName does not have to be specified on the setter. In such case, the setter will change the corresponding property on the whole control also.

Even though the sample uses a single trigger with a single setter, the ControlTemplate might have multiple triggers and each trigger can have multiple setters.

Note, that instead of the binding with converter, we could have also used the property trigger to control the visibility of the TextBlock when the button is checked via the following trigger:

XAML
<Trigger SourceName="TheVisibilityCheckBox"
         Property="IsChecked"
         Value="True">
    <Setter TargetName="TheText"
            Property="Visibility"
            Value="Visible" />
</Trigger>  

In that case, the default Visibility of the TextBlock should have been set to Collapsed.

Many times the triggers can be used instead of the Bindings with custom converters (which allows to avoid creating a custom converter C# class).

Other Types of Triggers

There are other, more complex triggers that can be used in ControlTemplates, Styles and DataTemplates. They are:

  • DataTriggers
  • MultiTrigger
  • MultiDataTriggers

 

DataTriggers allow to specify more complex conditions based on the bindings to properties. They are rarely used in ControlTemplates and more often used in DataTemplates. We will talk about them in the next installment that is going to discuss the DataTemplates.

MultiTrigger and MultiDataTrigger are used rarely (in my view) and I am not going to include the relevant samples into this series of articles. In general, they allow to specify multiple conditions instead of a single condition, so that the trigger fires only when all of those multiple conditions are satisfied.

WPF Styles

Introduction to Styles

Styles is another very important concept used throughout WPF in order to reduce XAML complexity and increase re-use of XAML parts. Styles can be considered as dictionaries of dependency (and attached) property values. They can be reused on multiple WPF controls and elements.

WPF Styles allow single inheritance, under which the sub-style inherits all the properties from the superstyle (but can override them if chooses).

Simple Style Sample with Inheritance

Assume that a lot of text blocks in your application will have Segoe UI font. You create a style that only defines that font:

XAML
<Style x:Key="BaseTextStyle"
       TargetType="TextBlock">
    <Setter Property="FontFamily"
            Value="Segoe UI"/>
</Style>  

Then you derive the rest of your styles from it. e.g. the HeaderStyle for header and PlainTextStyle for plain text:

XAML
<Style x:Key="HeaderStyle"
       TargetType="TextBlock"
       BasedOn="{StaticResource BaseTextStyle}">
    <Setter Property="FontSize"
            Value="20"/>
    <Setter Property="FontWeight"
            Value="Bold"/>
    <Setter Property="HorizontalAlignment"
            Value="Center"/>
</Style>


<Style x:Key="PlainTextStyle"
       TargetType="TextBlock"
       BasedOn="{StaticResource BaseTextStyle}">
    <Setter Property="FontSize"
            Value="12" />
    <Setter Property="FontWeight"
            Value="Normal" />
    <Setter Property="HorizontalAlignment"
            Value="Left" />
    <Style.Triggers>
        <Trigger Property="IsMouseOver"
                 Value="True">
            <Setter Property="Background"
                    Value="Yellow"/>
        </Trigger>
    </Style.Triggers>
</Style>  

The styles are derived by setting their BasedOn property to point to the super-style.

You can set the TextBlocks in you code to use the styles by utilizing their Style property:

XAML
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <TextBlock Text="This is a Header"
               Style="{StaticResource HeaderStyle}"/>


    <TextBlock Text="This is a plain text"
               Style="{StaticResource PlainTextStyle}" 
               Grid.Row="1">/>
</Grid>  

SimpleStyleWithInheritanceSample project shows how to create a Style inheritance hierarchy. Here is what you see when you run the project:

Image 6

If you place the mouse on top of the plain text, its background will change to yellow. This is achieved with the help of the Style trigger:

XAML
<Style.Triggers>
    <Trigger Property="IsMouseOver"
             Value="True">
        <Setter Property="Background"
                Value="Yellow"/>
    </Trigger>
</Style.Triggers>

Note, that the style triggers do not have SourceName property and the style trigger setters do not have TargetName property - style property triggers can only change the properties on the element on which the style is defined.

If you know that most of your text is plain (not a header), you can turn "PlainTextStyle" into the default style by removing the Key from it. In such case you do not need to set Style property on the TextBlock if you want it to have a default style:

XAML
...
<Window.Resources>
    ...
    <Style TargetType="TextBlock"
           BasedOn="{StaticResource BaseTextStyle}">
        <Setter Property="FontSize"
                Value="12" />
    ...
    </Style>
<Window.Resources/>
...
<TextBlock Text="This is a plain text"
          Grid.Row="1">/>
...

Defining Styles in a Separate Resource Dictionary

In majority of cases, it is better to define your styles in a separate resource dictionary so that they could be visible in many different XAML files. The resource dictionary can be specified in the same project or in a different project.

Creating and using the styles defined in separate resource dictionaries has been described in WPF Lookless Controls and I refer the readers to that article for a refresher.

Summary

This article continues discussion of the basic WPF concepts that might be helpful to every WPF developer. In the next article in this series I plan to discuss the Data Templates and the MVVM pattern.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect AWebPros
United States United States
I am a software architect and a developer with great passion for new engineering solutions and finding and applying design patterns.

I am passionate about learning new ways of building software and sharing my knowledge with others.

I worked with many various languages including C#, Java and C++.

I fell in love with WPF (and later Silverlight) at first sight. After Microsoft killed Silverlight, I was distraught until I found Avalonia - a great multiplatform package for building UI on Windows, Linux, Mac as well as within browsers (using WASM) and for mobile platforms.

I have my Ph.D. from RPI.

here is my linkedin profile

Comments and Discussions

 
-- There are no messages in this forum --