Click here to Skip to main content
Click here to Skip to main content

WPF Control Patterns. (WPF and XAML Patterns of Code Reuse in Easy Samples. Part 1)

, 5 Jul 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Article describes WPF and XAML patterns for code and functionality reuse.

Introduction

There is a popular misconception that XAML does not allow good separation of concerns and code reuse. In fact the opposite is true. WPF enables unique programming techniques which (once you understand it) brings the code reuse and customization to a new level.

WPF and XAML introduced a number of revolutionary programming concepts which can be applied outside of WPF to purely non-visual programming and can be used in other (non .NET) platforms.

From my point of view, the WPF-esq concepts comprise a step above the usual Object Oriented Programming similar in magnitude to what OOP was in comparison to the procedural programming.

Unfortunately Microsoft leadership itself did not quite understand what their engineers came up with and switched their priorities.

As was stated above, the WPF concepts can be generalized and applied outside of WPF domain e.g. to build non-visual software. In fact I published a series of blog posts on applying WPF paradigms outside of WPF domain. This article, however, does not deal with that. It simply presents and classifies the patterns by using WPF examples.

Using the XAML/WPF paradigms described below I managed to program with great speed at the same time producing high quality and high density code (code with large reuse and large amount of functionality per unit of code).

This is not a beginner's WPF tutorial - I assume that the readers are familiar with basic WPF concepts - dependency and attached properties, bindings, styles, templates, data templates, triggers, routed events.

I plan this article as the first installment in a series of 2 or 3 articles. This part is dedicated to patterns of reuse based on Controls and ControlTemplates. The subsequent articles will deal more with MVVM pattern and mapping non-visual objects into visual ones. 

Controls and Control Templates

Controls

WPF Control is one of the basic visual units of reuse in WPF. Controls can be placed in XAML code including other controls and made work together using various means. Here we shall go over dos and don'ts of programming with WPF controls based on very simple examples.

Let us first consider a very simple control - it will allow the user to enter some text into an editable field (using e.g. a TextBox), it will provide a label for that editable field. (The label will give the name of the field explaining what the user is entering). Also the control is going to have a Button "Save" as an example of doing some action on the entered text. We shall call this control EditableTextAndLabelControl.

Anti-Pattern - User Control with Code Behind

The simplest and the worst way of creating such control is by using WPF's UserControl functionality. The code for this anti-pattern is located under UserControlWithCodeBehind project. Unfortunately the Visual Studio provides an off-the-shelf way to create user controls - all you need to do is to right click on the project within the solution explorer, choose "New Item" under menu "Add" and choose "User Control (WPF)" (do not choose simply "User Control" - this will create a Win Forms User Control). Name the control "EditableTextAndLabelControl".

After clicking OK button you'll see that two files are created: XAML file "EditableTextAndLabelControl.xaml" and code behind file "EditableTextAndLabelControl.xaml.cs". XAML file will serve to specify the layout of the control while the code behind can be used to define the control's properties and behaviors. It is very convenient that the code behind can access the controls defined in XAML by name (but this is a deceptive advantage of programming with the code behind).

In the MainWindow we display two such controls with labels "MyText" and "MyOtherText". When "Save" button of the corresponding control is pressed, the SaveEvent of the control is fired. It is handled at the MainWindow level and we print the following lines underneath the controls e.g.: "Saved string 'Hello' for label 'MyText'":

Here is the significant part of the XAML code of MainWindow class:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <this:EditableTextAndLabelControl x:Name="MyUserControl1"
                                      HorizontalAlignment="Center"
                                      VerticalAlignment="Center"
                                      TheLabel="MyText"
                                      Margin="0,10"/>
    <this:EditableTextAndLabelControl x:Name="MyUserControl2"
                                      Grid.Row="1"
                                      HorizontalAlignment="Center"
                                      VerticalAlignment="Center"
                                      TheLabel="MyOtherText"                                         
                                      Margin="0,10"/>
    <TextBlock x:Name="TheSaveEventLog"
               Grid.Row="2"
               HorizontalAlignment="Stretch"
               VerticalAlignment="Stretch"/>
</Grid>

We simply define the two controls and under them we define a TextBlock to display the results of firing the SaveEvent.

The MainWindow.xaml.cs code is also very simple:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        MyUserControl1.SaveEvent += MyUserControl_SaveEvent;
        MyUserControl2.SaveEvent += MyUserControl_SaveEvent;
    }

    void MyUserControl_SaveEvent(string arg1, string arg2)
    {
        TheSaveEventLog.Text += "\nSaved string \"" + arg2 + "\" for label \"" + arg1 + "\"";
    }
}  

We access the two user controls by their names and assign MyUserControl_SaveEvent as a handler for their SaveEvent. MyUserControl_SaveEvent function adds a new line to the Text property of TheSaveEventLog control.

Now let us take a look to the code of the EditableTextAndLabelControl itself

. The control's code behind defines two dependency properties TheLabel and TheEditableText on the control. It also defines SaveEvent that fired when the the button is clicked:

public partial class EditableTextAndLabelControl : UserControl
{
public event Action<string, string> SaveEvent = null;

public EditableTextAndLabelControl()
{
    InitializeComponent();

    SaveButton.Click += SaveButton_Click;
}

// fires SaveEvent of the control
void SaveButton_Click(object sender, RoutedEventArgs e)
{
    if (SaveEvent != null)
    {
        SaveEvent(TheLabel, TheEditableText);
    }
}

#region TheLabel Dependency Property
public string TheLabel
{
    get { return (string)GetValue(TheLabelProperty); }
    set { SetValue(TheLabelProperty, value); }
}

public static readonly DependencyProperty TheLabelProperty =
DependencyProperty.Register
(
    "TheLabel",
    typeof(string),
    typeof(EditableTextAndLabelControl),
    new PropertyMetadata(null)
);
#endregion TheLabel Dependency Property


#region TheEditableText Dependency Property
public string TheEditableText
{
    get { return (string)GetValue(TheEditableTextProperty); }
    set { SetValue(TheEditableTextProperty, value); }
}

public static readonly DependencyProperty TheEditableTextProperty =
DependencyProperty.Register
(
    "TheEditableText",
    typeof(string),
    typeof(EditableTextAndLabelControl),
    new PropertyMetadata(null)
);
#endregion TheEditableText Dependency Property
}  

