简体   繁体   中英

Use animation according to dependencyproperty

I have finally managed to create my first custom control in WPF!

It´s basically a container that once you hover over it a new container slides in from the windows left edge.

Im now trying to make it able to choose if it should slide in from top, bottom, left or right.

Im wondering if I could implement a logic or something in the default template that could determine which animation to use according a property or smth. So if I had a property, lets call it SlideInFrom and it was SlideInFrom = "left". Then my animation here does it properly. I have no idea how to implement such logich though!

Here is my full source code:

Class:

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;
            }
        }        
    }
}
}

Dictionary containg the default template:

<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 Macdonalds trick to make the thing work: A folder is created named 'theme' in the TestCustomControl assembly the generic.xaml is placed inside the theme folder with following 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>

Finally this is the mainwindow using it:

<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>

Thanks on advance!

There may be a better way to do this but here is one way to do it.

First, create an enum with your SlideInFrom directions. Make sure this enum is NOT defined inside of your SlideGrid class or it won't work (at least it didn't for me).

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

Next, in your ControlTemplate create a second DoubleAnimation for the Y property and name both the X and Y DoubleAnimations. Like this:

<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>

In your SlideGrid class, create two private member variables to hold references to these DoubleAnimations:

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

In OnApplyTemplate() add the code that fills these. I added it right before the ChangeVisualState(false) line. We'll get to the slideInFromPropertyChangedCallback in a minute. We need to call it here because the animation variables weren't set yet when the SlideInFromProperty was set during the XAML parsing:

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));

Now, create a DependencyProperty that specifies the direction to SlideInFrom. We will need to know when it's value changes so we also need the slideInFromPropertyChangedCallback method:

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;
        }
    }
}

And that's it.

Here is the complete source:

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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM