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.
<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>
<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
Post a Comment