Note that the mechanism for making the SaveEvent fire when the "Save" button is clicked is contained within the code behind SaveButton.Click += SaveButton_Click;, while the binding between the controls and the dependency properties is defined in XAML:

<StackPanel Orientation="Horizontal">
    <TextBlock x:Name="TheLabelTextBlock"
               Text="{Binding Path=TheLabel, RelativeSource={RelativeSource AncestorType=this:EditableTextAndLabelControl}}"
               VerticalAlignment="Bottom"/>

    <TextBox x:Name="TheEditableTextTextBox"
             Grid.Column="1"
             Width="100"
             Text="{Binding Path=TheEditableText, RelativeSource={RelativeSource AncestorType=this:EditableTextAndLabelControl}}"
             VerticalAlignment="Bottom"
             Margin="10,0,10,0"/>

    <Button x:Name="SaveButton"
            Content="Save"
            Width="70"
            Grid.Column="2"
            VerticalAlignment="Bottom"/>
</StackPanel>  

Pattern - Lookless Control

The problem with the (anti-)pattern presented above is that the control is very tightly coupled with the visual representation. Suppose that we want to have different visual representations of the controls e.g. we want to display one of the controls horizontally (as above) and one of them vertically i.e. label being on top of the TextBox with the "Save" Button at the bottom. We would have to create two completely different controls with identical code behind functionality and even very similar but not identical XAML code.

In this section, we create a very similar lookless control and show how it can have different visual representations. The code for this section is contained under LooklessControlSample project.

When you build a lookless control, do not try to nail down its visual representation first. Instead think about what properties and events it has in order to interact with other controls or with the View Model.

Just like in previous section, create a new item, but instead of choosing "User Control (WPF)" as the item type, create simply a C# class EditableTextAndLabelControl. Make the class public and make it inherit from WPF Control class:

public class EditableTextAndLabelControl : Control
{
}  

Just like in the previous sample, add two string typed dependency properties to it: TheLabel and TheEditableText; also add SaveEvent of type Action<string, string>. It is best to use propdp snippet for defining dependency properties in a class. The result will look like this:

public partial class EditableTextAndLabelControl : Control
{
    public event Action<string, string> SaveEvent = null;


    #region TheLabel Dependency Property
    public string TheLabel
    {
        get { return (string)GetValue(TheLabelProperty); }
        set { SetValue(TheLabelProperty, value); }
    }

    public static readonly DependencyProperty TheLabelProperty =
    DependencyProperty.Register
    (
        "TheLabel",
        typeof(string),
        typeof(EditableTextAndLabelControl),
        new PropertyMetadata(null)
    );
    #endregion TheLabel Dependency Property


    #region TheEditableText Dependency Property
    public string TheEditableText
    {
        get { return (string)GetValue(TheEditableTextProperty); }
        set { SetValue(TheEditableTextProperty, value); }
    }

    public static readonly DependencyProperty TheEditableTextProperty =
    DependencyProperty.Register
    (
        "TheEditableText",
        typeof(string),
        typeof(EditableTextAndLabelControl),
        new PropertyMetadata(null)
    );
    #endregion TheEditableText Dependency Property
}

This is basically it - we have defined the lookless control. Let us place the controls into the MainWindow.xaml file and see how they look. Here is the XAML code of MainWindow.xaml (in fact it is identical to the one of the previous sample):

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <this:EditableTextAndLabelControl x:Name="MyControl1"
                                      HorizontalAlignment="Center"
                                      VerticalAlignment="Center"
                                      TheLabel="MyText"
                                      Margin="0,10"/>
    <this:EditableTextAndLabelControl x:Name="MyControl2"
                                      Grid.Row="1"
                                      HorizontalAlignment="Center"
                                      VerticalAlignment="Center"
                                      TheLabel="MyOtherText"                                         
                                      Margin="0,10"/>
    <TextBlock x:Name="TheSaveEventLog"
               Grid.Row="2"
               HorizontalAlignment="Stretch"
               VerticalAlignment="Stretch"/>
</Grid>

If we try to run the project as is, we shall get an empty window. The reason is that we never defined the visual representation for the lookless control. We should define it as ControlTemplate. For clarity sake XAML coders usually create a special project folder for templates and styles. Let us create the folder "Themes" in our project and create a WPF Resource dictionary SampleControls.xaml in it. We can define the XML namespace this by adding the following line to header tag of the resource dictionary: xmlns:this="clr-namespace:LooklessControlSample". Then within the ResourceDictionary we can define our control template:

<ResourceDictionary  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                     xmlns:this="clr-namespace:LooklessControlSample">
    <ControlTemplate x:Key="TheHorizontalTemplateForEditableTextAndLabelControl">
        <StackPanel Orientation="Horizontal">
            <TextBlock x:Name="TheLabelTextBlock"
                       Text="{Binding Path=TheLabel, RelativeSource={RelativeSource AncestorType=this:EditableTextAndLabelControl}}"
                       VerticalAlignment="Bottom" />

            <TextBox x:Name="TheEditableTextTextBox"
                     Grid.Column="1"
                     Width="100"
                     Text="{Binding Path=TheEditableText, RelativeSource={RelativeSource AncestorType=this:EditableTextAndLabelControl}}"
                     VerticalAlignment="Bottom"
                     Margin="10,0,10,0" />

            <Button x:Name="SaveButton"
                    Content="Save"
                    Width="70"
                    Grid.Column="2"
                    VerticalAlignment="Bottom" />
        </StackPanel>
    </ControlTemplate>
