Command binding issue in CustomControl: CanExecute() fires, Execute() does not - wpf

I'm binding a button command, in a ControlTemplate, to an Execute() method in a CustomControl. I'm using a RoutedCommand, the CanExecute() fires, but the Execute() never does. When the CustomControl is placed in the main window, the code works as expected. When it is placed in a Usercontrol, I have this issue. I have tried several ways to wire up the buttons Command (RelayCommand etc) but can't seem to figure out what's wrong. Any help is appreciated.
For context, this is a TokenizingTextBox control - an early fork of the Xceed open source version. The button is for deleting the token from the list of tokens.
The complete style of a TokenIten (which contains the button of interest):
<Style TargetType="{x:Type local:TokenItem}">
<Setter Property="Background" Value="#F3F7FD" />
<Setter Property="BorderBrush" Value="#BBD8FB" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Cursor" Value="Arrow" />
<Setter Property="Padding" Value="2,1,1,1" />
<Setter Property="Margin" Value="1,0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TokenItem}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
CornerRadius="0,0,5,5"
Margin="{TemplateBinding Margin}"
>
<StackPanel Orientation="Horizontal" Margin="1" x:Name="myRoot">
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" />
<Button Margin="3,0,0,0" Cursor="Hand"
Command="{x:Static local:TokenizedTextBoxCommands.Delete}" CommandParameter="{TemplateBinding TokenKey}"
PresentationTraceSources.TraceLevel="High">
<!--<Button.Template>
<ControlTemplate TargetType="Button">
<ContentPresenter />
</ControlTemplate>
</Button.Template>-->
<Image Source="/Resources/delete8.png" Width="8" Height="8" />
</Button>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The static Command:
public static class TokenizedTextBoxCommands
{
private static RoutedCommand _deleteCommand = new RoutedCommand();
public static RoutedCommand Delete => _deleteCommand;
}
The Custom Control inherits from ItemsControl. In the non-static constructor, we wire up the static delete command to the DeleteToken method:
public TokenizedTextBox()
{
CommandBindings.Add(new CommandBinding(TokenizedTextBoxCommands.Delete, DeleteToken, CanDelete));
}
Finally CanDelete which just sets CanExecute to true:
private void CanDelete(object sender, CanExecuteRoutedEventArgs canExecuteRoutedEventArgs)
{
canExecuteRoutedEventArgs.CanExecute = true;
}
And DeleteToken - functionality omitted, signature is really only important thing here:
private void DeleteToken(object sender, ExecutedRoutedEventArgs e)
{
...
}
So, hopefully this is enough information for anyone interested in providing guidance/suggestions. Thanks.

Little interest here so I hired a Mentor through Pluralsight. The bindings were correct, but the CustomControl had a RichTextBox which was capturing the Mouse Click. We fixed the issue using a Behavior targeting the Button's PreviewMouseDown.

Related

Application.Resource inline code to find

All,
I am trying to create a Metro Window Style. I have the style in Application.Resource file. I am trying to enable Dragging of the window in inline code. The problem is, when I have the style inside the Window XAML itself, I can access the properties of the Window. But in Application.Resource file, I loose the reference to the Window.
What I want to know is: how can I access the Window properties in inline code to which the style is applied to so I can apply the DragMove to the Window?
If this is not the right way then can I get the correct way of handling this problem please?
<Application.Resources>
<Style x:Key="MetroWindowStyle" TargetType="{x:Type Window}">
<Setter Property="WindowStyle" Value="None" />
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome ResizeBorderThickness="6" CaptionHeight="0" GlassFrameThickness="0" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border x:Name="MainBorder" Background="#FFEEEEEE" BorderBrush="#FFA4A4A4" BorderThickness="1">
<DockPanel LastChildFill="True">
<Border x:Name="HeaderBorder" Height="30" Background="#FFE6E6E6" DockPanel.Dock="Top" MouseLeftButtonDown="HeaderBorder_MouseLeftButtonDown">
</Border>
<AdornerDecorator DockPanel.Dock="Bottom">
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}"/>
</AdornerDecorator>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<x:Code>
<![CDATA[
private void HeaderBorder_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove(); //////Complains here because I don't have access to the Window to which this style is applied to
}
]]>
</x:Code>
</Application.Resources>
Thanks
You could use an attached behaviour.
public class DraggableWindowBehaviour : Behavior<Window>
{
protected override void OnAttached()
{
AssociatedObject.PreviewMouseLeftButtonDown += _associatedObject_MouseLeftButtonDown;
}
private void _associatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
AssociatedObject.DragMove();
}
protected override void OnDetaching()
{
AssociatedObject.PreviewMouseLeftButtonDown -= _associatedObject_MouseLeftButtonDown;
}
}
Inside the Border control of your window control template:
<e:Interaction.Behaviors>
<b:DraggableWindowBehaviour/>
</e:Interaction.Behaviors>

