Introduction
In this article, I'll show you how to create a very simple control to represent a small cross button - the sort that you see in the tabbed windows or certain controls.
Background
I found that when creating a custom tab control that allowed the user to close the tabs with a small button, a control like this would be useful.
However, in later projects, this small control has been surprisingly versatile.
Creating a Custom Control
Let's get started. Create a new Visual Studio Solution and add a WPF Custom Control Library to it. The custom control code is very simple:
public class CrossButton : Button
{
static CrossButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CrossButton),
new FrameworkPropertyMetadata(typeof(CrossButton)));
}
}
Now we are deriving the control from Button
; the only thing this code really does is makes sure that the style we use for the
CrossButton
is the correct style that we will add in our Generic.xaml dictionary.
We actually have no custom code in the control - we could define the whole thing as a style that just re-templates the button. However, in my local version, I have
added a few more features - if you have a class like this, you can extend yours too.
The XAML
The XAML in the Generic.xaml file is basic as well:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CrossButton">
<Style TargetType="{x:Type local:CrossButton}">
Generic.xaml is a resource dictionary, we need a reference to the namespace we have defined in the control in. The only thing we have in the resource dictionary
is the style for the cross button.
Note: If you want to implement this control simply as a style, just set the target type to 'Button
' and
give the style a key - then apply the style to any button.
Moving on, we'll need a few resources for the button.
<!---->
<Style.Resources>
<SolidColorBrush x:Key="NormalBackgroundBrush" Color="#00000000" />
<SolidColorBrush x:Key="NormalBorderBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="NormalForegroundBrush" Color="#FF8f949b" />
<SolidColorBrush x:Key="HoverBackgroundBrush" Color="#FFc13535" />
<SolidColorBrush x:Key="HoverForegroundBrush" Color="#FFf9ebeb" />
<SolidColorBrush x:Key="PressedBackgroundBrush" Color="#FF431e20" />
<SolidColorBrush x:Key="PressedBorderBrush" Color="#FF110033" />
<SolidColorBrush x:Key="PressedForegroundBrush" Color="#FFf9ebeb" />
</Style.Resources>
These are the brushes we'll use to paint the button in its various states.
<!---->
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Focusable" Value="False" />
This is such a small button that it'll help to have the cursor as a hand so that it is obvious it is clickable. Also, by setting the
Focusable
style to False
, we stop the user from tabbing to the control which in most circumstances would be a bit odd.
Here's where it gets more interesting:
<!---->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
This is where we are going to change the control's template. The control template is what is used to draw the control - by changing the template, we can completely
change how we draw the control.
<Grid Background="Transparent">
-->
<Ellipse x:Name="backgroundEllipse" />
-->
<Path x:Name="ButtonPath"
Margin="3"
Stroke="{StaticResource NormalForegroundBrush}"
StrokeThickness="1.5"
StrokeStartLineCap="Square"
StrokeEndLineCap="Square"
Stretch="Uniform"
VerticalAlignment="Center"
HorizontalAlignment="Center">
The cross button is a simple looking button - a small cross with a red circle behind it when the mouse is over it. We put the ellipse and path (which will draw the cross)
in a grid, drawing one on top of the other.
We have the path, but we'll need to define the geometry for it:
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="0,0">
<LineSegment Point="25,25"/>
</PathFigure>
<PathFigure StartPoint="0,25">
<LineSegment Point="25,0"/>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
The geometry is very simple - two line segments! We can close the path and the grid and now move onto the more interactive features. We'll use triggers to change colours
as the mouse moves over the control.
<!---->
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="backgroundEllipse" Property="Fill"
Value="{StaticResource HoverBackgroundBrush}" />
<Setter TargetName="ButtonPath" Property="Stroke"
Value="{StaticResource HoverForegroundBrush}"/>
</Trigger>
The first trigger is fired when the mouse moves over - simply changing the colour of the background ellipse and the button path.
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
If the control isn't enabled, we'll just hide it - it's a very discrete little button.
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="backgroundEllipse" Property="Fill"
Value="{StaticResource PressedBackgroundBrush}" />
<Setter TargetName="backgroundEllipse" Property="Stroke"
Value="{StaticResource PressedBorderBrush}" />
<Setter TargetName="ButtonPath" Property="Stroke"
Value="{StaticResource PressedForegroundBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
We use the final trigger to colour the control black and white when it is pressed. After this, we close all of the tags and we're done with the dictionary.
Example
The example application shows how to use the control in a data template to remove items from a list:
This is just one use for the control - below is another (not in the example):
If anyone would find the above control useful, let me know and I'll write an article about it.