<ResourceDictionary/>  

In order to make our MainWindow.xaml file notice the SampleControls.xaml file we need to add the following XAML code to its top:

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Themes/SampleControls.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>  

Note that if the XAML resource dictionary files are located in a different project, the short notation for the Source property above is not sufficient, one would also need to specify the assembly name.

To make the controls recognize the fact that they have that template, we set their Template properties to "{StaticResource TheHorizontalTemplateForEditableTextAndLabelControl}".

Now if we run our code, we shall see the application that is almost similar to the one of our previous sample except that nothing happens when we press button "Save" on our controls.

There are 3 major ways to make the lookless controls aware of the events happening on its visuals:

  1. Using built-in WPF Command Functionality
  2. Using MS Expression Blend SDK
  3. By accessing control's visual parts from C# code.

We shall describe all of them below.

Communicating between the Visual and Non-Visual Parts of the Lookless Control using built-in WPF Command Functionality

This is the way that most WPF tutorials describe, it is also the worst way of communicating between the visual and non-visual functionality. It is bad because of two reasons:

  1. Only a few events on a few controls, like Click event on a Button or Menu controls can be handled using command functionality.
  2. It will require a modification of the lookless control class by adding a DelegateCommand object to it.

Since we do not think that this is a good way of communicating between the visual and non-visual functionality and since it has been widely covered elsewhere - we are not going to provide an example for it here.

 

Communicating between the Visual and Non-Visual Parts of the Lookless Control using MS Expression Blend SDK

This is probably the most elegant way to pass the events from the visual (XAML) to non-visual code. I've described this method in detail in MVVM Pattern Made Simple article applying it to MVVM pattern. The same principle can be applied in lookless controls.

First of all, do not be put down by the words "Expression Blend" - this is just an SDK and you do not have to install MS Expression Blend in order to obtain it. In fact it can be downloaded from MS Expression Blend SDK and used for free. On top of that, we only need two dll files from the SDK: Microsoft.Expression.Interactions.dll and System.Windows.Interactivity.dll and we provide these files together with our code under MSExpressionBlendSDKDlls folder.

Since you download these files from the internet, you'll probably have to unblock them. In order to do it, right click on each of the files, choose Properties menu item and click "Unblock" button.

The project, of course, needs to reference those files and in order to get access to the SDK functionality the following xml namespaces should be added to the top tag of "SampleControls.xaml":

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"  

Let us add a method Save to our lookless control EditableTextAndLabelControl:

public void Save()
{
    if (SaveEvent != null)
        SaveEvent(TheLabel, TheEditableText);
}

This method simply fires SaveEvent.

Now we can make the button SaveButton call method Save of the EditableTextAndLabelControl class by adding the following XAML code within the <Button> tag:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Click"
                    SourceObject="{Binding ElementName=SaveButton}">
        <ei:CallMethodAction MethodName="Save"
                             TargetObject="{Binding RelativeSource={RelativeSource AncestorType=this:EditableTextAndLabelControl}}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

Now, if we run the project it will run exactly like the one from the previos section - adding lines when the "Save" button is pressed.

Communicating between the Visual and Non-Visual Parts of the Lookless Control by Accessing Control's Visual Parts from C# code.

This method is most complex and should only be used when it is necessary to get full access to the control's visuals from the C# code. We shall explain this method in detail later in order not to break the narration.

Different Layouts for the same Lookless Control

Here we are going to show how to produce different visual layouts for the same lookless control.

Open the file "SampleControls.xaml" and create another ControlTemplate almost exactly the same as our "TheHorizontalTemplateForEditableTextAndLabelControl" template, only using vertical StackPanel orientation instead of horizontal. Let us call this ControlTemplate "TheVerticalTemplateForEditableTextAndLabelControl":

<ControlTemplate x:Key="TheVerticalTemplateForEditableTextAndLabelControl"
                 TargetType="this:EditableTextAndLabelControl">
    <StackPanel Orientation="Vertical">
        <TextBlock x:Name="TheLabelTextBlock"
                   Text="{Binding Path=TheLabel, RelativeSource={RelativeSource AncestorType=this:EditableTextAndLabelControl}}"
                   VerticalAlignment="Bottom" />

        <TextBox x:Name="TheEditableTextTextBox"
                 Grid.Column="1"
                 Width="100"
                 Text="{Binding Path=TheEditableText, RelativeSource={RelativeSource AncestorType=this:EditableTextAndLabelControl}}"
                 VerticalAlignment="Bottom"
                 Margin="10,0,10,0" />

        <Button x:Name="SaveButton"
                Content="Save"
                Width="70"
                Grid.Column="2"
                VerticalAlignment="Bottom">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click"
                                SourceObject="{Binding ElementName=SaveButton}">
                    <ei:CallMethodAction MethodName="Save"
                                         TargetObject="{Binding RelativeSource={RelativeSource AncestorType=this:EditableTextAndLabelControl}}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
    </StackPanel>
</ControlTemplate>  

Let us assign this template to the second control:

<this:EditableTextAndLabelControl x:Name="MyControl2"
                                  Grid.Row="1"
                                  HorizontalAlignment="Center"
                                  VerticalAlignment="Center"
                                  Template="{StaticResource TheVerticalTemplateForEditableTextAndLabelControl}"
                                  TheLabel="MyOtherText"
                                  Margin="0,10" />

Here is what we are going to get:

Two controls of the same type are styled in two different ways by two different templates.

Using WPF Styles instead of Templates

