根据dependencyproperty使用动画

本文关键字:动画 dependencyproperty 根据 | 更新日期: 2023-09-27 18:12:57

我终于在WPF中创建了我的第一个自定义控件!

它基本上是一个容器,一旦你把鼠标悬停在它上面,一个新的容器就会从窗口的左边缘滑进来。

我现在试着让它能够选择从上、下、左或右滑动。

我想知道我是否可以在默认模板中实现一个逻辑或一些东西,可以根据属性或史密斯来确定使用哪个动画。如果我有一个属性,我们叫它SlideInFrom它是SlideInFrom = "left"那么我的动画就能很好地做到这一点。我不知道如何实现这样的逻辑!

这是我的完整源代码:

类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace TestCustomControl
{
    [TemplatePart(Name = "SlideContainerClosed", Type = typeof(ContentPresenter)),
    TemplatePart(Name = "SlideContainerExpanded", Type = typeof(ContentPresenter)),
    TemplateVisualState(Name = "Closed", GroupName = "ViewStates"),
    TemplateVisualState(Name = "Expanded", GroupName = "ViewStates")]
    public class SlideGrid : System.Windows.Controls.Control
    {
        public static readonly DependencyProperty ClosedStateProperty = DependencyProperty.Register("ClosedState", typeof(object), typeof(SlideGrid), null);
        public static readonly DependencyProperty ExpandedStateProperty = DependencyProperty.Register("ExpandedState", typeof(object), typeof(SlideGrid), null);
    public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register("IsExpanded", typeof(bool), typeof(SlideGrid), null);
    static SlideGrid()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(SlideGrid), new FrameworkPropertyMetadata(typeof(SlideGrid)));
    }
    public object ClosedState 
    { 
        get 
        { 
            return base.GetValue(ClosedStateProperty); 
        } 
        set
        { 
         base.SetValue(ClosedStateProperty, value); 
        } 
    }
    public object ExpandedState
    {
        get
        {
            return base.GetValue(ExpandedStateProperty);
        }
        set
        {
            base.SetValue(ExpandedStateProperty, value);
        }
    }
    public bool IsExpanded 
    { 
        get 
        { 
            return (bool)base.GetValue(IsExpandedProperty); 
        } 
        set 
        { 
            base.SetValue(IsExpandedProperty, value); 
            ChangeVisualState(true); 
        } 
    }
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        ContentPresenter slideContainerClosed = base.GetTemplateChild("ClosedState") as ContentPresenter;
        if (slideContainerClosed != null) slideContainerClosed.MouseEnter += slideContainerClosed_GotMouseCapture;
        ContentPresenter slideContainerExpanded = base.GetTemplateChild("ExpandedState") as ContentPresenter;
        if (slideContainerExpanded != null) slideContainerExpanded.MouseLeave += slideContainerExpanded_LostMouseCapture;
        this.ChangeVisualState(false);
    }
    private void slideContainerClosed_GotMouseCapture(object sender, MouseEventArgs e)
    {
        this.IsExpanded = true;
    }
    private void slideContainerExpanded_LostMouseCapture(object sender, MouseEventArgs e)
    {
        this.IsExpanded = false;
    }
    private void ChangeVisualState(bool useTransitions)
    {
        if (!this.IsExpanded)
        {
            VisualStateManager.GoToState(this, "Closed", useTransitions);
        }
        else
        {
            VisualStateManager.GoToState(this, "Expanded", useTransitions);
        }
        UIElement closed = ClosedState as UIElement;
        if (closed != null)
        {
            if (IsExpanded)
            {
                closed.Visibility = Visibility.Hidden;
            }
            else
            {
                closed.Visibility = Visibility.Visible;
            }
        }
        UIElement expanded = ExpandedState as UIElement;
        if (expanded != null)
        {
            if (IsExpanded)
            {
                expanded.Visibility = Visibility.Visible;
            }
            else
            {
                expanded.Visibility = Visibility.Hidden;
            }
        }        
    }
}
}

包含默认模板的字典:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:TestCustomControl">
    <Style TargetType="{x:Type local:SlideGrid}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:SlideGrid}">
                    <Grid>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="ViewStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition GeneratedDuration="0:0:0.3" To="Expanded">
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetName="FlipButtonTransform"
       Storyboard.TargetProperty="X" To="0" Duration="0:0:0.3"></DoubleAnimation>
                                        </Storyboard>
                                    </VisualTransition>
                                    <VisualTransition GeneratedDuration="0:0:0.3" To="Closed">
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetName="FlipButtonTransform"
       Storyboard.TargetProperty="X" To="-300" Duration="0:0:0.3"></DoubleAnimation>
                                        </Storyboard>
                                    </VisualTransition>
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="Closed">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="ExpandedState" 
                                                             Storyboard.TargetProperty="Opacity" 
                                                             To="0" 
                                                             Duration="0" ></DoubleAnimation>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Expanded">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="ClosedState" 
                                                             Storyboard.TargetProperty="Opacity" 
                                                             To="0" 
                                                             Duration="0" ></DoubleAnimation>
                                        <DoubleAnimation Storyboard.TargetName="FlipButtonTransform"
                                                         Storyboard.TargetProperty="X"  
                                                         To="0" 
                                                         Duration="0"></DoubleAnimation>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <!-- This is the Closed content. -->
                        <ContentPresenter x:Name="ClosedState" 
                     Content="{TemplateBinding ClosedState}">
                        </ContentPresenter>
                        <!-- This is the Expanded content. -->
                        <ContentPresenter x:Name="ExpandedState"
                     Content="{TemplateBinding ExpandedState}">
                            <ContentPresenter.RenderTransform>
                                <TranslateTransform x:Name="FlipButtonTransform" X="-300"></TranslateTransform>
                            </ContentPresenter.RenderTransform>
                        </ContentPresenter>

                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Matthew macdonald的技巧是:在TestCustomControl组件中创建一个名为"theme"的文件夹通用。Xaml被放置在主题文件夹中,下面是Xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/TestCustomControl;component/SlideGrid.xaml" />
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

最后这是主窗口使用它:

<Window x:Class="TestCustomControl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"        
        xmlns:lib="clr-namespace:TestCustomControl">
    <Grid x:Name="LayoutRoot" Background="White">
        <lib:SlideGrid x:Name="panel" IsExpanded="False"
         Margin="10">
            <lib:SlideGrid.ClosedState>
                <StackPanel Background="Aqua">
                </StackPanel>
            </lib:SlideGrid.ClosedState>
            <lib:SlideGrid.ExpandedState>
                <Grid Background="Black">
                </Grid>
            </lib:SlideGrid.ExpandedState>
        </lib:SlideGrid>
    </Grid>
</Window>

提前感谢!

根据dependencyproperty使用动画

可能有更好的方法来做到这一点,但这是一种方法。

首先,用你的SlideInFrom方向创建一个枚举。确保这个enum不是在你的SlideGrid类中定义的,否则它不会工作(至少对我来说不是)。

public enum SlideInFromDirection
{
    Left = 0,
    Top = 1,
    Right = 2,
    Bottom = 3,
}

接下来,在你的ControlTemplate中为Y属性创建第二个DoubleAnimation,并将X和Y命名为DoubleAnimation。这样的:

<VisualTransition GeneratedDuration="0:0:0.3" To="Expanded">
        <Storyboard>
            <DoubleAnimation x:Name="VSExpandedX" Storyboard.TargetName="FlipButtonTransform" Storyboard.TargetProperty="X" To="0" Duration="0:0:0.3"></DoubleAnimation>
            <DoubleAnimation x:Name="VSExpandedY" Storyboard.TargetName="FlipButtonTransform" Storyboard.TargetProperty="Y" To="0" Duration="0:0:0.3"></DoubleAnimation>
        </Storyboard>
</VisualTransition>

在你的SlideGrid类中,创建两个私有成员变量来保存对这些DoubleAnimations的引用:

private System.Windows.Media.Animation.DoubleAnimation VSExpandedX;
private System.Windows.Media.Animation.DoubleAnimation VSExpandedY;

在OnApplyTemplate()中添加填充这些的代码。我在ChangeVisualState(false)行之前添加了它。我们一会儿会讲到slideInFromPropertyChangedCallback。我们需要在这里调用它,因为在XAML解析期间设置SlideInFromProperty时动画变量还没有设置:

this.VSExpandedX = base.GetTemplateChild("VSExpandedX") as System.Windows.Media.Animation.DoubleAnimation;
this.VSExpandedY = base.GetTemplateChild("VSExpandedY") as System.Windows.Media.Animation.DoubleAnimation;
slideInFromPropertyChangedCallback(this, new DependencyPropertyChangedEventArgs(SlideInFromProperty, this.SlideInFrom, this.SlideInFrom));

现在,创建一个DependencyProperty指定SlideInFrom的方向。我们需要知道它的值何时发生变化所以我们还需要slideInFromPropertyChangedCallback方法:

public SlideInFromDirection SlideInFrom
{
    get { return (SlideInFromDirection)GetValue(SlideInFromProperty); }
    set { SetValue(SlideInFromProperty, value); }
}
public static readonly DependencyProperty SlideInFromProperty =
    DependencyProperty.Register("SlideInFrom", typeof(SlideInFromDirection), typeof(SlideGrid), new PropertyMetadata(SlideInFromDirection.Left, slideInFromPropertyChangedCallback));
