Bind attached property in ControlTemplate

117 Views Asked by At
<Style x:Key="DeckButton_Help" TargetType="{x:Type Button}">
    <Setter Property="Height" Value="22"/>
    <Setter Property="Padding" Value="0,0"/>
    <Setter Property="Margin" Value="0,0"/>
    <Setter Property="Content" Value=""/>
    <Setter Property="local:ButtonProperties.ImageSource" Value=""/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Button Style="{StaticResource ButtonForeground}" HorizontalContentAlignment="Left" Background="#e1e1e1" BorderBrush="#9d9d9d" 
                        Command="{TemplateBinding Command}" Padding="5,4,0,0" >
                    <Button.Content>
                        <Grid Height="16">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="16"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <Image  Style="{StaticResource ButtonImage}" Height="16" Width="16" Source="{TemplateBinding local:ButtonProperties.ImageSource}" 
                                    VerticalAlignment="Center"  HorizontalAlignment="Left" Margin="0,-4,0,0"/>
                            <TextBlock Grid.Column="1" Text="{TemplateBinding Content}" HorizontalAlignment="Center" Margin="5,0,0,0" />
                        </Grid>
                    </Button.Content>
                </Button>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Here I am trying to set local:ButtonProperties.ImageSource property from the outer button.

<Button local:ButtonProperties.ImageSource="/CommonResources;component/bitmap/IDB_BUTTON_HELP.PNG" 
 Style="{StaticResource DeckButton_Help}" Command="{Binding HelpCommand}" Content="Help" Margin="20,0,0,0" Width="65" />

and ButtonProperties is defined in this way:

public class ButtonProperties
{
    public static readonly DependencyProperty ImageSourceProperty =
        DependencyProperty.RegisterAttached(
            "ImageSource",
            typeof(string),
            typeof(ButtonProperties),
            new FrameworkPropertyMetadata(null));

    public static string GetImageSource(DependencyObject obj)
    {
        return (string)obj.GetValue(ImageSourceProperty);
    }

    public static void SetImageSource(DependencyObject obj, string value)
    {
        obj.SetValue(ImageSourceProperty, value);
    }
}

The problem is that icon is not set and when I debug SetImageSource and GetImageSource are not called.

2

There are 2 best solutions below

0
Muhammad Sulaiman On BEST ANSWER

Instead of a TemplateBinding you could use a regular Binding like shown below. It would provide automatic type conversion from string to ImageSource.

<Image ... Source="{Binding RelativeSource={RelativeSource TemplatedParent},
                            Path=(local:ButtonProperties.ImageSource)}"/>

If you are setting the image path correctly, this should work (I've tested the code on my device).


It would however make sense to declare the attached property as ImageSource instead of a string, so that a TemplateBinding would also work. The conversion from string to ImageSource would then take place when you assign the attached property on the Button, and the attached property could not contain any invalid strings.

public class ButtonProperties
{
    public static readonly DependencyProperty ImageSourceProperty =
        DependencyProperty.RegisterAttached(
            "ImageSource",
            typeof(ImageSource),
            typeof(ButtonProperties));

    public static ImageSource GetImageSource(DependencyObject obj)
    {
        return (ImageSource)obj.GetValue(ImageSourceProperty);
    }

    public static void SetImageSource(DependencyObject obj, ImageSource value)
    {
        obj.SetValue(ImageSourceProperty, value);
    }
}

Side notes:

  1. Breakpoints in dependency property setters and getters won't get hit under certain circumstances, e.g. when the property is set in XAML. The framework directly calls SetValue and GetValue.
  2. If the default value of a property of type string is null, then no need to use new FrameworkPropertyMetadata(null) in property definition, nor <Setter Property="local:ButtonProperties.ImageSource" Value=""/> in style definition.
3
Clemens On

A TemplateBinding does not perform automatic type conversion, as you are expecting in the Binding

Source="{TemplateBinding local:ButtonProperties.ImageSource}

where the source property is of type string and the target property of type ImageSource.

You have to use the correct type in the declaration of your attached property:

public class ButtonProperties
{
    public static readonly DependencyProperty ImageSourceProperty =
        DependencyProperty.RegisterAttached(
            "ImageSource",
            typeof(ImageSource),
            typeof(ButtonProperties));

    public static ImageSource GetImageSource(DependencyObject obj)
    {
        return (ImageSource)obj.GetValue(ImageSourceProperty);
    }

    public static void SetImageSource(DependencyObject obj, ImageSource value)
    {
        obj.SetValue(ImageSourceProperty, value);
    }
}

You also have to remove the Setter

<Setter Property="local:ButtonProperties.ImageSource" Value=""/>

from the Button Style, because an empty string is not a valid value for a property of type ImageSource.


As a note, the SetImageSource method is not called when the ImageSource property is set in XAML. The framework bypasses the setter and getter and directly calls SetValue and GetValue.