Very often WPF developers prefer to use Styles instead of ControlTemplates. The Style would allow to set Template property on a control, but it can also be used for setting other properties e.g. Width, Height, alignments and so on. Also a style without a key (but with TargetType property defined) can be considered a default style for the control. E.g. if we assume that in majority of cases we'll need the horizontal template, we can make the corresponding style to be the default while in order for the control to get a vertical style one would have to provide the key.

Such styles are defined in Themes/SampleControls.xaml file of the sample:

<!-- Horizontal Style for EditableTextAndLabelControl (this is a default style for the 
    control - a style without a resource key) -->
<Style TargetType="this:EditableTextAndLabelControl">
    <Setter Property="Template"
            Value="{StaticResource TheHorizontalTemplateForEditableTextAndLabelControl}" />
</Style>

<!-- Vertical Style for EditableTextAndLabelControl -->
<Style TargetType="this:EditableTextAndLabelControl"
       x:Key="TheVerticalStyleForEditableTextAndLabelControl">
    <Setter Property="Template"
            Value="{StaticResource TheVerticalTemplateForEditableTextAndLabelControl}" />
</Style>  

The controls, defined in MainWindow.xaml file should not be setting the Template any more (Styles will do it for them). The horizontal control does not have to define anything extra (the default style will be applied to it by default), while the vertical one, should set its style to the corresponding resource:

<this:EditableTextAndLabelControl x:Name="MyControl1"
                                  HorizontalAlignment="Center"
                                  VerticalAlignment="Center"
                                  TheLabel="MyText"
                                  Margin="0,10" />
<this:EditableTextAndLabelControl x:Name="MyControl2"
                                  Grid.Row="1"
                                  HorizontalAlignment="Center"
                                  VerticalAlignment="Center"
                                  Style="{StaticResource TheVerticalStyleForEditableTextAndLabelControl}" 
                                  TheLabel="MyOtherText"
                                  Margin="0,10" />  

Further Reuse by using Orientation Dependency Property

Note, that in the example above, the horizontal and vertical representations of the control are very similar. They both consist of the same building blocks: a TextBlock, a TextBox and a Button. In general they do not have to be similar and can consists of totally different building blocks - the visual design is entirely up to the developer. Many times, however, it makes sense that the controls will be somewhat similar, and in such case, we can improve the code reuse further as we discuss in this subsection.

The code for this sample can be found under LooklessControlWithOrientationPropertySample solution. EditableTextAndLabelControl in this project is almost exactly the same as above, only it has one extra dependency property - TheOrientation:

#region TheOrientation Dependency Property
public Orientation TheOrientation
{
    get { return (Orientation)GetValue(TheOrientationProperty); }
    set { SetValue(TheOrientationProperty, value); }
}

public static readonly DependencyProperty TheOrientationProperty =
DependencyProperty.Register
(
    "TheOrientation",
    typeof(Orientation),
    typeof(EditableTextAndLabelControl),
    new PropertyMetadata(Orientation.Horizontal)
);
#endregion TheOrientation Dependency Property  

This property can be used in the Control Template to define the orientation of the control. This is exactly what we did in SampleControls.xaml file:

<ControlTemplate x:Key="TheTemplateForEditableTextAndLabelControl"
                 TargetType="this:EditableTextAndLabelControl">
    <StackPanel Orientation="{Binding Path=TheOrientation, RelativeSource={RelativeSource TemplatedParent}}"> 
        <TextBlock x:Name="TheLabelTextBlock"
                   Text="{Binding Path=TheLabel, RelativeSource={RelativeSource AncestorType=this:EditableTextAndLabelControl}}"
                   VerticalAlignment="Bottom" />

        <TextBox x:Name="TheEditableTextTextBox"
                 Grid.Column="1"
                 Width="100"
                 Text="{Binding Path=TheEditableText, RelativeSource={RelativeSource AncestorType=this:EditableTextAndLabelControl}}"
                 VerticalAlignment="Bottom"
                 Margin="10,0,10,0" />

        <Button x:Name="SaveButton"
                Content="Save"
                Width="70"
                Grid.Column="2"
                VerticalAlignment="Bottom">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click"
                                SourceObject="{Binding ElementName=SaveButton}">
                    <ei:CallMethodAction MethodName="Save"
                                         TargetObject="{Binding RelativeSource={RelativeSource AncestorType=this:EditableTextAndLabelControl}}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
    </StackPanel>
</ControlTemplate>  

At the top of the template, we bind the StackPanel's Orientation property to TheOrientation property of the control:

<StackPanel Orientation="{Binding Path=TheOrientation, RelativeSource={RelativeSource TemplatedParent}}">  

In MainWindow.xaml file (where the controls are used), we have to set the corresponding control's orientations:

<this:EditableTextAndLabelControl x:Name="MyControl1"
                                  HorizontalAlignment="Center"
                                  VerticalAlignment="Center"
                                  TheLabel="MyText"
                                  TheOrientation="Horizontal" 
                                  Margin="0,10" />
<this:EditableTextAndLabelControl x:Name="MyControl2"
                                  Grid.Row="1"
                                  TheOrientation="Vertical" 
                                  HorizontalAlignment="Center"
                                  VerticalAlignment="Center"
                                  TheLabel="MyOtherText"
                                  Margin="0,10" />  

Note that in this case, we have only one control template for both horizontal and vertical representations of the control. The extra property TheOrientation gave us another degree of freedom in control's customization!

Note, also, that we can add more dependency properties to the control in defining e.g. margins between different elements within the control, colors, etc.

Finally I'd like to discuss the binding between the Orientation property of the StackPanel and TheOrientation property of the EditableTextAndLabel control that so greatly increased the flexibility of our visual representation:

<StackPanel Orientation="{Binding Path=TheOrientation, RelativeSource={RelativeSource TemplatedParent}}">  

