绑定在自定义控件的故事板上不起作用:值变为空
Binding not working on storyboard on custom control: values becomes null
我创建了一个自定义控件,我想在加载该控件后立即更改该控件的视觉状态。
我想使用绑定模式从依赖属性中获取颜色,以便动态设置它们。
我找到了一种方法来绑定属性并避免 可冻结属性的限制:分离故事板并使它们成为资源,如您在 xaml 中所见代码。
更改视觉状态(在我的例子中为“已激活”)工作正常并且它使用绑定属性的值,但是当我想在加载控件后立即更改状态时出现问题 : 绑定变得“损坏”并且从属性中获得的颜色似乎是“空的”(或透明的)。
如果我使用静态颜色而不是绑定 属性(例如绿色或#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, 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;
}
}
尝试添加到 MainWindow.xaml 我的控件并将 属性 IsActivated 设置为 true,因此加载后状态必须变为 Activated,但这会破坏控件并变得不可见。
<local:Switcher x:Name="switcher" HorizontalAlignment="Left" VerticalAlignment="Top"
Margin="50,50,0,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
s 在运行时被冻结了。
<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>
我创建了一个自定义控件,我想在加载该控件后立即更改该控件的视觉状态。
我想使用绑定模式从依赖属性中获取颜色,以便动态设置它们。
我找到了一种方法来绑定属性并避免 可冻结属性的限制:分离故事板并使它们成为资源,如您在 xaml 中所见代码。
更改视觉状态(在我的例子中为“已激活”)工作正常并且它使用绑定属性的值,但是当我想在加载控件后立即更改状态时出现问题 : 绑定变得“损坏”并且从属性中获得的颜色似乎是“空的”(或透明的)。
如果我使用静态颜色而不是绑定 属性(例如绿色或#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, 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;
}
}
尝试添加到 MainWindow.xaml 我的控件并将 属性 IsActivated 设置为 true,因此加载后状态必须变为 Activated,但这会破坏控件并变得不可见。
<local:Switcher x:Name="switcher" HorizontalAlignment="Left" VerticalAlignment="Top"
Margin="50,50,0,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
s 在运行时被冻结了。
<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>