【问题标题】:Inheritance of VisualStateGroup / VisualState in a XAML Style / base classXAML 样式/基类中 VisualStateGroup/VisualState 的继承
【发布时间】:2018-07-16 02:05:09
【问题描述】:
  • 我有一个基类SecurePage,它继承自UserControl
  • 应用程序内部的每个“页面”都继承自 SecurePage
  • 我想在SecurePage 的默认Style 中定义一个VisualStateGroup 和一些VisualStates。

问题是,在派生类中没有这些VisualStates 可用。

var states = VisualStateManager.GetVisualStateGroups(this);

返回一个空列表。

如果我复制我的XAMLVisualState 定义并将其粘贴到我的DerivadedFooSecurePage 中,我可以轻松进入状态:

VisualStateManager.GoToState(this, "Blink", false);

与此处描述的问题相同:VisualState in abstract control


更多细节

安全页面

[TemplateVisualState(GroupName = "State", Name = "Normal")]
[TemplateVisualState(GroupName = "State", Name = "Blink")]
public class SecurePage: UserControl
{
    public SecurePage()
    {
        DefaultStyleKey = typeof(HtSecurePage);
    }
}

<Style TargetType="basic:SecurePage">
    <Setter Property="FontSize" Value="14"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="basic:SecurePage">
                <Grid>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="Signals">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="Blink">
                                <Storyboard>
                                    <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="border">
                                        <EasingColorKeyFrame KeyTime="0:0:0.4" Value="#FF3AFF00">
                                            <EasingColorKeyFrame.EasingFunction>
                                                <BounceEase EasingMode="EaseIn" Bounciness="3" Bounces="4"/>
                                            </EasingColorKeyFrame.EasingFunction>
                                        </EasingColorKeyFrame>
                                    </ColorAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <ContentPresenter Content="{TemplateBinding Content}"/>
                    <Border 
                        x:Name="border"
                        BorderThickness="5"
                        BorderBrush="Transparent"
                        IsHitTestVisible="False"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

信息页

Info.xaml.cs

namespace Views.General
{
    public partial class Info
    {
        public Info()
        {
            InitializeComponent();
        }
    }
}

Info.xaml

<basic:SecurePage
    x:Class="Views.General.Info"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:basic="clr-namespace:Foo.PlcFramework.Controls.Basic;assembly=Foo"
    FontSize="14">
    <Grid>
        <TextBlock Text="HelloWorld"/>
    </Grid>
</basic:SecurePage>

实时调试

  • states = 0
  • 调用VisualStateManager.GoToState(this, "Blink", false); 后没有任何反应

  • states = 0
  • 调用VisualStateManager.GoToState(this, "Blink", false); 后没有任何反应

将 VisualState 复制到派生类中

namespace Views.General
{
    [TemplateVisualState(GroupName = "State", Name = "Normal")]
    [TemplateVisualState(GroupName = "State", Name = "Blink")]
    public partial class Info
    {
        public Info()
        {
            InitializeComponent();
            var states = VisualStateManager.GetVisualStateGroups(this);
            VisualStateManager.GoToState(this, "Blink", false);
        }
    }
}

<basic:SecurePage 
    x:Class="Views.General.Info"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:basic="clr-namespace:Foo.PlcFramework.Controls.Basic;assembly=Foo"
    FontSize="14">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="Signals">
                <VisualState x:Name="Normal"/>
                <VisualState x:Name="Blink">
                    <Storyboard>
                        <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="border">
                            <EasingColorKeyFrame KeyTime="0:0:0.4" Value="#FF3AFF00">
                                <EasingColorKeyFrame.EasingFunction>
                                    <BounceEase EasingMode="EaseIn" Bounciness="3" Bounces="4"/>
                                </EasingColorKeyFrame.EasingFunction>
                            </EasingColorKeyFrame>
                        </ColorAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <TextBlock Text="HelloWorld"/>
        <Border 
            x:Name="border"
            BorderThickness="5"
            BorderBrush="Transparent"
            IsHitTestVisible="False"/>
    </Grid>
