zoukankan      html  css  js  c++  java
  • [UWP 自定义控件]了解模板化控件(5):VisualState

    1. 功能需求

    使用TemplatePart实现上篇文章的两个需求(Header为空时隐藏HeaderContentPresenter,鼠标没有放在控件上时HeaderContentPresent半透明),虽然功能已经实现,但这样实现的话基本上也就别想扩展了。譬如开发者做不到通过继承或修改ControlTemplate实现如下功能:

    • 半透明时的Opacity不是0.7,而是0.5。
    • 半透明和不透明之前切换时有渐变动画。

    当然也并不是不可以用代码实现这些需求,只是会复杂很多。大部分的开发者都是对C#熟悉,对XAML陌生,很容易就选择尽量使用C#实现全部功能,将所有功能集中在同一个地方并用熟悉的语言处理,当然也有这样做的优点,不过既然在用XAML平台,就应该尽可能利用XAML平台UI和代码分离的优点。

    这篇文章用ContentView2示例讲解VisualState如何实现上述的需求,ContentView2和上篇文章的ContentView一样继承自HeaderedContentControl。

    2. VisualState

    在实现需求前首先解释VisualState的概念。

    VisualState 指定控件处于特定状态时的外观。控件的代码指定控件处于何种状态,控件的ControlTemplate中根节点包含VisualStateManager.VisualStateGroups附加属性,并在其中确定各个VisualState的外观。

    以CheckBox为例,CheckBox基本上包含Unchecked、Checked、Indeterminate三种状态,它通过IsChecked的值在这三种状态中转换。

    这三种状态的外观如下所示:

    实际上Checkbox的VisualState复杂很多,这里是简化的模型。

    3. 确定VisualState

    要使用VisualState,首先要明确控件中包含哪些VisualState。在ContentView2中有两组VisualState:

    • CommonStates: 默认是“Normal”,当鼠标进入控件时是“PointerOver”。
    • HeaderStates: 默认是“NoHeader”,当Header属性的值不为空时是“HasHeader”。

    其中“CommonStates”、“HeaderStates”称为VisualStateGroup,“Normal”、“PointerOver”等称为VisualState。在同一个VisualStateGroup中的VisualState是互斥的,控件始终只能处于每组状态中的一种。例如,控件只能处于NoHeader状态,或者HasHeader状态。

    模板化控件可以使用TemplateVisualStateAttribute协定声明它的VisualState,用于通知控件的使用者有这些VisualState可用。TemplateVisualStateAttribute是可选的,而且就算控件声明了这些VisualState,ControlTemplate也可以不包含它们中的任何一个,并且不会引发异常。

    ContentView2的TemplateVisualStateAttribute如下:

    [TemplateVisualState(Name = NormalState, GroupName = CommonStates)]
    [TemplateVisualState(Name = PointerOverState,GroupName =CommonStates)]
    [TemplateVisualState(Name = NoHeaderState, GroupName = HeaderStates)]
    [TemplateVisualState(Name = HasHeaderState, GroupName = HeaderStates)]
    public class ContentView2 : HeaderedContentControl
    {
        public const string CommonStates = "CommonStates";
        public const string NormalState = "Normal";
        public const string PointerOverState = "PointerOver";
    
        public const string HeaderStates = "HeaderStates";
        public const string NoHeaderState = "NoHeader";
        public const string HasHeaderState = "HasHeader";
    
    
    }
    

    4. VisualStateManager

    VisualStateManager用于管理VisualState并操作它们之间的转换。

    public ContentView2()
    {
        this.DefaultStyleKey = typeof(ContentView2);
    }
    
    private bool _isPointerEntered;
    
    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        UpdateVisualState(false);
    }
    
    protected override void OnPointerEntered(PointerRoutedEventArgs e)
    {
        base.OnPointerEntered(e);
        _isPointerEntered = true;
        UpdateVisualState();
    }
    
    protected override void OnPointerExited(PointerRoutedEventArgs e)
    {
        base.OnPointerExited(e);
        _isPointerEntered = false;
        UpdateVisualState();
    }
    
    protected override void OnHeaderChanged(object oldValue, object newValue)
    {
        base.OnHeaderChanged(oldValue, newValue);
        UpdateVisualState();
    }
    
    internal virtual void UpdateVisualState(bool useTransitions = true)
    {
        if (_isPointerEntered)
            VisualStateManager.GoToState(this, PointerOverState, useTransitions);
        else
            VisualStateManager.GoToState(this, NormalState, useTransitions);
    
        if (Header == null)
            VisualStateManager.GoToState(this, NoHeaderState, useTransitions);
        else
            VisualStateManager.GoToState(this, HasHeaderState, useTransitions);
    }
    
    

    ContentView2的其它代码如上所示,在OnApplyTemplate、OnHeaderChanged及鼠标进入离开时使用VisualStateManager.GoToState(Control control, string stateName,bool useTransitions)更新VisualState。useTransitions这个参数指示是否使用 VisualTransition 进行状态过渡,简单来说即是VisualState之间切换时用不用VisualTransition里面定义的动画。

    注意OnApplyTemplate中的这句代码:UpdateVisualState(false)。控件在加载ControlTemplate时就需要确定它的状态,一般这时候都不会使用过渡动画。

    VisualStateManager.GoToState不会使控件重复进入某个状态,譬如如果控件已处于PointerOverState,再次调用VisualStateManager.GoToState(this, PointerOverState, useTransitions)不会触发任何操作,也不会打断正在执行的过渡动画或重复触发动画。

    到这里为止ContentView2.cs的工作已经完成,接下来就是XAML的责任了。

    5. 使用Blend编辑ControlTemplate

    使用Blend编辑ContentView2的空白ControlTemplate时,由于已经声明了TemplateVisualStateAttribute,可以看到在“状态”窗口已经默认就有定义好的状态。

    编辑后结果如下:

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="CommonStates">
            <VisualStateGroup.Transitions>
                <VisualTransition GeneratedDuration="0:0:0.5">
                    <VisualTransition.GeneratedEasingFunction>
                        <CubicEase EasingMode="EaseInOut" />
                    </VisualTransition.GeneratedEasingFunction>
                </VisualTransition>
            </VisualStateGroup.Transitions>
            <VisualState x:Name="Normal">
                <VisualState.Setters>
                    <Setter Target="HeaderContentPresenter.(UIElement.Opacity)"
                            Value="0.5" />
                </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="PointerOver">
                <VisualState.Setters>
                    <Setter Target="HeaderContentPresenter.(UIElement.Opacity)"
                            Value="1" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
        <VisualStateGroup x:Name="HeaderStates">
            <VisualState x:Name="NoHeader">
                <VisualState.Setters>
                    <Setter Target="HeaderContentPresenter.(UIElement.Visibility)"
                            Value="Collapsed" />
                </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="HasHeader" />
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    
    
    

    从XAML中可以看出VisualState子节点的Setter是关键所在,如PointerOver的VisualState通过Setter将HeaderContentPresenter的Opacity更改为1,满足了“当鼠标移动到控件控件上时,设置Header的Opacity=1”这个需求。

    另外,VisualStateGroup.Transitions 节点定义了CommonStates在各个状态之间切换时的过渡动画。VisualStateManager.GoToState(this, PointerOverState, useTransitions) 中的参数useTransitions即是控制是否使用过渡动画。示例中使用的过渡动画为CubicEase,过渡时间为0.5秒。

    需要注意的是不同VisualStateGroup之间尽量不要对同一个UI元素的同一个属性进行操作,否则会引起冲突。

    这个主题不会详细讲解使用Blend修改VisualState,因为那会占用很多篇幅。幸好Blend在这方面做得很容易上手,而且多年来基本操作都没有变过,可以在网上找到很多这方面的文章。

    6. 结论

    很多时候VisualState方式并不会比TemplatePart方式少写代码,譬如ContentView2的代码量就基本和ContentView一致,而XAML行数还更多。但VisualState的实现方式更灵活,更加符合UI与代码分离原则及开放封闭原则。

  • 相关阅读:
    Aizu 0033
    Aizu 0118
    【思维】贪心+细节——cf1361B
    【思维】构造+凸包+向量叉积——LEETCODE 游乐园的迷宫
    【思维】三元环计数+鸽笼定理/贪心——LEETCODE 游乐园的游览计划 好题
    dp+线性筛——LEETCODE切分数组
    【经典】带障碍的铺砖块——LEETCODE 覆盖
    【思维】树形dp+构造——leetcode二叉树任务调度
    【思维】状压dp—— 2020 联想杯 M
    【思维】建图+排列组合+预处理+最短路—— 2020 联想杯 E
  • 原文地址:https://www.cnblogs.com/dino623/p/VisualState.html
Copyright © 2011-2022 走看看