I have created a custom control and I want to change the visualstate of this control immediately after the control is loaded.
I want to use binding mode to get colors from dependency properties so i can set them dynamically.
I've found a way to binding the properties and avoid the limitation of freezable properties: separate storyboards and make them resources, as you can see in xaml code.
Changing the visualstate (to "Activated" in my case) works correctly and it uses the values from the bound properties, but problems starts when I want to change the state immediately after the control is loaded: binding becomes "broken" and the colors obtained from properties seems to be "null" (or transparent).
If i use a static color instead to bind a property (e.g. Green or #ffffffff) the color is loaded correctly even immediately after loading, but this way is not usable in my case because this means that the color will be "unchangeable".
xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1">
<Style TargetType="{x:Type local:Switcher}">
<Setter Property="Background" Value="#FF3F3F46"/>
<Setter Property="BackgroundOnActivated" Value="#FF2D2D30"/>
<Setter Property="IsActivated" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Switcher}">
<Grid>
<Grid.Resources>
<Storyboard x:Key="SwitcherOnActivated">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Border"
Storyboard.TargetProperty="Background">
<!-- with Value="Green" animation works correctly -->
<DiscreteObjectKeyFrame KeyTime="0"
Value="{Binding BackgroundOnActivated,
RelativeSource={RelativeSource TemplatedParent}}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Grid.Resources>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<!-- it goes to "Activated" when IsActivated becomes true-->
<VisualState x:Name="Activated"
Storyboard="{StaticResource SwitcherOnActivated}"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="PART_Border"
Background="{TemplateBinding Background}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
code behind
public class Switcher : Control
{
public Brush BackgroundOnActivated
{
get => (Brush)GetValue(BackgroundOnActivatedProperty);
set => SetValue(BackgroundOnActivatedProperty, value);
}
public static readonly DependencyProperty BackgroundOnActivatedProperty =
DependencyProperty.Register(nameof(BackgroundOnActivated), typeof(Brush), typeof(Switcher));
public bool IsActivated
{
get => (bool)GetValue(IsActivatedProperty);
set => SetValue(IsActivatedProperty, value);
}
public static readonly DependencyProperty IsActivatedProperty =
DependencyProperty.Register(nameof(IsActivated), typeof(bool), typeof(Switcher),
new PropertyMetadata(false, new PropertyChangedCallback(OnIsActivatedChanged)));
static Switcher()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Switcher), new FrameworkPropertyMetadata(typeof(Switcher)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
/*OnApplyTemplate() is used to apply the correct initial
visualstate after loading the control (but it seems to be broken)*/
_ = VisualStateManager.GoToState(this, IsActivated ? "Activated" : "Normal", true);
}
protected virtual void OnActivationChanged()
{
/*If the boolean value of IsActivated is changed, the visualstate
is switched between "Normal" and "Activated"*/
_ = VisualStateManager.GoToState(this, IsActivated ? "Activated" : "Normal", true);
}
private static void OnIsActivatedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Switcher)d).OnActivationChanged();
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
//When I click on the control the boolean value of the property
IsActivated is inverted and this will call OnIsActivatedChanged()*/
base.OnMouseLeftButtonDown(e);
IsActivated = !IsActivated;
}
}
Try to add to MainWindow.xaml my control and set the property IsActivated to true, so after loaded the state has to become Activated, but this breaks the control and it becomes invisible.
<local:Switcher x:Name="switcher" HorizontalAlignment="Left" VerticalAlignment="Top"
Margin="50,50,0,0" Height="100" Width="100" IsActivated="True"/>
The control on its normal state:
What I want to get:
What I actually get:
Any solutions / workaround to get the correct colors even if the visualstate is changed immediately after loading?
Have you thought about using ControlTemplate.Triggers instead? It's much simpler to achieve what you want:
If you absolutely need to use the
VisualStateManager
, then you'll need to use separateBorder
controls and control theirVisibility
. The reason why your original idea doesn't work is due to theStoryboard
s being frozen at runtime.