Since it is defined within ControlTemplate of the control, we can use TemplatedParent mode to bind it to a property on the control. Another, more generic way of performing this binding would be by using AncestorType mode:

<StackPanel Orientation="{Binding Path=TheOrientation, RelativeSource={RelativeSource AncestorType=this:EditableTextAndLabelControl}}"> 

This binding mode can be used, even when the control's component is not defined within the ControlTemplate of the control (as long as we are sure that the component is contained within the control).

A shorthand for {Binding Path=TheOrientation, RelativeSource={RelativeSource TemplatedParent}}, BTW, is {TemplateBinding TheOrientation} - which we could have used above.

A Non-Invasive Method of Adding a Property to a Control

Assume that you want to add a property (like we added TheOrientation property to EditableTextAndLabel control), without modifying the control itself. It can be useful when e.g. you do not have a code for the control and when you do not want to or cannot derive another class from it, or when you want to apply the same property to many different unrelated controls.

WPF provides an easy way of doing it by employing attached properties.

Project LooklessControlWithOrientationAttachedPropertySample contains the corresponding sample. As you can see, its code is similar to the one in the previous sample, aside from the fact that TheOrientation property is defined as an attached property in a static class AttachedProperties:

#region TheOrientation attached Property
public static Orientation GetTheOrientation(DependencyObject obj)
{
    return (Orientation)obj.GetValue(TheOrientationProperty);
}

public static void SetTheOrientation(DependencyObject obj, Orientation value)
{
    obj.SetValue(TheOrientationProperty, value);
}

public static readonly DependencyProperty TheOrientationProperty =
DependencyProperty.RegisterAttached
(
    "TheOrientation",
    typeof(Orientation),
    typeof(AttachedProperties),
    new PropertyMetadata(Orientation.Horizontal)
);
#endregion TheOrientation attached Property  

The references to the property are also updated to reflect the fact that it is now an attached property. Here is the line containing the binding to this property from SampleControls.xaml file:

  <StackPanel Orientation="{Binding Path=(this:AttachedProperties.TheOrientation), RelativeSource={RelativeSource TemplatedParent}}">

Note that within the binding path, attached properties are defined in parenthesis.

Also the lines within MainWindow.xaml file where the attached properties are set need to be modified:

<this:EditableTextAndLabelControl x:Name="MyControl1"
                                  HorizontalAlignment="Center"
                                  VerticalAlignment="Center"
                                  TheLabel="MyText"
                                  this:AttachedProperties.TheOrientation="Horizontal" 
                                  Margin="0,10" />
<this:EditableTextAndLabelControl x:Name="MyControl2"
                                  Grid.Row="1"
                                  this:AttachedProperties.TheOrientation="Vertical" 
                                  HorizontalAlignment="Center"
                                  VerticalAlignment="Center"
                                  TheLabel="MyOtherText"
                                  Margin="0,10" />

Generic Paradigms Represented by our Lookless Control Samples

Skeleton-Meat Paradigm

The lookless control provides an illustration for a WPF paradigm repeated again and again within the framework - I call it Skeleton-Meat paradigm.

Skeleton is represented by some simple functionality that nails down the public interface of a reusable component. In case of the lookless controls the Skeleton is defined by our EditableTextAndLabelControl class and consists of its properties, events and functions.

Meat part is built around the Skeleton. It is usually much more complex than the Skeleton. It reflects the Skeleton's properties and mimics Skeleton's behaviors. It can also provide its own interactions and behaviors, except that they are always private behaviors of the component and do not affect the component's interaction with other components.

In our case Meat is represented by the Style and TemplateControl objects defined in XAML.

Note that Meat part has access to Skeleton part (it needs to know the properties it should display and/or modify and the behaviors it needs to mimic). Skeleton part is usually completely unaware about the Meat - I say usually, because sometimes for more complex controls the C# Skeleton of a control might have some 'vague' knowledge about various parts within the control's Template.

Note that in MainWindow.xaml file where our lookless controls are used, we plug in the control itself, defining its properties for external interactions - the Meat part is essentially not used there.

Non-Invasiveness

The second paradigm that I'd like to discuss here is Non-Invasiveness. WPF provides a way to add a property to a control without modifying the control itself by using Attached Properties. Moreover, this property can be used both within C# and XAML code for modifying the control's look and behavior.

From my viewpoint, it is very important because it allows better separation of concerns. E.g. the control itself can contain only core properties for the main interactions with the parts of the application external to the control while some look and feel issues can be managed by attaching properties to the control.

Attached events are also part of Non-Invasiveness paradigm and will be discussed later.

Composition Hierarchy

Another paradigm that we observed is Composition Hierarchy. We can compose controls out of other controls which are in turn composed of finer controls etc. In our case, MainWindow control consists of two EditableTextAndLabelControl objects (placed within a Grid panel and with a TextBlock for displaying the messages). In their turn EditableTextAndLabelControl objects consist of a TextBlock a TextBox and a Button.

MainWindow in our case is more like a UserControl, but the same Composition Hierarchy concept can be applied to lookless controls. One can assemble a bunch of lookless controls into "super" lookless control within its template while connecting their properties that we want to manipulate at the "super" lookless control level by using bindings. In our case, we connected Text property of a TextBlock to TheLabel property and Text property of a TextBox to TheEditableText property of the lookless control.

We could also create more properties on our EditableTextAndLabelControl that would be in charge of e.g. label text color and editable text color - e.g. we could have defined LabelForeground and EditableTextForeground properties on our EditableTextAndLabelControl and connect them to the corresponding components' properties within the template using bindings.

Restyling the Lookless Controls

Note that since Meat part is not used for external interactions, it can be easily swapped without affecting the whole application. In our case, we showed how, by using two different templates, we can present our lookless control in two different ways - horizontally and vertically. One can come up with much more dramatic examples, like in e.g. Creating a look-less custom control in WPF where analog and digital clocks are created based on the same lookless control, or The power of Styles and Templates in WPF where a WPF ListBox control is re-styled into the Solar system.