</basic:SecurePage >

  • states = 0
  • 调用VisualStateManager.GoToState(this, "Blink", false);后状态改变!!

我只想在SecurePageXAML Style定义中定义状态,然后去任何派生类中的状态!

【问题讨论】:

    标签: c# wpf xaml expression-blend visualstatemanager


    【解决方案1】:

    诊断

    经过一番摸索,我终于找到了罪魁祸首——它是UserControl 本身。更准确地说 - overridden StateGroupsRoot property,由 VisualStateManager.GoToState 方法使用。通常,它返回控件模板的根元素,但在UserControl 的情况下,它返回UserControl.Content 属性的值。因此,当您调用 GoToState 时,您的模板中定义的状态不会被考虑在内。

    解决方案

    这个问题至少有两种解决方案。

    第一个解决方案是从 ContentControl 而不是 UserControl 派生您的基类 (SecurePage)。后者并没有太大的不同——它默认FocusableIsTabStop 属性为falseHorizontanContentAlignmentVerticalContentAlignmentStretch。此外,除了覆盖提到的StateGroupsRoot 属性外,它还提供了自己的AutomationPeerUserControlAutomationPeer),但我认为您不必担心这一点。

    第二种解决方案是在模板根目录上使用VisualStateManager.GoToElementState。例如:

    public class SecurePage : UserControl
    {
        //Your code here...
    
        private FrameworkElement TemplateRoot { get; set; }
    
        public override void OnApplyTemplate()
        {
            if (Template != null)
                TemplateRoot = GetVisualChild(0) as FrameworkElement;
            else
                TemplateRoot = null;
        }
    
        public bool GoToVisualState(string name, bool useTransitions)
        {
            if (TemplateRoot is null)
                return false;
            return VisualStateManager.GoToElementState(TemplateRoot, name, useTransitions);
        }
    }
    

    其他注意事项

    在您的控件上调用VisualStateManager.GetVisualStateGroups 会产生一个空列表,因为它只是一个普通的附加依赖属性访问器,并且您没有在控件上设置1该属性。要获取您在模板中定义的组,您应该调用它并将模板根作为参数传递。根据同样的原则,您不希望在您的控件上调用 Grid.GetColumn 来返回您在模板中某处设置的值。

    关于在控件的构造函数中调用GoToState - 它很可能不会工作,因为您的控件只是被实例化,而且很可能它的Templtate 为空(记住你定义模板内的视觉状态)。最好将该逻辑移至 OnApplyTemplate 覆盖。


    1 由于VisualStateManager.VisualStateGroupsProperty 是只读的,通过 set 我的意思是向VisualStateManager.GetVisualStateGroups 返回的列表中添加项目

    【讨论】:

    • 感谢您的解释!
    【解决方案2】:

    我厌倦了重新生成您提到的场景,并且我能够从基类获取VisualStateGroup的列表。这是我实现的示例。

    首先,您需要获取当前基类的VisualStateGroup,然后将自定义创建的组添加到其中。

    基类

    public partial class SecurePage : UserControl
    {
        public VisualStateGroup vsGroup = new VisualStateGroup();
    
        public SecurePage()
        {
            System.Collections.IList groups = VisualStateManager.GetVisualStateGroups(this);
    
    
            VisualState state1 = new VisualState() { Name = "State1" };
    
            Storyboard sb = new Storyboard();
            DoubleAnimation anim = new DoubleAnimation(1, 0, TimeSpan.FromSeconds(1.0));
            Storyboard.SetTargetProperty(anim, new PropertyPath(FrameworkElement.OpacityProperty));
            sb.AutoReverse = true;
            sb.Children.Add(anim);
    
            state1.Storyboard = sb;
    
            vsGroup.States.Add(state1);
    
            groups.Add(vsGroup);
        }
    }
    

    派生类

    派生类的根是基类的类型。请记住,一定要改变它。 XAML 文件的第一行。检查下面。

    <UserControl x:Class="WPFTest.SecurePage"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
          xmlns:local="clr-namespace:WPFTest"
          mc:Ignorable="d" 
          d:DesignHeight="300" d:DesignWidth="300">
    
        <Grid>
            <Rectangle x:Name="rect" Height="100" Width="100" Fill="Red" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        </Grid>
    </UserControl>
    
    public partial class DerivadedFooSecurePage : SecurePage
    {
        public DerivadedFooSecurePage()
        {
            InitializeComponent();
    
            var states = VisualStateManager.GetVisualStateGroups(this);
    
            //VisualStateManager.GoToElementState(this.rect, "State1", false);
        }
    } 
    

    进一步可以在任何窗口或其他用户控件上实现DerivadedFooSecurePage

    【讨论】:

    • 感谢您的回复! 我想在基类中定义 VisualStates 并在派生类中使用它。在后面的代码中定义它是没有解决方案的!我添加了更多细节来理解我的问题
    【解决方案3】:

    解决方法

    我为每个可用的VisualState 定义了一个enum 值。

    public enum ESecurePageVisualState
    {
        Normal,
        Blink
    }
    

    SecurePage 类中添加了DependencyProperty

    /// <summary>
    /// Set the visual State of the page
    /// </summary>
    public ESecurePageVisualState VisualState
    {
        get => (ESecurePageVisualState)GetValue(VisualStateProperty);
        set => SetValue(VisualStateProperty, value);
    }
    
    /// <summary>
    /// The <see cref="VisualState"/> DependencyProperty.
    /// </summary>
    public static readonly DependencyProperty VisualStateProperty = DependencyProperty.Register("VisualState", typeof(ESecurePageVisualState), typeof(SecurePage), new PropertyMetadata(ESecurePageVisualState.Normal));
    

    并更改了SecurePageStyle

    <Style TargetType="basic:SecurePage">
        <Setter Property="FontSize" Value="14"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="basic:SecurePage">
                    <Grid x:Name="Grid">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="Signals">
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="Blink">
                                    <Storyboard>
                                        <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="border">
                                            <EasingColorKeyFrame KeyTime="0:0:0.4" Value="#FF3AFF00">
                                                <EasingColorKeyFrame.EasingFunction>
                                                    <BounceEase EasingMode="EaseIn" Bounciness="3" Bounces="4"/>
                                                </EasingColorKeyFrame.EasingFunction>
                                            </EasingColorKeyFrame>
                                        </ColorAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <ContentPresenter Content="{TemplateBinding Content}"/>
                        <Border 
                            x:Name="border"
                            BorderThickness="5"
                            BorderBrush="Transparent"
                            IsHitTestVisible="False"/>
                        <i:Interaction.Triggers>                            
                            <ei:DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=basic:SecurePage}, Path=VisualState}" Value="{x:Static basic:ESecurePageVisualState.Normal}">
                                <ei:GoToStateAction StateName="Normal" TargetName="Grid" TargetObject="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}}"/>
                            </ei:DataTrigger>
                            <ei:DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=basic:SecurePage}, Path=VisualState}" Value="{x:Static basic:ESecurePageVisualState.Blink}">
                                <ei:GoToStateAction StateName="Blink" TargetName="Grid" TargetObject="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}}"/>
                            </ei:DataTrigger>
                        </i:Interaction.Triggers>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    现在我可以通过属性更改VisualState

    public partial class Info
    {
        public Info()
        {
            InitializeComponent();
            Loaded += (sender, args) =>
            {
                VisualState = ESecurePageVisualState.Blink;
            };
        }       
    }
    

    如果您找到更好的解决方案,请告诉我!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-23
      • 1970-01-01
      • 2016-04-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多