我不理解VisualStateGroup
的基本内容。我读过的所有内容都让我相信它们是正交的。也就是说,一个组中的状态更改不会影响其他组。实际上,如果情况并非如此,那么它们将毫无意义。
然而,为了理解我遇到的一些奇怪的行为,我把一个简单的例子放在一起,表明一个组中的状态变化可以触发另一个组中的动画。我试图理解这是怎么回事。
所有我想要的是一个基于ToggleButton
的控件,在切换(IsChecked == true
)时有一个外观,例如,无论它是否有焦点或鼠标是否在它上面。
首先,我有一个非常简单的控件,可以在自定义组中自定义状态之间进行转换:
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
[TemplateVisualState(Name = normalState, GroupName = activationGroupName)]
[TemplateVisualState(Name = hoverState, GroupName = activationGroupName)]
[TemplateVisualState(Name = activatedState, GroupName = activationGroupName)]
public class CustomControl : ToggleButton
{
private const string activationGroupName = "Activation";
private const string normalState = "Normal";
private const string hoverState = "Hover";
private const string activatedState = "Activated";
public CustomControl()
{
this.DefaultStyleKey = typeof(CustomControl);
this.Checked += delegate
{
this.UpdateStates();
};
this.Unchecked += delegate
{
this.UpdateStates();
};
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.UpdateStates();
}
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
this.UpdateStates();
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
this.UpdateStates();
}
private void UpdateStates()
{
var state = normalState;
if (this.IsChecked.HasValue && this.IsChecked.Value)
{
state = activatedState;
}
else if (this.IsMouseOver)
{
state = hoverState;
}
VisualStateManager.GoToState(this, state, true);
}
}
其次,我有一个用于此控件的模板,它根据当前状态更改背景颜色:
<Style TargetType="local:CustomControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomControl">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Activation">
<VisualStateGroup.Transitions>
<VisualTransition To="Normal" GeneratedDuration="00:00:0.2"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal"/>
<VisualState x:Name="Hover">
<Storyboard>
<ColorAnimation Duration="00:00:0.2" To="Red" Storyboard.TargetName="grid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Activated">
<Storyboard>
<ColorAnimation Duration="00:00:0.2" To="Blue" Storyboard.TargetName="grid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="grid" Background="White">
<TextBlock>ToggleButton that should be white normally, red on hover, and blue when checked</TextBlock>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
在大多数情况下它都有效,但是当控制失去焦点时,它会转换回正常状态。
我可以通过在我的控件中明确处理焦点更改并制定状态更新来解决这个问题:
public CustomControl()
{
this.DefaultStyleKey = typeof(CustomControl);
this.Checked += delegate
{
this.UpdateStates();
};
this.Unchecked += delegate
{
this.UpdateStates();
};
// explicitly handle focus changes
this.GotFocus += delegate
{
this.UpdateStates();
};
this.LostFocus += delegate
{
this.UpdateStates();
};
}
然而,对我来说,为什么我必须这样做是没有意义的。
为什么一个VisualStateGroup
状态发生变化导致另一个人定义的动画执行?对我来说,实现既定目标的最简单,最正确的方法是什么?
由于
答案 0 :(得分:2)
请注意GotoState
方法如何简单地使用州名,而不知道它属于哪个组。虽然这些组将控件可以在任何时间保持的状态集(每组一个状态)分开,但状态名称在管理器中的所有状态中都是唯一的。
另请注意,您派生的ToggleButton
具有与该类关联的以下属性: -
[TemplateVisualStateAttribute(Name = "Normal", GroupName = "CommonStates")]
这表明你继承的逻辑在某些时候会调用GotoState
状态为“Normal”。通常,控件只有一个UpdateStates
方法(就像你拥有的那样),并且在任何状态可能已经改变的任何时候,它都会被调用。此函数将依次使用每个组中的一个州名称多次调用GotoState
。因此,当控件失去焦点时,ToggleButton
调用GotoState
来设置“未聚焦”状态,但同时也将调用“正常”和“已检查”或“未选中”状态。
现在您已经拥有自己的模板,其中“未聚焦”,“已选中”状态不存在,因此来自GotoState
的{{1}}来电不会执行任何操作。但是你有“正常”。 VSM无法知道ToggleButton
使用“Normal”是为了从默认模板的“CommonStates”组中进行选择。相反,它只会找到一个名为“Normal”的ToggleButton
,在这种情况下可以在“Activiation”组中找到。因此,您现有的“已激活”状态将替换为“正常”。
将“正常”更改为“Blah”,事情按预期工作。
(有助于记住,尽管他们出现在VisualState
中,但是他们并没有真正做很多事情,他们唯一可以想到的就是在Blend UI中。