Note that all of the built-in WPF controls are lookless controls and thus allow tremendous customization. Most of the 3rd party controls at least for Teleric and DevExpress frameworks are also lookless controls.

Accessing Elements Defined in XAML Control Template from the C# Code of the Control

Now, as I promised above, I'd like to show how to access parts of the template from the C# code of a lookless control. In this case, the Skeleton obtains some knowledge of the Meat so that it might impose some restrictions on how the Meat (the Template) can be constructed. This should only applied when some complex behaviors (e.g. resizing etc.) is expected from the control and is totally unnecessary for our simple EditableTextAndLabelControl but to keep things simple I'll use our control for this example.

The code for this sample is located under FindingTemplatePartInCodeSample project. Instead of using Expression Blend SDK functionality, we get a reference to the button defined by the template from the C# code and add a handler to its Click event, firing SaveEvent of the control when it happens.

The ControlTemplate defined in SampleControls.xaml file becomes only simpler. It does not need references to the Expression Blend SDK and its button does not have any triggers:

<Button x:Name="PART_SaveButton"
        Content="Save"
        Width="70"
        Grid.Column="2"
        VerticalAlignment="Bottom" />

Note that the name of the button was changed to have prefix "PART_". This is a general convention for parts of the control that C# code expects to find.

EditableTextAndLabelControl code was changed in the following way. I removed method Save() and instead added the following code:

Button _saveButton = null;
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    if (this.Template == null)
        return;

    _saveButton = this.Template.FindName("PART_SaveButton", this) as Button;

    if (_saveButton != null)
        _saveButton.Click += _saveButton_Click;
}

void _saveButton_Click(object sender, RoutedEventArgs e)
{
    if (SaveEvent != null)
        SaveEvent(TheLabel, TheEditableText);
}

OnApplyTemplate() method pulls the button control out of the template by its name "PART_SaveButton". It also adds _saveButton_Click handler to the Button's Click event.

Note that using this method breaks the principle that the Skeleton does not know anything about the Meat and so, the method should be used as little as possible.

Dependency or Attached Property Change Detection Pattern

When a dependency or attached property is defined, one can specify a callback to be fired when this property changes. This can be sometimes very useful, as it allows to fire events, or do some other processing within the Skeleton ones a property changes on the control.

E.g. let us assume that we want to remove the button from our EditableTextAndLabelControl and instead fire the SaveEvent once TheText property changes. Note that binding on the Text property of the TextBox (defined in the XAML template) has control over when to change the TheText to which it binds. By default it which change on focus lost, while if we set UpdateSourceTrigger property of the binding to PropertyChanged it will start changing on every character typed or deleted.

The code for this sample is located under DependencyPropertyChangeDetectionSample project.

The definition of TheEditibleText dependency property now includes a callback - OnEditableTextChanged. This callback calls our old Save() method which in turn fires the SaveEvent:

public static readonly DependencyProperty TheEditableTextProperty =
DependencyProperty.Register
(
    "TheEditableText",
    typeof(string),
    typeof(EditableTextAndLabelControl),
    new PropertyMetadata(null, OnTheEditableTextChanged)
);

// dependency property change callback
private static void OnTheEditableTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    ((EditableTextAndLabelControl)d).Save();
} 

The template defined in SampleControls.xaml file, now does not have the "Save" Button, and the binding of property Text of its TextBox control now has UpdateSourceTrigger set to PropertyChanged so that every key stroke will invoke SaveEvent:

<TextBox x:Name="TheEditableTextTextBox"
         Grid.Column="1"
         Width="100"
         Text="{Binding Path=TheEditableText,
                        UpdateSourceTrigger=PropertyChanged,
                        RelativeSource={RelativeSource AncestorType=this:EditableTextAndLabelControl}}" 
         VerticalAlignment="Bottom"
         Margin="10,0,10,0" />

Here is how the app will look when the user types "Hello World" in MyOtherText control:

Now, assume that we want to detect a change of a dependency (or attached) property on a control, but we do not have access to the code that defines that property and therefore cannot add a handler to it.

A standard way of dealing with this situation would be to define our own attached or dependency property, bind it to the changing property and detect the change of our property (whose code we have access to). Indeed, this is what we've done to the Text property of the TextBox control. In the example above, we are detecting when it changes by binding it to TheEditableText property of our control and defining a callback on TheEditableText property.

Behavior Pattern

This is a great pattern in order to improve the separation of concerns. To the best of my knowledge, it was first introduced by Expression Blend SDK. 

It is actually very easy to create behavior functionality without using Expression Blend SDK and here I show how. In fact, as will be shown in the subsequent article(s), the behavior pattern is not unique to WPF and can be used for purely non-visual functionality - a slight modification to the IBehavior Interface presented in this article can make it completly WPF agnostic. 

Behavior is a C# object which by being attached to another object, e.g. a WPF control, changes its behavior by adding behavior specific handlers to the object's events. Behavior knows the WPF object it modifies while the WPF object does not have any knowledge of the behavior(s) attached to it. Behavior is thus part of the Meat, but since it is built in C# it has more power to transform the object than Meat contained in XAML.

The best way to attach a behavior to a WPF object is non-invasively - via an attached property. Usually behaviors are defined as XAML resources and attached to WPF objects in XAML.

To see a behavior sample, open BehaviorSample project. IBehavior is an interface that all behaviors should implement:

public interface IBehavior
{
    void Attach(FrameworkElement el);

    void Detach(FrameworkElement el);
}  

Static class AttachedProperties contains TheBehavior attached property that allows to attach a behavior to any FrameworkElement. The property change callback method OnBehaviorChanged calls OnDetach method for the old behavior and OnAttach method for the new:

private static void OnTheBehaviorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    FrameworkElement el = (FrameworkElement)d;

    IBehavior oldBehavior = (IBehavior) e.OldValue;

    if (oldBehavior != null)
        oldBehavior.Detach(el);

    IBehavior newBehavior = (IBehavior) e.NewValue;

    if (newBehavior != null)
        newBehavior.Attach(el);
}  

ChangeOpacityOnMouseOverBehavior defines a behavior that changes opacity of an element to 0.5 when the mouse moved on top of the element and then changes it back to 1 when the mouse moves out:

public class ChangeOpacityOnMouseOverBehavior : IBehavior
{
    public void Attach(FrameworkElement el)
    {
        el.MouseEnter += el_MouseEnter;
        el.MouseLeave += el_MouseLeave;
    }

    void el_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
    {
        FrameworkElement el = (FrameworkElement)sender;

        el.Opacity = 1;
    }

    void el_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
    {
        FrameworkElement el = (FrameworkElement)sender;

        el.Opacity = 0.5;
    }

    public void Detach(FrameworkElement el)
    {
        el.MouseLeave -= el_MouseLeave;
        el.MouseEnter -= el_MouseEnter;
    }
}  

Finally inside MainWindow.xaml file we define a Grid panel in the center of the window with green Background and attach the behavior to it:

<Window.Resources>
    <this:ChangeOpacityOnMouseOverBehavior x:Key="TheChangeColorBehavior" />
</Window.Resources>
<Grid>
    <Grid HorizontalAlignment="Center"
          VerticalAlignment="Center"
          Height="30"
          Width="100"
          Background="Green"
          this:AttachedProperties.TheBehavior="{StaticResource TheChangeColorBehavior}" /> 
</Grid>  

As a result, a green rectangle is displayed in the middle of the window, whose opacity reduces when mouse is placed over it:

Note that the above pattern will only allow to have no more than one behavior attached to one object. This is not always convenient. In order to attach multiple behaviors to the same object we can define an attached property TheBehaviors instead of TheBehavior. This property can be of type IEnumerable<IBehavior>.

Example of attaching multiple behaviors to the same object is located under AttachingMultipleBehiorsToObjectSample project. Instead of TheBehavior attached property, here we have TheBehaviors property of the type IEnumerable<IBehavior>. On top of ChangeOpacityOnMouseOverBehavior, we also define ChangeWidthBehavior which expands the visual element when the mouse is over it and reverts it to the original size when the mouse is moved away. In MainWindow.xaml file we create an array two such behaviors and attach them to the Grid panel by using TheBehaviors attached property:

<Window.Resources>
    <x:Array Type="this:IBehavior" x:Key="TheBehaviors">
        <this:ChangeOpacityOnMouseOverBehavior/>
        <this:ChangeWidthBehavior />
    </x:Array>
</Window.Resources>
<Grid>
    <Grid HorizontalAlignment="Center"
          VerticalAlignment="Center"
          Height="30"
          Width="100"
          Background="Green"
          this:AttachedProperties.TheBehaviors="{StaticResource TheBehaviors}" />
</Grid>

When running the sample, you can see that not only the opacity of the green rectangle in the center of the windows changes, but also the rectangle expands horizontally when the mouse is over it.

Template Parts and Loose Coupling

Perhaps the most important advantage related to ControlTemplates is the fact that they allow to achieve a great degree of independence between different parts of a control (Loose Coupling).

We shall start with a simple fact that any Control object is essentially replaced in the visual tree by its template. The control object will still exist in the visual tree, of course, but only as a container. To illustrate this, please open ControlTemplateReplacementSample project.

All of the project's significant code is located in MainWindow.xaml file. We define a very simple template within Window.Resources section:

<ControlTemplate x:Key="MyControlTemplate">
    <Grid Background="Yellow">
        <TextBlock Text="Hello World" Margin="30"/>
    </Grid>
</ControlTemplate>  

and then assign this template to a control:

<Control x:Name="MyControl"
         Template="{StaticResource MyControlTemplate}" 
         HorizontalAlignment="Center"
         VerticalAlignment="Center"/>  

as a result, when we open the window, we see text "Hello World" within a yellow rectangle, just as if we placed the code of the template within the main window instead of the Control "MyControl":

The main example of Template Parts Loose Coupling pattern is located under MultipleTemplatePartsSample project. It defines one lookless control MyTestControlWithHeader. This control has 3 dependency properties - HeaderTemplate, MainTemplate and EventLog. Two first properties are of ControlTemplate type; they correspond to the two templates - one for the control's header and another for the "main" part of the control. The third property is just a string representing a "log" of some visual events happening on the control.

The lookless control also defines a method LogClickEvent(). At each invocation it appends the following line to the EventLog string: "Click Received":

public class MyTestControlWithHeader : Control
{
    #region HeaderTemplate Dependency Property
    public ControlTemplate HeaderTemplate
    {
        get { return (ControlTemplate)GetValue(HeaderTemplateProperty); }
        set { SetValue(HeaderTemplateProperty, value); }
    }

    public static readonly DependencyProperty HeaderTemplateProperty =
    DependencyProperty.Register
    (
        "HeaderTemplate",
        typeof(ControlTemplate),
        typeof(MyTestControlWithHeader),
        new PropertyMetadata(null)
    );
    #endregion HeaderTemplate Dependency Property

    #region MainTemplate Dependency Property
    public ControlTemplate MainTemplate
    {
        get { return (ControlTemplate)GetValue(MainTemplateProperty); }
        set { SetValue(MainTemplateProperty, value); }
    }

    public static readonly DependencyProperty MainTemplateProperty =
    DependencyProperty.Register
    (
        "MainTemplate",
        typeof(ControlTemplate),
        typeof(MyTestControlWithHeader),
        new PropertyMetadata(null)
    );
    #endregion MainTemplate Dependency Property

