Introduction
This tip is about how to create WPF ImageButton
simply and effectively. I used a WPF Custom Control for this tip.
To create this, we need to add an Image Control to the button content. We also want to be able to easily change that image and size from Code or XAML.
First Step: Create WPF Custom Control
For those unfamiliar with custom controls I must say, it's like making a new control, but we need to use a control as a Base Control.
This way, we keep all Base Control features and then add new options to it or even change some features to make more comfortable controls for our usage. Create a new WPF Custom control library. Name it ExtendedButton
(as an example). Your project contains the following parts:
CustomControl
class: Contains code and inheritance from Control
- Generic.xaml: Contain
CustomControl
Template. It is in Themes SubFolder
Second Step: CustomControl Class Code
After creating your project, you have the CustomControl1.cs class. Rename the class to ImageButton
. Then rename your constructor and other CustomControl1
, like the below code.
Now we need to change base class from Control
to Button
. This inheritance defines our Base Control. I choose Button
as the Base Control because it has all the features we need and also requires the minimum amount of work to became ImageButton
. Although you can use any object as Base control which was inherited from Control Class
.
public class ImageButton : Button
{
static ImageButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton),
new FrameworkPropertyMetadata(typeof(ImageButton)));
}
}
For setting the image we need a property, which I named Image
, but in the WPF Custom Control, we used Dependency Property (for more information, look at wpftutorial.net).
Also, we need two properties to set Image Size
, for our new Control
(ImageWidth
and ImageHeight
).
public ImageSource Image
{
get { return (ImageSource)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image",
typeof(ImageSource), typeof(ImageButton), new PropertyMetadata(null));
public double ImageHeight
{
get { return (double)GetValue(ImageHeightProperty); }
set { SetValue(ImageHeightProperty, value); }
}
public static readonly DependencyProperty ImageHeightProperty =
DependencyProperty.Register("ImageHeight",
typeof(double), typeof(ImageButton), new PropertyMetadata(Double.NaN));
public double ImageWidth
{
get { return (double)GetValue(ImageWidthProperty); }
set { SetValue(ImageWidthProperty, value); }
}
public static readonly DependencyProperty ImageWidthProperty =
DependencyProperty.Register("ImageWidth",
typeof(double), typeof(ImageButton), new PropertyMetadata(Double.NaN));
Important
-
Pay attention to the parameter which we use in the
PropertyMetadata
constructor.
This parameter uses Default Value for property. So for some types we need set the correct value, like Image Property.
-
Dependency Property must register for custom Control.
This done by the Register method and attaches to the Custom Control by the third parameter (in this overload) with type of
CustomControl
.
OK. We did everything we want to do with this class.
Third Step: Image Space Issue
Before we go for our Control Appearance, we need to do something about that image issue. Our control has an image and text so then if
the control doesn't have any image, then we have an image control without any image (Blank Space). To prevent this issue, I created a simple convertor to check image value. If the image value is null
, the image box will go invisible.
WPF has provided a feature for this solution and calls it Convertor
. (For more information, look at wpftutorial.net). It's simple, just add a class to your project (name it VisibilityConvertor
) then inherit your class from IValueConverter
and, finally, implement it.
Now add a simple code for value checking to get this code:
public class VisibilityConvertor : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return Visibility.Collapsed;
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
Fourth Step: Generic Class
Now we must define our new control appearance. To do this, we must go to Generic.xml in the theme directory. After creating a project, our Generic.xml will be like the code below:
<resourcedictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ExtendedButton">
<local:visibilityconvertor x:key="VisibilityConvertor" />
<style targettype="{x:Type local:ImageButton}">
<Setter Property="Template" >
<Setter.Value >
<ControlTemplate TargetType="{x:Type local:ImageButton}" >
<Button >
<StackPanel Orientation="Horizontal" >
<Image Margin="2 0" Source="{TemplateBinding Image}"
Width="{TemplateBinding ImageWidth}"
Height="{TemplateBinding ImageHeight}"
Visibility="{TemplateBinding Image,
Converter={StaticResource VisibilityConvertor}}"
VerticalAlignment="Center"/ >
<TextBlock Text="{TemplateBinding Content}" VerticalAlignment="Center"/ >
</StackPanel >
</Button >
</ControlTemplate >
</Setter.Value >
</Setter >
</style>
</resourcedictionary>
Well done. Our custom control is ready and you can use it.
Points of Interest
As you see in custom control template, I use a button with a stack panel.
But if you want to use the default button template, you can use the template I put in OtherTemplate file in sample.
I produced that template with Microsoft Blend from a button and changed the content to stack panel.
The difference between these templates is, you can access all the button appearances in the second template (like background and foreground).
So if you want to change it, you must only copy all styles and lines in OtherTemplate
and replace the current template in Generic.Xaml.