private static void slideInFromPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    SlideGrid sg = d as SlideGrid;
    if (sg != null && e.NewValue is SlideInFromDirection)
    {
        if (sg.VSExpandedX == null || sg.VSExpandedY == null)
            return;
        SlideInFromDirection sd = (SlideInFromDirection)e.NewValue;
        switch (sd)
        {
            case SlideInFromDirection.Left:
                sg.VSExpandedX.From = -300.0;
                sg.VSExpandedY.From = 0.0;
                break;
            case SlideInFromDirection.Right:
                sg.VSExpandedX.From = 300.0;
                sg.VSExpandedY.From = 0.0;
                break;
            case SlideInFromDirection.Top:
                sg.VSExpandedX.From = 0.0;
                sg.VSExpandedY.From = -300.0;
                break;
            case SlideInFromDirection.Bottom:
                sg.VSExpandedX.From = 0.0;
                sg.VSExpandedY.From = 300.0;
                break;
        }
    }
}

就是这样。

这是完整的源代码:

MainWindow.xaml

<Window x:Class="WpfApplication5.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication5"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <local:SlideGrid  x:Name="panel" IsExpanded="False" Margin="10" SlideInFrom="Bottom" >
        <local:SlideGrid.ClosedState>
            <StackPanel Background="Aqua" />
        </local:SlideGrid.ClosedState>
        <local:SlideGrid.ExpandedState>
            <Grid Background="Black">
            </Grid>
        </local:SlideGrid.ExpandedState>
    </local:SlideGrid>
</Grid>

SlideGrid.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication5
{
    public enum SlideInFromDirection
    {
        Left = 0,
        Top = 1,
        Right = 2,
        Bottom = 3,
    }
    [TemplatePart(Name = "SlideContainerClosed", Type = typeof(ContentPresenter)),
    TemplatePart(Name = "SlideContainerExpanded", Type = typeof(ContentPresenter)),
    TemplateVisualState(Name = "Closed", GroupName = "ViewStates"),
    TemplateVisualState(Name = "Expanded", GroupName = "ViewStates")]
    public class SlideGrid : System.Windows.Controls.Control
    {
        public static readonly DependencyProperty ClosedStateProperty = DependencyProperty.Register("ClosedState", typeof(object), typeof(SlideGrid), null);
        public static readonly DependencyProperty ExpandedStateProperty = DependencyProperty.Register("ExpandedState", typeof(object), typeof(SlideGrid), null);
        public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register("IsExpanded", typeof(bool), typeof(SlideGrid), null);

        public SlideInFromDirection SlideInFrom
        {
            get { return (SlideInFromDirection)GetValue(SlideInFromProperty); }
            set { SetValue(SlideInFromProperty, value); }
        }
        public static readonly DependencyProperty SlideInFromProperty =
            DependencyProperty.Register("SlideInFrom", typeof(SlideInFromDirection), typeof(SlideGrid), new PropertyMetadata(SlideInFromDirection.Left, slideInFromPropertyChangedCallback));
        private static void slideInFromPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SlideGrid sg = d as SlideGrid;
            if (sg != null && e.NewValue is SlideInFromDirection)
            {
                if (sg.VSExpandedX == null || sg.VSExpandedY == null)
                    return;
                SlideInFromDirection sd = (SlideInFromDirection)e.NewValue;
                switch (sd)
                {
                    case SlideInFromDirection.Left:
                        sg.VSExpandedX.From = -300.0;
                        sg.VSExpandedY.From = 0.0;
                        break;
                    case SlideInFromDirection.Right:
                        sg.VSExpandedX.From = 300.0;
                        sg.VSExpandedY.From = 0.0;
                        break;
                    case SlideInFromDirection.Top:
                        sg.VSExpandedX.From = 0.0;
                        sg.VSExpandedY.From = -300.0;
                        break;
                    case SlideInFromDirection.Bottom:
                        sg.VSExpandedX.From = 0.0;
                        sg.VSExpandedY.From = 300.0;
                        break;
                }
            }
        }
        private System.Windows.Media.Animation.DoubleAnimation VSExpandedX;
        private System.Windows.Media.Animation.DoubleAnimation VSExpandedY;
        static SlideGrid()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(SlideGrid), new FrameworkPropertyMetadata(typeof(SlideGrid)));
        }
        public object ClosedState
        {
            get
            {
                return base.GetValue(ClosedStateProperty);
            }
            set
            {
                base.SetValue(ClosedStateProperty, value);
            }
        }
        public object ExpandedState
        {
            get
            {
                return base.GetValue(ExpandedStateProperty);
            }
            set
            {
                base.SetValue(ExpandedStateProperty, value);
            }
        }
        public bool IsExpanded
        {
            get
            {
                return (bool)base.GetValue(IsExpandedProperty);
            }
            set
            {
                base.SetValue(IsExpandedProperty, value);
                ChangeVisualState(true);
            }
        }
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            ContentPresenter slideContainerClosed = base.GetTemplateChild("ClosedState") as ContentPresenter;
            if (slideContainerClosed != null) slideContainerClosed.MouseEnter += slideContainerClosed_GotMouseCapture;
            ContentPresenter slideContainerExpanded = base.GetTemplateChild("ExpandedState") as ContentPresenter;
            if (slideContainerExpanded != null) slideContainerExpanded.MouseLeave += slideContainerExpanded_LostMouseCapture;
            this.VSExpandedX = base.GetTemplateChild("VSExpandedX") as System.Windows.Media.Animation.DoubleAnimation;
            this.VSExpandedY = base.GetTemplateChild("VSExpandedY") as System.Windows.Media.Animation.DoubleAnimation;
            slideInFromPropertyChangedCallback(this, new DependencyPropertyChangedEventArgs(SlideInFromProperty, this.SlideInFrom, this.SlideInFrom));
            this.ChangeVisualState(false);
        }
        private void slideContainerClosed_GotMouseCapture(object sender, MouseEventArgs e)
        {
            this.IsExpanded = true;
        }
        private void slideContainerExpanded_LostMouseCapture(object sender, MouseEventArgs e)
        {
            this.IsExpanded = false;
        }
        private void ChangeVisualState(bool useTransitions)
        {
            if (!this.IsExpanded)
            {
                VisualStateManager.GoToState(this, "Closed", useTransitions);
            }
            else
            {
                VisualStateManager.GoToState(this, "Expanded", useTransitions);
            }
            UIElement closed = ClosedState as UIElement;
            if (closed != null)
            {
                if (IsExpanded)
                {
                    closed.Visibility = Visibility.Hidden;
                }
                else
                {
                    closed.Visibility = Visibility.Visible;
                }
            }
            UIElement expanded = ExpandedState as UIElement;
            if (expanded != null)
            {
                if (IsExpanded)
                {
                    expanded.Visibility = Visibility.Visible;
                }
                else
                {
                    expanded.Visibility = Visibility.Hidden;
                }
            }
        }
    }
}

