问题描述
我已经创建了一个自定义控件,我想在控件加载后立即更改其视觉状态。
我想使用绑定模式从依赖项属性中获取颜色,以便可以动态设置它们。
我找到了一种绑定属性并避免可冻结属性的限制的方法:将故事板分开并为其提供资源,如您在xaml代码中所见。
更改视觉状态(在我的情况下为“已激活”)可以正常工作,并且使用绑定属性中的值,但是当我想在加载控件后立即更改状态时,问题就开始了 :绑定变为“断开”,并且从属性获得的颜色似乎为“空”(或透明)。
如果我使用静态颜色代替绑定属性(例如Green或#ffffffff),即使在加载后立即也可以正确加载颜色,但是这种方式在我的情况下不可用,因为这意味着颜色将“不可更改” “。
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>
背后的代码
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,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;
}
}
尝试将我的控件添加到MainWindow.xaml并将属性IsActivated设置为true,因此在加载后状态必须变为Activated,但这会破坏控件并使它变得不可见。
<local:Switcher x:Name="switcher" HorizontalAlignment="Left" VerticalAlignment="Top"
Margin="50,50,0" Height="100" Width="100" IsActivated="True"/>
处于正常状态的控件:
我想要得到什么:
我实际上得到的是:
是否有任何解决方案/解决方法来获得正确的颜色,即使加载后立即改变了视觉状态?
解决方法
您是否考虑过使用ControlTemplate.Triggers?实现您想要的东西要简单得多:
<Style TargetType="{x:Type local:Switcher}">
<Setter Property="Background" Value="#FF000000" />
<Setter Property="BackgroundOnActivated" Value="#FFFF0000" />
<Setter Property="IsActivated" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Switcher}">
<Grid>
<Border x:Name="PART_Border"
Background="{TemplateBinding Background}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsActivated" Value="True">
<Setter TargetName="PART_Border" Property="Background" Value="{Binding BackgroundOnActivated,RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
如果您绝对需要使用VisualStateManager
,则需要使用单独的Border
控件并控制其Visibility
。您最初的想法不起作用的原因是Storyboard
在运行时被冻结了。
<Style TargetType="{x:Type local:Switcher}">
<Setter Property="Background" Value="#FF000000" />
<Setter Property="BackgroundOnActivated" Value="#FFFF0000" />
<Setter Property="IsActivated" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Switcher}">
<Grid>
<Border x:Name="PART_Border"
Background="{TemplateBinding Background}" />
<Border x:Name="ActivatedBorder"
Background="{TemplateBinding BackgroundOnActivated}"
Visibility="Collapsed" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Activated">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="PART_Border"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="ActivatedBorder"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>