How to bind Fill property to a custom property into a controltemplate

I have a button control which its template is stilyzed in an external resource Theme.xaml. Below the controltemplate definition:
<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
<Grid x:Name="Grid">
<Border x:Name="Background" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="2,2,2,2" CornerRadius="2,2,2,2">
<Border x:Name="Hover" Background="{StaticResource HoverBrush}" CornerRadius="1,1,1,1" Height="Auto" Width="Auto" Opacity="0"/>
</Border>
<StackPanel Orientation="Horizontal" Margin="2,2,2,2">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" />
</StackPanel>
...
Now I added an item which is an ellipse that must be filled with red or green color (as a semaphore) depending on a custom property defined into my usercontrol:
<UserControl.Resources>
<ResourceDictionary Source="Themes/theme.xaml"/>
</UserControl.Resources>
<Grid>
<Button Click="Button_Click"></Button>
<Ellipse x:Name="ellipse1" Width="20" Height="20" Margin="5,40,45,5"></Ellipse>
</Grid>
and in the behind code I have:
private SolidColorBrush ButtonValue_;
public SolidColorBrush ButtonValue {
get { return ButtonValue_; }
set {
ButtonValue_ = value;
}
}
I'm trying to put into the CONTROLTEMPLATE this ellipse item, but i have some problems regarding how to BIND the Fill property of the ellipse with the ButtonValue custom property into the controlTemplate.
Any hints??
Thanks in advance
You can go to several directions:
Implement a custom control, that is your own class derived from an existing control (Button in your case). Add a dependency property (e.g. ButtonValue). Note - dependency property aren't standard .NET property - they have much more. Check out the following sample: http://msdn.microsoft.com/en-us/library/cc295235(v=expression.30).aspx (A custom button), or here: http://wpftutorial.net/HowToCreateACustomControl.html (A simpler sample, but without a property.
Have a data context for the control. Typically the data context is a separate class (a.k.a. the "View Model"), but if you aren't following the mvvm paradigm, it is OK the data context is self. Whatever data context you are using, it must derived from INotifyPropertyChanged, and it must file PropertyChanged event.
(Recommended!) Create a Control Template for CheckBox. When you come to think about it, logically your control is really a button with a binary state. Red/Green in your case, Checked/Unchecked for a CheckBox. So logically, you are looking for a checkbox, but you just want to present it differently.
So in your control template, draw the ellipse, and add a trigger for the IsChecked property:
<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type CheckBox}">
<Grid>
... everything else in the control ...
<Ellipse x:Name="ellipse1" Width="20" Height="20" Margin="5,40,45,5" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="ellipse1" Property="Fill" Value="Red" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="ellipse1" Property="Fill" Value="Green" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
This is a nice example for the difference between behavior and presentation of WPF.
While your control may look like a button, it behaves like a CheckBox, in the sense that it has two states.
EDIT: Use ToggleButton - this is the base class of CheckBox (and RadioButton), and it has exactly the functionality that you need, including the IsChecked property.
You have a couple of options:
1.The easiest one is to re-purpose an unused Brush or Color(with a converter) Button existing property:
<Window.Resources>
<ControlTemplate x:Key="repurposedProperty" TargetType="Button">
<Border Background="{TemplateBinding BorderBrush}">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Window.Resources>
...
<Button Template="{StaticResource repurposedProperty}">Button</Button>
2.Other option is to define an attached property and use it in the ControlTemplate. On any Button that you apply the template to you have to set the attached property:
public static readonly DependencyProperty AttachedBackgroundProperty =
DependencyProperty.RegisterAttached("AttachedBackground", typeof (Brush), typeof (MainWindow),
new PropertyMetadata(default(Brush)));
public static void SetAttachedBackground(UIElement element, Brush value)
{
element.SetValue(AttachedBackgroundProperty, value);
}
public static Brush GetAttachedBackground(UIElement element)
{
return (Brush) element.GetValue(AttachedBackgroundProperty);
}
...
<
Window.Resources>
<ControlTemplate x:Key="attachedProperty" TargetType="Button">
<Border Background="{TemplateBinding WpfApplication1:MainWindow.AttachedBackground}">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Window.Resources>
...
<Button Template="{StaticResource attachedProperty}">
<WpfApplication1:MainWindow.AttachedBackground>
<SolidColorBrush Color="Pink"></SolidColorBrush>
</WpfApplication1:MainWindow.AttachedBackground>
Button</Button>
PS: you can use a binding to set the value of the attached property.

How turn-off tooltips for the whole application

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.

Silverlight: Binding a custom control to an arbitrary object

I am planning on writing a hierarchical organizational control, similar to an org chart. Several org chart implementations are out there, but not quite fit what I have in mind.
Binding fields in a DataTemplate to a custom object does not seem to work.
I started with a generic, custom control, i.e.
public class NodeBodyBlock : ContentControl
{
public NodeBodyBlock()
{
this.DefaultStyleKey = typeof(NodeBodyBlock);
}
}
It has a simple style in generic.xaml:
<Style TargetType="org:NodeBodyBlock">
<Setter Property="Width" Value="200" />
<Setter Property="Height" Value="100" />
<Setter Property="Background" Value="Lavender" />
<Setter Property="FontSize" Value="11" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="org:NodeBodyBlock">
<Border Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"
Background="{TemplateBinding Background}" CornerRadius="4" BorderBrush="Black" BorderThickness="1" >
<Grid>
<VisualStateManager/> ... clipped for brevity
</VisualStateManager.VisualStateGroups>
<ContentPresenter Content="{TemplateBinding Content}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My plan now is to be able to use this common definition as a base definition of sorts, with customized version of it used to display different types of content.
A simple example would be to use this on a user control with the following style:
<Style TargetType="org:NodeBodyBlock" x:Key="TOCNode2">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=NodeTitle}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
and an instance defined as
<org:NodeBodyBlock Style="{StaticResource TOCNode2}" x:Name="stTest"
DataContext="{StaticResource DummyData}" />
The DummyData is defined as
<toc:Node NodeNumber="mynum" NodeStatus="A"
NodeTitle="INLine Node Title!"
x:Key="DummyData"/>
With a simple C# class behind it, where each of the fields is a public property.
When running the app, the Dummy Data values simply do not show up in the GUI. A trivial test such as
<TextBlock Text="{Binding NodeTitle}" DataContext="{StaticResource DummyData}"/>
works just fine.
Any ideas around where I am missing the plot?
Update: Binding to the datacontext in the definition in generic.xaml works fine, but any binding in the ContentPresenter is lost.
Your control template is missing a binding on the ContentPresenter, it should look like this:-
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
I just ended up using this example as a base:
http://10rem.net/blog/2010/02/05/creating-customized-usercontrols-deriving-from-contentcontrol-in-wpf-4
Not quite sure what I missed, but the example works.

WPF TriState Image Button

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.

Resources