Generic.xaml

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication5">
<Style TargetType="{x:Type local:SlideGrid}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:SlideGrid}">
                <Grid>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="ViewStates">
                            <VisualStateGroup.Transitions>
                                <VisualTransition GeneratedDuration="0:0:0.3" To="Expanded">
                                    <Storyboard>
                                        <DoubleAnimation x:Name="VSExpandedX" Storyboard.TargetName="FlipButtonTransform" Storyboard.TargetProperty="X" To="0" Duration="0:0:0.3"></DoubleAnimation>
                                        <DoubleAnimation x:Name="VSExpandedY" Storyboard.TargetName="FlipButtonTransform" Storyboard.TargetProperty="Y" To="0" Duration="0:0:0.3"></DoubleAnimation>
                                    </Storyboard>
                                </VisualTransition>
                                <VisualTransition GeneratedDuration="0:0:0.3" To="Closed">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="FlipButtonTransform" Storyboard.TargetProperty="X" To="-300" Duration="0:0:0.3"></DoubleAnimation>
                                    </Storyboard>
                                </VisualTransition>
                            </VisualStateGroup.Transitions>
                            <VisualState x:Name="Closed">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="ExpandedState" 
                                                         Storyboard.TargetProperty="Opacity" 
                                                         To="0" 
                                                         Duration="0" ></DoubleAnimation>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Expanded">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="ClosedState" 
                                                         Storyboard.TargetProperty="Opacity" 
                                                         To="0" 
                                                         Duration="0" ></DoubleAnimation>
                                    <DoubleAnimation Storyboard.TargetName="FlipButtonTransform"
                                                     Storyboard.TargetProperty="X"  
                                                     To="0" 
                                                     Duration="0"></DoubleAnimation>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <!-- This is the Closed content. -->
                    <ContentPresenter x:Name="ClosedState"  Content="{TemplateBinding ClosedState}">
                    </ContentPresenter>
                    <!-- This is the Expanded content. -->
                    <ContentPresenter x:Name="ExpandedState" Content="{TemplateBinding ExpandedState}">
                        <ContentPresenter.RenderTransform>
                            <TranslateTransform x:Name="FlipButtonTransform" X="-300" Y="0"></TranslateTransform>
                        </ContentPresenter.RenderTransform>
                    </ContentPresenter>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>