【问题标题】:VisualStateGroup Triggering Animation in Another VisualStateGroupVisualStateGroup 在另一个 VisualStateGroup 中触发动画
【发布时间】:2010-07-12 18:02:14
【问题描述】:

VisualStateGroups 有一些基本的东西我不理解。我读过的所有内容都让我相信它们是正交的。也就是说,一个组中的状态更改不会影响其他组。事实上,如果不是这种情况,它们将毫无意义。

但是,为了了解我遇到的一些奇怪行为,我整理了一个简单的示例,说明一个组中的状态变化可以触发另一个组中的动画。我试图了解这是怎么回事。

我只想要一个基于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 的状态变化会导致另一个定义的动画执行?我实现既定目标的最简单、最正确的方法是什么?

谢谢

【问题讨论】:

    标签: silverlight silverlight-4.0 visualstatemanager visualstategroup


    【解决方案1】:

    请注意GotoState 方法如何简单地获取州名,而没有任何关于它属于哪个组的限定。虽然组将控件可以在任何时间保持的状态集(每组一个状态)分开,但状态名称在管理器中的所有状态中应该是唯一的。

    另请注意,您从中派生的ToggleButton 具有与该类关联的以下属性:-

    [TemplateVisualStateAttribute(Name = "Normal", GroupName = "CommonStates")]
    

    这表明您继承的逻辑将在某个时候调用GotoState,状态为“正常”。通常,控件只有一个 UpdateStates 方法(就像您拥有的那样),并且在 any 状态可能发生变化的任何时候都会调用它。该函数将依次调用GotoState 多次,每个组都有一个状态名称。因此,当控件失去焦点时,ToggleButton 会调用 GotoState 来设置“未聚焦”状态,但同时也会使用“正常”和“已检查”或“未检查”来调用它。

    现在您已经有了自己的模板,其中不存在“未聚焦”、“已检查”状态,因此从 ToggleButtonGotoState 的调用不会执行任何操作。但是,您确实有“正常”。 VSM 无法知道ToggleButton 使用“Normal”是为了从其默认模板的“CommonStates”组中进行选择。相反,它只会找到一个名为“Normal”的VisualState,在这种情况下,它位于您的“激活”组中。因此,您现有的“已激活”状态将替换为您的“正常”。

    将您的“正常”更改为“废话”,事情就会如您所愿。

    (请记住,尽管组名出现在 TemplateVisualStateAttribute 中,但它们的作用并不大,我能想到的唯一用途是在 Blend UI 中)

    【讨论】:

    • 谢谢安东尼。我实际上尝试使用一个唯一的州名——我称之为Normal2——在发布我的问题之后,我可以确认它有效。在我看来,事情仍然“破碎”,因为我不能使用内置状态,因为底层控制(在这种情况下为ToggleButton)正在切换状态,而没有任何拦截这些更改的能力。因此,尝试使用内置状态会导致视觉异常,因为当我不想这样做时,底层控件会切换状态。
    • 这太痛苦了,Silverlight 不允许在样式中使用触发器。这样就不需要VSM了。 Kent,我想用 ToggleButton 实现完全相同的目标,想知道自上次发帖以来您是否最终实现了目标?
    • @Kave:也许我偏向于在 WPF 之前使用 Silverlight 进行开发,但我自己更喜欢 VSM 模型而不是触发器。它提供了一个抽象层,这对我来说比将 Xaml 中描述的 UI 与特定事件如此紧密地集成更有意义。但是,我可以看到 WPF 开发人员失去触发器的灵活性是多么令人沮丧。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-08
    相关资源
    最近更新 更多