6

My WPF project uses a lot of image buttons, but since I haven't found a way to do it properly (I have to write the same triggers and style each time, only difference is the image source), my resource dictionary became very long for nothing. Is there a better way of doing this?

Here's a sample of the style I'm using for my buttons :

<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
    <!-- Some setters -->
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Grid>
                    <Image Source="Images.png" Stretch="Fill"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <!-- Some triggers ( IsFocused, IsMouseOver, etc.) -->
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Thank you :)

Shugah
  • 86
  • 1
  • 3

3 Answers3

11

The easiest way is to create a specific control with an Image property:

public class ImageButton : Button
{
    static ImageButton()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof (ImageButton),
            new FrameworkPropertyMetadata(typeof (ImageButton)));
    }


    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(default(ImageSource)));

}

Then you just create a style for it in generic.xaml, and bind to the Image property instead of setting the image explicitly:

<Style x:Key="{x:Type my:ImageButton}" TargetType="{x:Type my:ImageButton}">
    <!-- Some setters -->
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type my:ImageButton}">
                <Grid>
                    <Image Source="{TemplateBinding Image}" Stretch="Fill"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <!-- Some triggers ( IsFocused, IsMouseOver, etc.) -->
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Then you can just use it like this:

<my:ImageButton Image="Image.png" />

If you need more images for different states of the button, you can add more dependency properties to the control.

Another possible approach is to use what I call a "parameterized style", to avoid creating a specific control; see this blog post for details.

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • This example shows one major problem with custom controls in WPF. As soon as I overide the ControlTemplate, all theming is basically broken. How can I keep all the theming of the original button under all environments? Of course I can copy the original ControlTemplate of the Button into my ImageButton but this will only work for the generic theme. I would have to provide control templates for all themes I want to support. But this breaks if there is a theme I didn't know. – bitbonk Oct 20 '15 at 12:17
  • @bitbonk, yes, that's one of the major drawbacks of this templating system... I don't know of any good solution, unfortunately. – Thomas Levesque Oct 20 '15 at 12:53
  • @ThomasLevesque The link to 'this blog post' leads to this StackOverflow question and not to the blog post :( – kazarey Aug 14 '17 at 06:30
  • @ThomasLevesque Also, shouldn't the 5th line of the XAML snippet contain `{x:Type my:ImageButton}` instead of `{x:Type Button}`? I'm getting 'The member is not recognized or is not accessible' error, but with this change everything works fine. – kazarey Aug 14 '17 at 06:35
  • @kazarey I fixed the link, and the XAML. Thanks! – Thomas Levesque Aug 16 '17 at 07:54
1

You could try to create a custom control that inherits from Button, and use it as TargetType in your template. Then you can use TemplateBinding in the Image Source.

<Image Source="{TemplateBinding ImageSource}" Stretch="Fill"/>

You will need to create the ImageSource property in your custom control. That way you can set the source in xaml, but you need only one resource template.

dave
  • 223
  • 2
  • 9
0

You can use the BasedOn Attribute like this:

<Style x:Key="BlueHighlightedButtonStyle"  BasedOn="{StaticResource ButtonStyle}" TargetType="Button">
    <Setter Property="Background" Value="DeepSkyBlue"/>
</Style>

This will make a Button Style with the same attributes as your ButtonStyle, but with a DeepSkyBlue Background.

Florian Baierl
  • 2,378
  • 3
  • 25
  • 50
  • Only problem is that the BaseOn attribute doesn't work with all the triggers, I want to avoid redefining them each time I add a different button. I tried to use TemplateBinding in the ControlTemplate but it didn't work. – Shugah Aug 28 '13 at 07:36