1

关于VisualStateGroup有一些基本的东西我不理解。我读过的所有东西都让我相信它们是正交的。也就是说,一个群体的状态变化不会影响其他群体。事实上,如果情况并非如此,那么他们就会毫无意义。VisualStateGroup在另一个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中的状态改变会导致由另一个定义的动画执行?对我来说,实现既定目标的最简单,最正确的方法是什么?

感谢

回答

2

注意如何GotoState方法简单地以国家名义而没有明确规定它属于哪个组。虽然这些组将控制可以在任何时间保持的状态集合(每个组的一个状态)分开,但状态名称在管理器中的所有状态中都是唯一的。

还请注意,您从中获得具有与类相关的下列属性ToggleButton: -

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

这表明您已经继承逻辑会在某个时候调用GotoState与状态“正常” 。通常控件只有一个UpdateStates方法(就像你一样),并且在任何一个状态下,状态可能已经改变的任何点都会被调用。这个函数反过来会多次调用GotoState,每个组中有一个状态名称。因此,在控制失去焦点的时候,ToggleButton调用GotoState来设置“未聚焦”状态,但与此同时也会用“正常”以及“已选中”或“未选中”来调用它。

现在你已经有了自己的模板,其中状态“未聚焦”,“检查”不存在,所以那些GotoStateToggleButton的呼叫不做任何事情。但是你有“正常”。 VSM无法知道ToggleButton对“Normal”的使用是否打算从其默认模板的“CommonStates”组中选择。相反,它只是找到名为“Normal”的VisualState,在这种情况下,可以在“激活”组中找到该名称。因此,您现有的“已激活”状态将替换为您的“正常”状态。

将您的“Normal”改为“Blah”,事情就像您期望的那样工作。

(它有助于记住,组名并不真正做了很多,尽管他们在TemplateVisualStateAttribute存在,他们只是利用我能想到的是在混合UI)

+0

感谢安东尼。我实际上尝试使用一个独特的国家名称 - 我称之为'Normal2' - 在发布我的问题后,可以确认它的工作。在我看来,事情仍然“破碎”,因为我不能使用内置状态,因为底层控件(在这种情况下为“ToggleButton”)正在切换状态,而没有任何拦截这些更改的能力。因此,试图使用内置状态会导致视觉异常,因为底层控制开关在我不想要时会显示状态。 – 2010-07-13 18:19:02

+0

这是一种令Silverlight在样式中不允许触发器的痛苦。这样就不需要VSM了。肯特,我有完全相同的目标来实现与ToggleButton,并想知道你是否最终实现了你的目标,因为你的上一篇文章? – Houman 2011-05-05 06:46:40

+0

@Kave:也许我偏于WPF之前在Silverlight中开发,但我更喜欢VSM模型来触发自己。它提供了一个抽象层,比Xaml中描述的用户界面与特定事件紧密集成更有意义。不过,我可以看到触发器对WPF开发者的灵活性的损失会令人沮丧。 – AnthonyWJones 2011-05-05 07:21:31