TemplateBinding to DependencyProperty on a custom control not working

16,279

I have encountered this before, TemplateBinding does not work for custom dependency properties on controls. See these related questions:

issues with template binding and binding of custom component

TemplateBinding does not work in certain cases(when using TranslateTransform)

I have always used this instead:

{Binding MyProperty, RelativeSource={RelativeSource TemplatedParent}}

It is semantically the same as TemplateBinding, and can also support value converters etc ...

Share:
16,279
Waleed
Author by

Waleed

CS MSc Grad.

Updated on June 08, 2022

Comments

  • Waleed
    Waleed almost 2 years

    Currently, I'm working on a simple custom button that uses user supplied images as a background for the pressed and normal states. I've a lot of buttons so I decided to write a custom button and implement two properties for the pressed and normal states' pictures.

    Here is the code I'm using

    public partial class ThemeableButton : Button
    {
        public ThemeableButton()
        {
            InitializeComponent();
        }
    
    
        public static readonly DependencyProperty PressedContentBackgroundSourceProperty = DependencyProperty.Register(
                        "PressedContentBackgroundSource", typeof(ImageSource), typeof(ThemeableButton), null);
        public ImageSource PressedContentBackgroundSource
        {
            get { return (ImageSource)GetValue(PressedContentBackgroundSourceProperty); }
            set
            {
                (value as BitmapImage).CreateOptions = BitmapCreateOptions.BackgroundCreation; 
                SetValue(PressedContentBackgroundSourceProperty, value);
            }
        }
    
    
        public static readonly DependencyProperty NormalContentBackgroundSourceProperty =
            DependencyProperty.Register("NormalContentBackgroundSource", typeof(ImageSource), typeof(ThemeableButton), null);
    
        public ImageSource NormalContentBackgroundSource
        {
            get { return (ImageSource)GetValue(NormalContentBackgroundSourceProperty); }
            set
            {
                (value as BitmapImage).CreateOptions = BitmapCreateOptions.BackgroundCreation;
                SetValue(NormalContentBackgroundSourceProperty, value);
            }
        }
    }
    

    I wrote the style for this button as follows

            <Style x:Key="ThemeableButtonTemplate" TargetType="MJbox_UIComponents_Controls:ThemeableButton">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="BorderBrush" Value="{StaticResource PhoneForegroundBrush}"/>
            <Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
            <Setter Property="BorderThickness" Value="{StaticResource PhoneBorderThickness}"/>
            <Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiBold}"/>
            <Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMediumLarge}"/>
            <Setter Property="Padding" Value="0"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="MJbox_UIComponents_Controls:ThemeableButton">
                        <Grid>
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Source" Storyboard.TargetName="ButtonBackground">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{TemplateBinding NormalContentBackgroundSource}">
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Pressed">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Source" Storyboard.TargetName="ButtonBackground">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{TemplateBinding PressedContentBackgroundSource}">
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <Border BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="0">
                                <Image x:Name="ButtonBackground" Stretch="None" Source="{TemplateBinding NormalContentBackgroundSource}"/>
                            </Border>       
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    

    I tried a simple example

    <Controls:ThemeableButton Style="{StaticResource ThemeableButtonTemplate}" x:Name="btnDidntNeedIt" NormalContentBackgroundSource="{Binding Source={StaticResource DefaultTheme}, Path=DidntHaveButtonUnselected}"
                                       PressedContentBackgroundSource="{Binding Source={StaticResource DefaultTheme}, Path=DidntHaveButtonSelected}"
             />
    

    but the image is not showing, I tried by removing the TemplateBinding from the style and replaced it with the relative source to the image file and it worked fine. I just don't wanna create a customized style for each button on the app. Any possible workaround?

  • TernaryTopiary
    TernaryTopiary over 7 years
    This is the answer that worked for me too. I wish that TemplateBinding just worked and that some of the mystery and arbitrariness was dispelled.
  • Stevens Miller
    Stevens Miller almost 7 years
    "...TemplateBinding does not work for custom dependency properties on controls[.]" It works for me. <Rectangle Fill="{TemplateBinding FillBrush}" /> in my custom control's ControlTemplate fills the Rectangle with my FillBrush custom dependency property, even showing that property in VS's property sheet and filling it with whatever I set it to during design time. Maybe that's something new since 2011? Anyway, it works fine now. (But watch out for animating to things that aren't Freezeables. You still can't do that.)
  • Stevens Miller
    Stevens Miller almost 7 years
    To be more specific (at the risk of SO's bots grousing at me for extended discussions in comments), TemplateBinding won't work on the properties of Freezeables. As far as I can tell, it fails silently. Your solution certainly applies in those cases.
  • CodeOtaku
    CodeOtaku almost 3 years
    This doesn't seem to work on .net 4.7.2. Did they close this loophole?