I'm creating a new button control, and I wanted to create a new property under the brushes section where I can set the hovercolor. Anyone know how you would do this?
The simplest way to add a property that will appear in the "Brush" section of the Properties panel is to - define a Brush property in control's class code:
public partial class MyFancyControl : UserControl
{
// ...
public Brush FancyBrush
{
get;
set;
}
// ...
}
The property will show in "Brush" section with no further action (at least it does in my VS2013, see below).
While such a property will work just fine in most cases, the proper way to do it is to define it as a DependencyProperty:
public partial class MyFancyControl : UserControl
{
// ...
public Brush FancyBrush
{
get
{
return (Brush)GetValue(FancyBrushProperty);
}
set
{
SetValue(FancyBrushProperty, value);
}
}
public static readonly DependencyProperty FancyBrushProperty =
DependencyProperty.Register("FancyBrush", typeof(Brush), typeof(IntUpDown), new PropertyMetadata(default(Brush)));
// ...
}
Using DependencyProperty will enable binding and other "advanced" stuff.
Tip: Use VS Intellisense helpers to avoid need to type all the surrounding code - type "propdp" and press Tab twice.
To make sure the property will show up in the correct section of the Properties panel, add the Category attribute:
[System.ComponentModel.Category("Brush")]
public Brush FancyBrush
{...
Again, this seems to work automatically for Brush type so it may not be necessary.
You can also add a Description attribute that will show in the tooltip in the Properties panel:
[System.ComponentModel.Description("Gets or sets a brush that defines fancy look of the control.")]
You can just create the brush and add it as a resource something like:
<SolidColorBrush x:Key="MouseOverColor" Color="#FFFFFFF"/>
Then in the template triggers in your button template:
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ButtonBorder" Property="Background" Value="StaticResource MouseOverColor}"/>
</Trigger>
Here is a simple button from in a resource dictionary file that uses defined brushes:
<SolidColorBrush x:Key="SelectionHighlightBrush" Color="#282828"/>
<SolidColorBrush x:Key="SelectionHighlightTextBrush" Color="White"/>
<SolidColorBrush x:Key="ForegroundBrush" Color="#282828"/>
<SolidColorBrush x:Key="ControlBackgroundBrush" Color="White"/>
<SolidColorBrush x:Key="ControlBorderBrush" Color="#C0C0C0" />
<Style TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}"/>
<Setter Property="Background" Value="{StaticResource ControlBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource ControlBorderBrush}"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Name="temp" TargetType="ToggleButton">
<Border x:Name="bd" CornerRadius="3"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter x:Name="contentPresenter" Margin="{TemplateBinding Padding}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="Background" Value="{StaticResource SelectionHighlightBrush}"/>
<Setter Property="TextElement.Foreground" Value="{StaticResource SelectionHighlightTextBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Related
I have created a custom control (inherits Control) that exposes a single enumerated DependencyProperty. The default control template renders differently based on the selected value for the property using Triggers to turn elements on/off. The control works great when placed directly into a UserControl for viewing in the UI. However, the point of the control is to exist as part of a large composite control so it is also used in the ControlTemplate of another custom control. When I do so, changes to the dependency property are not recognized by the control. I verified this by adding a PropertyChangedCallback to the dependency property and setting a break point which is never hit.
For example, when I use "CustomControl" in a template like this:
<ControlTemplate>
<my:CustomControl EnumProperty="EnumValue" />
</ControlTemplate>
The EnumProperty (which is a DependencyProperty) is not changed to "EnumValue" and it remains the default value. And, as I said, a breakpoint in the PropertyChangedCallback for the DP is never called.
What am I missing?
UPDATE
Here is a cleansed version of my control:
public class CustomControl : Control
{
static CustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
}
public StandardIcon()
: base()
{
BorderType = BorderType.None;
}
public static readonly DependencyProperty BorderTypeProperty = DependencyProperty.Register("BorderType", typeof(BorderType), typeof(CustomControl), new PropertyMetadata(BorderType.None));
public BorderType BorderType
{
get { return (BorderType)GetValue(BorderTypeProperty); }
set { SetValue(BorderTypeProperty, value); }
}
}
<Style TargetType="{x:Type local:CustomControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl}">
<Border x:Name="Rectangle"
BorderBrush="{TemplateBinding Foreground}"
BorderThickness="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ContentPresenter ContentSource="Content" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="BorderType" Value="Rectangle">
<Setter Property="BorderThickness" TargetName="Rectangle" Value="2" />
</Trigger>
<Trigger Property="BorderType" Value="RoundedRectangle">
<Setter Property="BorderThickness" TargetName="Rectangle" Value="2" />
<Setter Property="CornerRadius" TargetName="Rectangle" Value="5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And this is how it is being used within another control (notice that it is in a DataTemplate and not a ControlTemplate as I originally indicated).
<Style TargetType="{x:Type local:OtherControl}">
<Setter Property="FontFamily" Value="{x:Static theme:StandardFonts.FontFamily}" />
<Setter Property="FontSize" Value="{x:Static theme:StandardFonts.FontSizeXS}" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:CustomControl BorderType="{Binding TemplatedParent.BorderType, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{Binding TemplatedParent.Foreground, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And then it is used like this:
<controls:OtherControl Foreground="Red" BorderType="Rectangle" />
The Foreground property IS changing as expected. When I change the Foreground of the OtherControl, the Foreground of the CustomControl is changed. But the BorderType property is not being respected - it always renders with the default BorderType.None value.
The parent of your ControlTemplate needs to have something for your CustomControl to bind to. Then, you bind the CustomControl in your template to the parent.
In the following example, I'm using a Border to template a Button, which binds its BorderBrush to the Button's Background:
<ControlTemplate TargetType="{x:Type Button}">
<Border BorderBrush="{TemplateBinding Background}" />
</ControlTemplate>
Replace Button with your "large composite control" and Border with my:CustomControl and you should be set...
I need to change the background of the selected item in a list view, programmatically.
The style currently looks like this (as default)
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="images/selection-large.png"/>
</Setter.Value>
</Setter>
</Trigger>
I need to be able to change that background image of the selected item programmatically.
I have attempted to do this myself however i am not really sure how to go about it.
I did notice that the listview has a styles property but other than that i am completely stumped.
I am using VB.net
Edit
Would something like this work? and if so, what am i doing wrong? My listview is completely invisible with this code.
Dim trigger As New Trigger()
trigger.Property = ListViewItem.IsSelectedProperty
trigger.Value = True
trigger.Setters.Add(New Setter(ListViewItem.BackgroundProperty, Brushes.Pink))
mylistview.Style.Triggers.Add(trigger)
mylistview.ItemContainerStyle = Style
Bind your IsSelected property to some bool value in the ViewModel and toggle that.
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListView.ItemContainerStyle>
Item ViewModel:
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
NotifyPropertyChange("IsSelected");
}
}
In WPF you don't manipulate UI elements in procedural code, instead you use DataBinding and manipulate your models / ViewModels' properties
As an aside, I recommend against using PNG or other bitmap based stuff for regular UI elements in WPF.
Instead of that you should create your UI using WPF elements which are vector-based and thus resolution independent.
You can do this within the XAML, Try this:
<Window.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="images/selection-large.png"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
I was able to finally work it out..
All i had to do was make another style based on the first one and give them both different x:keys.. Then i was able to change styles like this.
mylistview.ItemContainerStyle = DirectCast(FindResource("XKeyName"), Style)
i have button with this style:
<Style TargetType="Button" x:Key="btnStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Image x:Name="img" Source="{TemplateBinding Tag}" Margin="{TemplateBinding Margin}"
Stretch="None"></Image>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="img" Property="Source" Value="{DynamicResource imgClose_P}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="img" Property="Source" Value="{DynamicResource imgClose_H}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As you can see i'm binding the ImageSource to the Tag property of the button.
And in the Tag property i'm binding it to a ResourceDictionary that store this bitmap:
<BitmapImage x:Key="imgClose_N" UriSource="..\AppImages\mainWindow\TopBanner\CloseButton_sN.png" />
this gives me the ability to use this "Imagebutton" all over the application with different background images and one template.
the problem is how to keep this generic approach with triggers?
i would like the IsMouseOver trigger to change the background image but to bind it to some property of the control and not to write it hard coded in the control template.
how can this be done ?
As you already suggest by calling it an "Imagebutton", you may derive from Button and define some image properties to bind to, e.g. BackgroundImage, MouseOverImage, etc.
An alternative to deriving from Button would be to use attached properties to set the images and bind to those in your style, but these attached properties would also have to be defined somewhere, which doesn't make it simpler.
Here's an example for the first solution:
public class ImageButton : Button
{
public static readonly DependencyProperty NormalBackgroundImageProperty = DependencyProperty.Register(
"NormalBackgroundImage", typeof(ImageSource), typeof(ImageButton));
public static readonly DependencyProperty MouseOverBackgroundImageProperty = DependencyProperty.Register(
"MouseOverBackgroundImage", typeof(ImageSource), typeof(ImageButton));
public static readonly DependencyProperty PressedBackgroundImageProperty = DependencyProperty.Register(
"PressedBackgroundImage", typeof(ImageSource), typeof(ImageButton));
public ImageSource NormalBackgroundImage
{
get { return (ImageSource)GetValue(NormalBackgroundImageProperty); }
set { SetValue(NormalBackgroundImageProperty, value); }
}
public ImageSource MouseOverBackgroundImage
{
get { return (ImageSource)GetValue(MouseOverBackgroundImageProperty); }
set { SetValue(MouseOverBackgroundImageProperty, value); }
}
public ImageSource PressedBackgroundImage
{
get { return (ImageSource)GetValue(PressedBackgroundImageProperty); }
set { SetValue(PressedBackgroundImageProperty, value); }
}
}
and an appropriate style below. Note that this style also has a ContentPresenter for the button's content, and that it uses regular bindings with RelativeSource={RelativeSource TemplatedParent} instead of TemplateBindings. These are evaluated at runtime.
<Style TargetType="local:ImageButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ImageButton">
<Grid Margin="{TemplateBinding Margin}">
<Image x:Name="img" Source="{Binding NormalBackgroundImage, RelativeSource={RelativeSource TemplatedParent}}" Stretch="None"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="img" Property="Source" Value="{Binding MouseOverBackgroundImage, RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="img" Property="Source" Value="{Binding PressedBackgroundImage, RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You would have to define or replace the XAML namespace local with a mapping to the namespace/assembly that contains the class ImageButton.
The button could then be use like this:
<local:ImageButton
Margin="10"
NormalBackgroundImage="C:\Users\Public\Pictures\Sample Pictures\Desert.jpg"
PressedBackgroundImage="C:\Users\Public\Pictures\Sample Pictures\Tulips.jpg"
MouseOverBackgroundImage="C:\Users\Public\Pictures\Sample Pictures\Penguins.jpg"
Content="Click Me"/>
Is it possible to turn-off toooltips for all controls (always or based on some rule) without setting TooltipService.IsEnabled on each control? I mean, going through all logical items takes too much time.
Try this. It hides all tooltips.
<Style TargetType="{x:Type ToolTip}">
<Setter Property="Visibility"
Value="Collapsed" />
</Style>
There are several ways you should be able to use to accomplish this. Marco Zhou outlines two of them in this posting., both of these methods relying on setting TooltipService.IsEnabled to False for a parent control such as a Window. Apparently it inherits to all children, so you can set it just there to disable all tooltips.
You could also set all of your Tooltips to a style which had bindings to a property that would make them invisible or disabled when you wanted.
EDIT
Adding the Code to make it easier to understand:
Create the ToolTipEnabled Attached Property which sets the FrameworkPropertyMetadataOptions.Inherits so that it will be inherited by the children.
public class ToolTipBehavior
{
public static Boolean GetIsToolTipEnabled(FrameworkElement obj)
{
return (Boolean)obj.GetValue(ToolTipEnabledProperty);
}
public static void SetToolTipEnabled(FrameworkElement obj, Boolean value)
{
obj.SetValue(ToolTipEnabledProperty, value);
}
public static readonly DependencyProperty ToolTipEnabledProperty = DependencyProperty.RegisterAttached(
"IsToolTipEnabled",
typeof(Boolean),
typeof(ToolTipBehavior),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits, (sender, e) =>
{
FrameworkElement element = sender as FrameworkElement;
if (element != null)
{
element.SetValue(ToolTipService.IsEnabledProperty, e.NewValue);
}
}));
}
You can either use this property in the XAML or codebehind as below:
<Window x:Class="AnswerHarness.ToggleToolTipsDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:AnswerHarness"
Title="ToggleToolTipsDemo" Height="300" Width="300" Name="window">
<StackPanel>
<CheckBox IsChecked="{Binding Path=(cc:ToolTipBehavior.IsToolTipEnabled), ElementName=window}" Content="Enable ToolTip"/>
<Border BorderBrush="Green" BorderThickness="1" Background="Yellow" ToolTip="Border">
<StackPanel>
<Button Width="120" Height="30" Content="Button1" ToolTip="Button1"/>
<Button Width="120" Height="30" Content="Button2" ToolTip="Button2"/>
<Button Width="120" Height="30" Content="Button3" ToolTip="Button3"/>
</StackPanel>
</Border>
</StackPanel>
</Window>
Or
public partial class ToggleToolTipsDemo : Window
{
public ToggleToolTipsDemo()
{
InitializeComponent();
// You can programmatically disable tool tip here.
this.SetValue(ToolTipBehavior.ToolTipEnabledProperty, false);
}
}
Put this style where it is accessible throughout the application(a resourcedictionary or App.xaml) so you won't need to reference this style in any textbox.
<Style BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Microsoft_Windows_Themes:ListBoxChrome x:Name="Bd" ToolTipService.IsEnabled="False" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" SnapsToDevicePixels="true">
<ScrollViewer ToolTipService.IsEnabled="False" x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Microsoft_Windows_Themes:ListBoxChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
<Trigger Property="Text" Value="">
<Setter Property="ToolTipService.IsEnabled" Value="False"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
NOTE
This is the default textbox style generated by Expression blend to which I have added the following trigger which enables tooltips when textbox is not empty and disables them otherwise
<Trigger Property="Text" Value="">
<Setter Property="ToolTipService.IsEnabled" Value="False"/>
</Trigger>
I don't know of any global setting, but there is an easy way to 'visit' all of the elements of your visual tree using Linq-to-VisualTree, I utility I wrote a while back that providers a Linq-to-XML style API for the visual tree.
The following should do the trick:
foreach(var element in window.Descendants())
ToolttipService.SetIsEnabled(element, false);
You can try to use
ToolTipService.IsOpenProperty.OverrideMetadata(typeof(DependencyObject),new PropertyMetadata(false));
I don't have an answer for handling the entire app in one statement, but I've been able to centralize a number of UI-specific parameters in a general base class, then create applications which are derived off this base class and inherit the centralized settings. I should mention there's some extra plumbing you have to add to the base class to support MVVM as in the following:
public class MyMainWindowBaseClass : Window, INotifyPropertyChanged
{
...whatever unrelated stuff you need in your class here...
private int m_toolTipDuration = 3000; // Default to 3 seconds
public int MyToolTipDuration
{
get { return m_toolTipDuration; }
set
{
if (m_toolTipDuration != value)
{
bool transition = (value == 0 || m_toolTipDuration == 0);
m_toolTipDuration = value;
NotifyPropertyChanged("MyToolTipDuration");
if (transition)
{
NotifyPropertyChanged("MyToolTipEnabled");
}
}
}
}
public bool MyToolTipEnabled
{
get { return (m_toolTipDuration > 0); }
}
public event PropertyChangedEventHandler PropertyChanged;
... whatever variables, properties, methods, etc., you need here...
///-----------------------------------------------------------------------------
/// <summary>
/// Fires property-changed event notification
/// </summary>
/// <param name="propertyName">The name of the property that changed</param>
///-----------------------------------------------------------------------------
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The XAML code looks like this:
<Button Command="{Binding StartCommand}"
Content="Start"
FontWeight="Bold"
Height="Auto"
HorizontalAlignment="Left"
Margin="20,40,0,0"
Name="ui_StartButton"
ToolTip="Click this button to begin processing."
ToolTipService.IsEnabled="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=MyToolTipEnabled}"
ToolTipService.ShowDuration="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=MyToolTipDuration}"
VerticalAlignment="Top"
Width="90"/>
With the important bindings being those related to ToolTipService.IsEnabled and ToolTipService.ShowDuration.
You can see that if MyToolTipDuration is set to zero, MyToolTipEnabled will return false and this disables the tooltip. In my first attempt I tried simply setting MyToolTipDuration to zero without using the ToolTipService.IsEnabled= in conjunction with the MyToolTipEnabled property, but all that accomplished was flashing, barely-readable tooltips which appear and disappear.
Overall this worked pretty well for me (ymmv), though not as well as a single setting or single call that would have handled the entire app and circumvented the need for distributing these bindings into every item with a tooltip I wanted to be support the ability to disable. Oh well, when in Rome....
In any event, hopefully someone finds this of use.
Does anyone have any pointers for creating a tristate image button?
I have the following but what I really want to do is have a control with multiple ImageSource properties like <Controls.TristateButton Image="" HoverImage="" PressedImage="" />
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<StackPanel Orientation="Horizontal" >
<Image Name="PART_Image" Source="path to normal image" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" Value="path to mouse over image" TargetName="PART_Image"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Source" Value="path to pressed image" TargetName="PART_Image"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have run into the same problem myself. I have created an open source project here http://imagebuttonwpf.codeplex.com where you can get the latest version of the Image Button.
I don't like the "accepted" solution provided for several reasons (Although it is a lighter solution and has its own advantages)
Blockquote The accepted answer to this StackOverflow question shows an easy way to do this: WPF - How to create image button with template
Mainly I don't think its correct to override the control template for every button you would like to change the image for so I have created a custom control called ImageButton. It extends from button so as to have any of its functionality (though it may be able to extend from content control just as easily) but also contains an Image which can be styled without rewriting the entire control template.
Another reason why I don't like rewriting the entire control template for my button each time is that my base button style contains several borders and animation effects for mouse over, is pressed etc. Rewriting these each time obviously has its own redundancy problems.
Anyway here is the ImageButton class
public class ImageButton : Button
{
static ImageButton() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));
}
#region Dependency Properties
public double ImageSize
{
get { return (double)GetValue(ImageSizeProperty); }
set { SetValue(ImageSizeProperty, value); }
}
public static readonly DependencyProperty ImageSizeProperty =
DependencyProperty.Register("ImageSize", typeof(double), typeof(ImageButton),
new FrameworkPropertyMetadata(30.0, FrameworkPropertyMetadataOptions.AffectsRender, ImageSourceChanged));
public string NormalImage
{
get { return (string)GetValue(NormalImageProperty); }
set { SetValue(NormalImageProperty, value); }
}
public static readonly DependencyProperty NormalImageProperty =
DependencyProperty.Register("NormalImage", typeof(string), typeof(ImageButton),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender,ImageSourceChanged));
public string HoverImage
{
get { return (string)GetValue(HoverImageProperty); }
set { SetValue(HoverImageProperty, value); }
}
public static readonly DependencyProperty HoverImageProperty =
DependencyProperty.Register("HoverImage", typeof(string), typeof(ImageButton),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender, ImageSourceChanged));
public string PressedImage
{
get { return (string)GetValue(PressedImageProperty); }
set { SetValue(PressedImageProperty, value); }
}
public static readonly DependencyProperty PressedImageProperty =
DependencyProperty.Register("PressedImage", typeof(string), typeof(ImageButton),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender, ImageSourceChanged));
public string DisabledImage
{
get { return (string)GetValue(DisabledImageProperty); }
set { SetValue(DisabledImageProperty, value); }
}
public static readonly DependencyProperty DisabledImageProperty =
DependencyProperty.Register("DisabledImage", typeof(string), typeof(ImageButton),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender, ImageSourceChanged));
private static void ImageSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Application.GetResourceStream(new Uri("pack://application:,,," + (string) e.NewValue));
}
#endregion
Next up we need to provide a default control template for our button ive taken most of my borders etc out of this one, bar one so you can see that it is inherited throughout all our styles
<ControlTemplate x:Key="ImageButtonTemplate" TargetType="{x:Type Controls:ImageButton}">
<Grid x:Name="Grid">
<Border x:Name="Background" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="3" />
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<Image x:Name="ButtonImage" Source="{Binding NormalImage, RelativeSource={RelativeSource TemplatedParent}}"
Height="{Binding ImageSize, RelativeSource={RelativeSource TemplatedParent}}"
Width="{Binding ImageSize, RelativeSource={RelativeSource TemplatedParent}}"/>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" />
</StackPanel>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="ButtonImage" Property="Source" Value="{Binding HoverImage, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="ButtonImage" Property="Source" Value="{Binding PressedImage, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="ButtonImage" Property="Source" Value="{Binding DisabledImage, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
</ControlTemplate.Triggers>
then of course we need a default style for our new image button
<Style TargetType="{x:Type Controls:ImageButton}" BasedOn="{x:Null}">
<Setter Property="Template" Value="{StaticResource ImageButtonTemplate}" />
</Style>
And of course the benefits of using this method i have created a style based on the parent style which uses a Setter to change the dependency properties (instead of needed to override the control template - the goal)
<Style x:Key="TestImageButton" TargetType="{x:Type Controls:ImageButton}" BasedOn="{StaticResource {x:Type Controls:ImageButton}}">
<Setter Property="NormalImage" Value="/ImageButton;component/Resources/clear.png"/>
<Setter Property="HoverImage" Value="/ImageButton;component/Resources/clear_green.png" />
<Setter Property="PressedImage" Value="/ImageButton;component/Resources/clear_darkgreen.png" />
<Setter Property="DisabledImage" Value="/ImageButton;component/Resources/clear_grey.png" />
</Style>
and finally this means that one can declare the button in a few different ways either declare the image path in the XAML
<Controls:ImageButton
Content="Test Button 1"
NormalImage="/ImageButton;component/Resources/edit.png"
HoverImage="/ImageButton;component/Resources/edit_black.png"
PressedImage="/ImageButton;component/Resources/edit_darkgrey.png"
DisabledImage="/ImageButton;component/Resources/edit_grey.png"/>
Or alternatively use the style
<Controls:ImageButton
Content="Test Button 2"
Style="{DynamicResource TestImageButton}"/>
Hope it helps
The accepted answer to this StackOverflow question shows an easy way to do this:
WPF - How to create image button with template
You create property triggers on the IsEnabled and IsPressed properties and show or hide the images as needed.
As Avanka noted in his answer, you'll need to create dependency properties to set the paths to the images.
Ideally, you have to create a custom control, inherited from Button. Add three dependency properties, and create default style for new control.
You can check ImageButton class from FluidKit library - it does exactly what you want.