Skip to main content

...

....

C# WPF Tutorial - Custom Control Templates [Beginner]


Almost every control in WPF uses a control template to define how it displays itself and its contents. In fact, you can override the control template on every built-in control to make it appear and act exactly like you want. Rather than overriding an existing template, however, this tutorial is going to demonstrate how to build a custom content control and use a control template to define how it looks.

MSDN has tons ofexamples that override the control template for WPF controls, however they don't have any great examples on building your own. What I've needed on several occasions is a custom control that can be populated using XAML and contain any number of custom elements. That's what we're going to build today. Below are a couple of examples of what this this tutorial will cover.

Thought Bubble Example
1
 
<Window x:Class="CustomContentControl.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="clr-namespace:CustomContentControl"
    Title="Window1" Height="300" Width="300" Background="LightBlue">
  <Grid>
    <my:ThoughtBubble Width="200" Height="200" BubbleBackground="White">
      <StackPanel Orientation="Vertical" 
                  HorizontalAlignment="Center" 
                  VerticalAlignment="Center">
        <TextBlock Text="Here's a button!" Margin="0,0,0,5" />
        <Button Content="My Button"/>
      </StackPanel>
    </my:ThoughtBubble>
  </Grid>
</Window>
 
Thought Bubble Example
2
 
<Window x:Class="CustomContentControl.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="clr-namespace:CustomContentControl"
    Title="Window1" Height="400" Width="300" Background="LightBlue">
  <Grid>
    <my:ThoughtBubble Width="200" Height="300" BubbleBackground="LightGray">
      <Image Source="picard.jpg" />
    </my:ThoughtBubble>
  </Grid>
</Window>
 
The first thing we're going to need to do is build ourselves the thought bubble control. Unfortunately, WPF makes this a little more difficult than I'd like. We can't make a UserControl and use its XAML to define the look. If we did and we attempted to populate our bubble with named elements, we'd get a compile error that looks something like this:
Cannot set Name attribute value 'name' on element 'element'. 'element' is under the scope of element 'control', which already had a name registered when it was defined in another scope.

What we have to do instead is create a UserControl subclass without an attached XAML file and define the look in a style. We'll start with the class.
using System;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;

namespace CustomContentControl
{
  public class ThoughtBubble : UserControl
  {
    /// <summary>
    /// Gets or sets the background color of the thought bubble.
    /// </summary>
    public SolidColorBrush BubbleBackground
    {
      get { return (SolidColorBrush)GetValue(BubbleBackgroundProperty); }
      set { SetValue(BubbleBackgroundProperty, value); }
    }

    public static readonly DependencyProperty BubbleBackgroundProperty =
        DependencyProperty.Register("BubbleBackground", 
        typeof(SolidColorBrush), 
        typeof(ThoughtBubble), 
        new UIPropertyMetadata(Brushes.White));
  }
}
 
As you can see, the amount of C# code required to do this is very small. I only have one dependency property that lets users change the background color of the thought bubble. If you're not familiar with dependency properties, I'd recommend reading our introductorytutorial. Your object will have to extend some sort of display control - I typically just extend UserControl. That's it for code. The rest is entirely done in XAML.

Chungking up the XAML will probably make it difficult to read, so I'm going to post the entire thing and explain it afterwards.
<Application x:Class="CustomContentControl.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="clr-namespace:CustomContentControl"
    StartupUri="Window1.xaml">
  <Application.Resources>
    <Style TargetType="{x:Type my:ThoughtBubble}">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type my:ThoughtBubble}">
            <Grid>
              <Border CornerRadius="24"
                      Background="{TemplateBinding BubbleBackground}"
                      BorderBrush="Black" 
                      BorderThickness="2"
                      Margin="0,0,0,30" 
                      Padding="24">
                  <ContentPresenter />
              </Border>
              <Grid VerticalAlignment="Bottom" 
                    HorizontalAlignment="Right" 
                    Margin="0,0,30,0" >
                <Polygon Points="10,0 40,0 0,30" 
                         Fill="{TemplateBinding BubbleBackground}" 
                         VerticalAlignment="Bottom" 
                         HorizontalAlignment="Right" />
                <Line X1="10" Y1="0" X2="0" Y2="30" 
                      Stroke="Black" StrokeThickness="2" />
                <Line X1="10" Y1="0" X2="40" Y2="0" 
                      Stroke="{TemplateBinding BubbleBackground}" 
                      StrokeThickness="3" />
                <Line X1="40" Y1="0" X2="0" Y2="30" 
                      Stroke="Black" StrokeThickness="2" />
              </Grid>
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </Application.Resources>
</Application>
 
I'm using WPF's powerful styling system to do most of the work for me. This ControlTemplate is set using a style defined in App.xaml. App.xaml is automatically created whenever you create a new WPF project in Visual Studio. By giving my style a TargetType and not a key, this style will be applied to every instance of ThoughtBubble added to my app. This isn't the most elegant way to create the shape of a thought bubble, but it gets the job done. And besides, creating the shape isn't the important part of this tutorial.

When creating our ControlTemplate, we need to specify the type of object that this template is intended for. This is required if the definition uses a content presenter, which ours does, and I'll explain what it is a little later. The type is simply the same type as the style
{x:Type my:ThoughtBubble}


When we create the Border, you might notice the TemplateBinding markup extension. This lets us bind to properties on the object that this template is being applied to. This is how our BubbleBackground property is used.

The last oddity in this style is the ContentPresenter. This object is pretty simple - it displays the contents of a ContentControl. Since UserControl extends ContentControl, and our object extends UserControl, we get to use this object. This is what the user populates when they add elements inside our object.

And there you have it. We've successfully used a ControlTemplate to define a custom control that can be populated with any arbitrary WPF controls. Control templates are a very powerful feature of WPF and definitely something worth investigating when creating custom controls for your own project. 

Source Files:

Comments

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >();

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV