关于VisualStateGroups,有一些基本的东西我不理解。我读到的所有东西都让我相信它们是正交的。也就是说,一个组中的状态更改不会影响其他组。事实上,如果不是这样的话,它们将是相当没有意义的。
然而,为了理解我遇到的一些奇怪的行为,我放了一个简单的例子,说明一个组中的状态变化可以触发另一个组中的动画。我想知道这是怎么回事。
例如,我想要的是ToggleButton-based控件在切换(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中的状态更改会导致执行另一个定义的动画?什么是实现我的既定目标的最简单、最正确的方法?
谢谢
发布于 2010-07-13 20:52:23
请注意,GotoState方法只接受状态名称,而不对其所属的组进行任何限定。虽然这两个组分离了控件可以在任何时候保持的状态集(每个组一个状态),但是期望状态名称在管理器中的所有状态中都是唯一的。
另请注意,从中派生的ToggleButton具有与类关联的以下属性:-
[TemplateVisualStateAttribute(Name = "Normal", GroupName = "CommonStates")]这表明您继承的逻辑将在某个时刻调用状态为"Normal“的GotoState。通常,控件只有一个UpdateStates方法(如您所拥有的),并且在状态的任何可能发生更改的任何点上,都会调用它。此函数反过来将多次调用GotoState,每个组中有一个状态名称。因此,当控件失去焦点时,ToggleButton调用GotoState来设置“未聚焦”状态,但同时也会使用"Normal“和"Checked”或"Unchecked“调用它。
现在,您已经有了自己的模板,其中不存在“未聚焦”、“选中”状态,因此ToggleButton对GotoState的调用不会执行任何操作。然而,你确实有“正常”。VSM无法知道ToggleButton使用"Normal“是为了从其默认模板的"CommonStates”组中进行选择。相反,它只是找到一个名为"Normal“的VisualState,在这种情况下,它是在您的”活动“组中找到的。因此,您现有的“激活”状态将替换为您的“正常”状态。
把你的“正常”改成"Blah“,事情就会如你所愿。
(这有助于记住,尽管组名出现在TemplateVisualStateAttribute中,但它们实际上并不能做很多事情,我能想到的唯一用途是在Blend UI中)
https://stackoverflow.com/questions/3230907
复制相似问题