    #region EventLog Dependency Property
    public string EventLog
    {
        get { return (string)GetValue(EventLogProperty); }
        set { SetValue(EventLogProperty, value); }
    }

    public static readonly DependencyProperty EventLogProperty =
    DependencyProperty.Register
    (
        "EventLog",
        typeof(string),
        typeof(MyTestControlWithHeader),
        new PropertyMetadata(null)
    );
    #endregion EventLog Dependency Property

    public void LogClickEvent()
    {
        EventLog += "\nClick Recieved";
    }
}  

The control's style and template are defined within MainWindow.xaml file:

<Style TargetType="this:MyTestControlWithHeader">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="this:MyTestControlWithHeader">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>
                    <Control x:Name="HeaderControl"
                             Template="{Binding Path=HeaderTemplate, RelativeSource={RelativeSource TemplatedParent}}" />

                    <Control x:Name="MainControl"
                             Grid.Row="1"
                             Template="{Binding Path=MainTemplate, RelativeSource={RelativeSource TemplatedParent}}" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

As you can see, the control's template consists of a Grid panel with two rows - both rows contain Control objects. The upper row contains Control named "HeaderControl" - whose Template property is bound to HeaderTemplate property of the MyTestControlWithHeader object. The lower row contains Control named "MainControl" whose Template property is bound to MainTemplate property of the MyTestControlWithHeader object.

The important thing is that we can plug any two templates as HeaderTemplate and MainTemplate properties and make them work together via MyTestControlWithHeader functionality without being actually aware of one another.

This is exactly what we do - we define two templates: MyHeaderTemplate1 and MyMainTemplate1 (their names were chosen to underscore that they can be easier replaced) and plug them into the main control:

<this:MyTestControlWithHeader HeaderTemplate="{StaticResource MyHeaderTemplate1}"
                              MainTemplate="{StaticResource MyMainTemplate1}"/>

Here is code for "MyHeaderTemplate1" template:

<ControlTemplate x:Key="MyHeaderTemplate1">
    <Grid Height="50" 
          Background="Yellow">
        <StackPanel Orientation="Horizontal">
            <Button Content="Button to Click"
                    Height="25" 
                    Width="120"
                    Margin="5,0">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <ei:CallMethodAction TargetObject="{Binding RelativeSource={RelativeSource AncestorType=this:MyTestControlWithHeader}}"
                                             MethodName="LogClickEvent" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
        </StackPanel>
    </Grid>
</ControlTemplate>  

It contains a button whose click results in calling LogClickEvent method on the containing MytestControlWithHeader object.

Here is the "MyMainTemplate1" template:

<ControlTemplate x:Key="MyMainTemplate1">
    <Grid Background="Green">
        <TextBlock x:Name="LogText"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   Foreground="Black"
                   Text="{Binding Path=EventLog, RelativeSource={RelativeSource AncestorType=this:MyTestControlWithHeader}}" />
    </Grid>
</ControlTemplate>  

It defines a text block whose Text property is bound to EventLog property of the containing MyTestControlWithHeader object.

Now, if you run the application - clicking on the button in the header will result to adding a text line "Click Received" to the main part of the control:

As you see, the two template parts have no knowledge whatsoever of one another. They have some knowledge of the containing lookless control, and communicate via its methods and properties. Clicking on the header Button invokes LogClickEvent() method of the lookless control (via MS Expression Blend SDK plumbing). This method adds a text line to the EventLog property of the same object, which in turn has Text property of the TextBlock within the MainTemplate bound to it.

Conclusions

In this article we discussed practical patterns for code reuse based on WPF Controls and ControlTemplate functionality. In the subsequent article(s) we plan to discuss View-View Model based patterns which have even higher potential for code reuse and separation of concerns.

Acknowledgement

A hat tip to my colleague Chad Wright for pointing some errors in this article.

License

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

Share

About the Author

Nick Polyak
Architect AWebPros
United States United States
I have 15 years of experience developing enterprise software, starting from C++ and Java on UNIX and moving towards C# on Windows platforms.
I am fascinated by the new .NET technologies especially WPF, Silverlight and LINQ.
Recently I decided to make a move and start my own contracting consulting and mentoring company AWebPros.
I can be contacted via my web site awebpros.com or through my blog at nickssoftwareblog.com

Comments and Discussions

 
GeneralMy vote of 4 PinmemberMahBulgaria18-Aug-14 4:09 
GeneralRe: My vote of 4 PinprofessionalNick Polyak18-Aug-14 4:54 
GeneralMy vote of 5 PinprofessionalMihai MOGA20-Jul-14 5:46 
GeneralRe: My vote of 5 PinprofessionalNick Polyak20-Jul-14 16:11 
QuestionMissing things? Pinprotectorthatraja5-Jul-14 3:00 
AnswerRe: Missing things? PinprofessionalNick Polyak5-Jul-14 16:56 
GeneralBehaviors Pinmemberazweepay30-Jun-14 21:28 
GeneralRe: Behaviors [modified] PinmemberNick Polyak1-Jul-14 20:05 
GeneralRe: Behaviors Pinmemberazweepay1-Jul-14 20:20 
GeneralMy vote of 5 PinmemberCarsten V2.029-Jun-14 5:17 
GeneralRe: My vote of 5 PinmemberNick Polyak29-Jun-14 17:49 
QuestionVery good and informative article PinprofessionalVolynsky Alex25-Jun-14 9:27 
AnswerRe: Very good and informative article PinmemberNick Polyak28-Jun-14 17:24 
GeneralRe: Very good and informative article PinprofessionalVolynsky Alex28-Jun-14 21:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.1411019.1 | Last Updated 6 Jul 2014
Article Copyright 2014 by Nick Polyak
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid