WPF TriState Image Button

18,807

Solution 1

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.

Solution 2

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

Solution 3

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.

Share:
18,807
David Ward
Author by

David Ward

I specialise in Microsoft technologies, primarily c#, .net, SQL Server

Updated on June 27, 2022

Comments

  • David Ward
    David Ward almost 2 years

    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>
    
  • Nam G VU
    Nam G VU almost 14 years
    You can write an article for this on codeproject.net with such a solution like this :) One more thing, can you add a sample usage codes of your ImageButton control? It would be much easier for us to apply your solution quickly.
  • PJUK
    PJUK almost 14 years
    I have created this as a project on codeplex imagebuttonwpf.codeplex.com if you download the release from that it has it all setup in a project.
  • Robert Taylor
    Robert Taylor about 13 years
    There is no accepted answer to that question. Also the highest voted answer does not really answer the question; it is not a very reusable control since the style has 3 hardcoded image paths.
  • surfen
    surfen about 12 years
    If you need to be able to change something inside the style or template, for example the button text or image path, you can use attached properties like this: stackoverflow.com/a/650620/724944