locked
Overriding value in Visual State for Custom Button class using custom Dependency Property

    Question

  • Hi,

    This might have been asked already, but I couldn't find it. It wasn't quite clear what to search for.

    The basic problem I am trying to solve is that if I want to only change one of the properties of a button that is edited in the visual states Storyboards, then it seems that I have to recreate the entire button's Style (or at least the ControlTemplate) every time. For example, let's assume that I have a type of button that has 2 different states (but is not a ToggleButton). The only difference between these two states is the background color of the button. Let's say in state A, the button is red and in state B, the button is green. I can easily do this by changing the background color.

    Now, let's say I also want to change the color in the PointerOver state. I could of course create two complete button styles and set them up completely from scratch, and then change the style of the button depending on state. But that seems like a lot of work, and it really clutters my XAML. Ideally, there would be a way to add another dependency property to a custom button, say ExtraBrush, and then in the control template, you could use TemplateBinding ExtraBrush in the PointerOverState.

    I did some experiments along this way, but couldn't really figure it out.
    What I got to so far is below. It doesn't work, but I hope you can see what I am trying to do. Look for the use of ExtraBrush in the XAML.

    <Style TargetType="local:MyButton">
                <Setter Property="Background" Value="Green" />
                <Setter Property="Foreground" Value="{ThemeResource ButtonForegroundThemeBrush}"/>
                <Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderThemeBrush}" />
                <Setter Property="ExtraBrush" Value="Red"/>
                <Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
                <Setter Property="Padding" Value="12,4,12,4" />
                <Setter Property="HorizontalAlignment" Value="Left" />
                <Setter Property="VerticalAlignment" Value="Center" />
                <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
                <Setter Property="FontWeight" Value="SemiBold" />
                <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="local:MyButton">
                            <Grid>
                                <VisualStateManager.VisualStateGroups>
                                    <VisualStateGroup x:Name="CommonStates">
                                        <VisualState x:Name="Normal" />
                                        <VisualState x:Name="PointerOver">
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                                       Storyboard.TargetProperty="Background">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{TemplateBinding ExtraBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                                       Storyboard.TargetProperty="Foreground">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonPointerOverForegroundThemeBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                        <VisualState x:Name="Pressed">
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                                       Storyboard.TargetProperty="Background">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonPressedBackgroundThemeBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                                       Storyboard.TargetProperty="Foreground">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonPressedForegroundThemeBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                        <VisualState x:Name="Disabled">
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                                       Storyboard.TargetProperty="Background">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonDisabledBackgroundThemeBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                                       Storyboard.TargetProperty="BorderBrush">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonDisabledBorderThemeBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                                       Storyboard.TargetProperty="Foreground">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonDisabledForegroundThemeBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                    </VisualStateGroup>
                                    <VisualStateGroup x:Name="FocusStates">
                                        <VisualState x:Name="Focused">
                                            <Storyboard>
                                                <DoubleAnimation Storyboard.TargetName="FocusVisualWhite"
                                                         Storyboard.TargetProperty="Opacity"
                                                         To="1"
                                                         Duration="0" />
                                                <DoubleAnimation Storyboard.TargetName="FocusVisualBlack"
                                                         Storyboard.TargetProperty="Opacity"
                                                         To="1"
                                                         Duration="0" />
                                            </Storyboard>
                                        </VisualState>
                                        <VisualState x:Name="Unfocused" />
                                        <VisualState x:Name="PointerFocused" />
                                    </VisualStateGroup>
                                </VisualStateManager.VisualStateGroups>
                                <Border x:Name="Border"
                                Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Margin="3">
                                    <ContentPresenter x:Name="ContentPresenter"
                                              Content="{TemplateBinding Content}"
                                              ContentTransitions="{TemplateBinding ContentTransitions}"
                                              ContentTemplate="{TemplateBinding ContentTemplate}"
                                              Margin="{TemplateBinding Padding}"
                                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                                              AutomationProperties.AccessibilityView="Raw"/>
                                </Border>
                                <Rectangle x:Name="FocusVisualWhite"
                                   IsHitTestVisible="False"
                                   Stroke="{ThemeResource FocusVisualWhiteStrokeThemeBrush}"
                                   StrokeEndLineCap="Square"
                                   StrokeDashArray="1,1"
                                   Opacity="0"
                                   StrokeDashOffset="1.5" />
                                <Rectangle x:Name="FocusVisualBlack"
                                   IsHitTestVisible="False"
                                   Stroke="{ThemeResource FocusVisualBlackStrokeThemeBrush}"
                                   StrokeEndLineCap="Square"
                                   StrokeDashArray="1,1"
                                   Opacity="0"
                                   StrokeDashOffset="0.5" />
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        class MyButton : Windows.UI.Xaml.Controls.Button
        {
            public MyButton() : base()
            {
            }
    
            public static readonly DependencyProperty ExtraBrushProperty =
            DependencyProperty.RegisterAttached("ExtraBrush", typeof(Brush),
            typeof(MyButton), new PropertyMetadata(null));
    
            private Brush _ExtraBrush;
            public Brush ExtraBrush
            {
                get { return _ExtraBrush; }
                set { _ExtraBrush = value; }
            }
        }

    Thanks,

    Tomas

    Thursday, October 9, 2014 10:47 PM

Answers

  • Hi Tomas,

    TemplateBinding isn’t support in the VisualStateManger. You can see details in TemplateBinding markup extension. Use RelativeSource instead. Code like the following.

    Value=“{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ExtraBrush }”

    Regards,


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    • Marked as answer by T Hofmann Friday, October 17, 2014 5:04 PM
    Saturday, October 11, 2014 3:30 AM
    Moderator
  • Hi Tomas,

    You cannot set ExtraBrush property in control, it won’t work. But i cannot find any official explanation about this behaviour. After troubleshooting, I find the template will not get the extrabrush property from the button object, it just get value from the setter in template. So if you want to init this extrabrush, you could use static resource instead. Code like following.

    XAML in app.xaml

    <Application
        x:Class="App1.App"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:App1">   
        <Application.Resources>
            <SolidColorBrush Color="Black" x:Key="lg1"></SolidColorBrush>
        </Application.Resources>
    </Application>

    XAML in template

     <Setter Property="ExtraBrush" Value="{StaticResource lg1}"></Setter>

    If you still have questions, please feel free to let me know.

    Regards,


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place. Click HERE to participate the survey.

    • Marked as answer by T Hofmann Friday, October 17, 2014 5:04 PM
    Thursday, October 16, 2014 2:34 PM
    Moderator

All replies

  • Hi Tomas,

    TemplateBinding isn’t support in the VisualStateManger. You can see details in TemplateBinding markup extension. Use RelativeSource instead. Code like the following.

    Value=“{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ExtraBrush }”

    Regards,


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    • Marked as answer by T Hofmann Friday, October 17, 2014 5:04 PM
    Saturday, October 11, 2014 3:30 AM
    Moderator
  • Thanks Herro, that works so far.

    Is there a way to make this work for when overriding that ExtraBrush value on the button?

    For example, if I declare a Button in XAML as follows:

    <local:MyButton Content="Lala" ExtraBrush="Yellow"/>

    then it doesn't actual change the color when the pointer is over the button.

    Can this be done easily?

    Best Regards,

    Tomas Hofmann

    Tuesday, October 14, 2014 6:03 PM
  • Hi Tomas,

    You cannot set ExtraBrush property in control, it won’t work. But i cannot find any official explanation about this behaviour. After troubleshooting, I find the template will not get the extrabrush property from the button object, it just get value from the setter in template. So if you want to init this extrabrush, you could use static resource instead. Code like following.

    XAML in app.xaml

    <Application
        x:Class="App1.App"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:App1">   
        <Application.Resources>
            <SolidColorBrush Color="Black" x:Key="lg1"></SolidColorBrush>
        </Application.Resources>
    </Application>

    XAML in template

     <Setter Property="ExtraBrush" Value="{StaticResource lg1}"></Setter>

    If you still have questions, please feel free to let me know.

    Regards,


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place. Click HERE to participate the survey.

    • Marked as answer by T Hofmann Friday, October 17, 2014 5:04 PM
    Thursday, October 16, 2014 2:34 PM
    Moderator
  • Hi Herro,

    Thanks for your explanation. I solved it by creating 2 styles, one deriving from the other but overriding ExtraBrush (and any other added brushes I have). I can then select the style through bindings in my ViewModel.

    It's too bad that I can't bind to the actual ExtraBrush property, as that would have been easier to set/control. But it still work very well.

    Best Regards,

    Tomas Hofmann

    Friday, October 17, 2014 5:02 PM