1

What I want:

I created a UserControl ImageButton, where the images can be set in WPF. Usually, I'm reusing the default image for all states but one, but have to configure the control in a very verbose way (see WPF snipped below). What I'd like to achieve is, that every image that is not set, just uses the default image.

What I have:

This is the way I'm describing the ImageButton at the moment:

<cc:ImageButton x:Name="cmdHideCustomWindowingArea" 
                DefaultImage="/Images/UI/ButtonDefault.png"
                HoverImage="/Images/UI/ButtonDefault.png"
                DownImage="/Images/UI/ButtonActive.png"
                UpImage="/Images/UI/ButtonDefault.png"
                DisabledImage="/Images/UI/ButtonDefault.png"/>

This is the style im using:

<Style TargetType="{x:Type cc:ImageButton}">
  <Setter Property="Template" >
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type cc:ImageButton}">
        <Grid x:Name="Grid">
          <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
            <Image x:Name="ButtonImage" Source="{Binding DefaultImage, 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 DownImage, RelativeSource={RelativeSource TemplatedParent}}" />
          </Trigger>
          <Trigger Property="IsEnabled" Value="false">
            <Setter TargetName="ButtonImage" Property="Source" Value="{Binding DisabledImage, RelativeSource={RelativeSource TemplatedParent}}" />
          </Trigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>  

And this is the code:

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

    public ImageSource DefaultImage
    {
        get { return (ImageSource)GetValue(DefaultImageProperty); }
        set { SetValue(DefaultImageProperty, value); }
    }
    public static readonly DependencyProperty DefaultImageProperty =
        DependencyProperty.Register("DefaultImage",
        typeof(ImageSource), typeof(ImageButton), new PropertyMetadata(null));

    public ImageSource HoverImage
    {
        get { return (ImageSource)GetValue(HoverImageProperty); }
        set { SetValue(HoverImageProperty, value); }
    }
    public static readonly DependencyProperty HoverImageProperty =
        DependencyProperty.Register("HoverImage",
        typeof(ImageSource), typeof(ImageButton), new PropertyMetadata(null));

    // 
    // ... and so on for every state (Down, Up, Disabled)
    //
}

What I've tried so far:

1. I already tried checking if the image is set like this, which does not work and the code seems to never even reach the getter at all...

get { return (ImageSource)GetValue(HoverImageProperty) ?? DefaultImage; }

2. Also, setting a TargetNullValue like recommended here does not work ("The member "TargetNullValue" is not recognized or is not accessible."):

<Trigger Property="IsPressed" Value="true">
    <Setter TargetName="ButtonImage" Property="Source" 
            Value="{Binding DownImage, RelativeSource={RelativeSource TemplatedParent}}" 
            TargetNullValue="{Binding DefaultImage, RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>

3. The last thing I did was trying to set a PriorityBinding, like recommended here:

<Trigger Property="IsMouseOver" Value="true">
    <Setter TargetName="ButtonImage" Property="Source">
        <Setter.Value>
            <PriorityBinding>
                <Binding Path="{Binding HoverImage, RelativeSource={RelativeSource TemplatedParent}}"/>
                <Binding Path="{Binding DefaultImage, RelativeSource={RelativeSource TemplatedParent}}"/>
            </PriorityBinding>
        </Setter.Value>
    </Setter> 
</Trigger>

But that returns the following error:

A 'Binding' cannot be set on the 'Path' property of type 'Binding'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

What I'll have to do:

What would be the right way to fall back to a another DependencyProperty in case the one I'm looking for is not set?

Community
  • 1
  • 1
DIF
  • 2,470
  • 6
  • 35
  • 49

1 Answers1

2

It sounds as though you are looking for the PriorityBinding Class (despite your earlier attempt at using it). From the linked page, a PriorityBinding:

Describes a collection of Binding objects that is attached to a single binding target property, which receives its value from the first binding in the collection that produces a value successfully.

...

PriorityBinding lets you associate a binding target (target) property with a list of bindings. The first binding that returns a value successfully becomes the active binding.

A binding returns a value successfully if:

1.The path to the binding source resolves successfully.

2.The value converter, if any, is able to convert the resulting value.

3.The resulting value is valid for the target property.

You should rearrange your Binding Path in your PriorityBindings... try this instead:

<PriorityBinding>
    <Binding Path="HoverImage" RelativeSource="{RelativeSource TemplatedParent}" />
    <Binding Path="DefaultImage" RelativeSource="{RelativeSource TemplatedParent}" />
</PriorityBinding>

For further information, you can also read through the WPF Tutorial - Priority Bindings tutorial on the Tech Pro website.


UPDATE >>>

As you correctly pointed out, the PriorityBinding will indeed accept null as a valid, returned value. However, you can get around that problem by using a custom IValueConverter implementation that returns DependencyProperty.UnsetValue when the actual value is null.

CSharpie
  • 9,195
  • 4
  • 44
  • 71
Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • Thanks for the suggestion. This actually works a little better, it does not throw an error any more, but now the button "flickers". I think a "null" value might be seen as successfull and so the buttn is switching between "null" and the default image... (because it has not size when no image is loaded and so the mouse does not hover over it and so it's going back to default and so on) – DIF Nov 05 '14 at 11:08