WPF GridLength折叠动画+ GridSplitter 拖拽布局

如下图所示一个布局:点击向左按钮,折叠树形导航菜单面板,拖拽左右分割滑块(WPF里叫GridSplitter)可以调整区域分割宽度.

界面样式选用了Materail DesginUI ,十分好用,外观漂亮,开发常规需求,足够使用了。

Xmal中的布局,其中有一列"menuLeft" 双向绑定了ViewModel里的MenuWidth属性, GridSplitter左右拖拽时动态改变了MenuWidth属性值;

<Grid Grid.Row="2">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition x:Name="menuLeft" Width="{Binding MenuWidth,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, Converter={StaticResource doubleToGrid}}">
                    </ColumnDefinition>
                    <ColumnDefinition Width="8*"/>
                </Grid.ColumnDefinitions>
                <Grid>
                </Grid>
                <Grid Grid.Column="1">                 
                </Grid>
                <GridSplitter  HorizontalAlignment="Right" DragDelta="GridSplitter_DragDelta"  Width="10" Height="400"/>
 </Grid>

ColumnDefinition的Width 类型为GridLength,非Double型,此处做了一个转化:

public class DoubletoGridConvert : IValueConverter
    {       
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var n = (double)value;
            return new GridLength(n);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            GridLength length = (GridLength)value;
            if (length != null)
                return length.Value;
            return 0;
        }
    }

注意:在menuLeft 的WidthProperty 属性动画结束后,GridSplitter再无法继续拖拽了。这个可能是WPF本身设计的原因,要解决此问题,有三种做法:

There are three ways to do this with storyboard animations:

  • Set the animation's FillBehavior property to Stop

  • Remove the entire Storyboard.

  • Remove the animation from the individual property.

出处:https://msdn.microsoft.com/zh-cn/library/aa970493.aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-4

我这里的做法是:结合1和3点,具体为:

 <Storyboard x:Key="menuStoryBoard">
                <utility:GridLengthAnimation BeginTime="00:00:00" Completed="GridLengthAnimation_Completed" FillBehavior="Stop" Duration="0:0:0.3" Storyboard.TargetName="menuLeft" Storyboard.TargetProperty="Width"/>
            </Storyboard>

  动画结束后:

private void GridLengthAnimation_Completed(object sender, EventArgs e)
        {
            this.menuLeft.BeginAnimation(ColumnDefinition.WidthProperty, null);
        }

其中动画产生的代码为:

  MenuCollapseCommand = new DelegateCommand(() =>
            {
                IsMenuCollapse = !IsMenuCollapse;
                var gla = MenuStoryboard.Children[0] as GridLengthAnimation;
                if (IsMenuCollapse)
                {
                    menuWidthBeforeCollpase = MenuWidth;
                    gla.From = new GridLength(MenuWidth, GridUnitType.Pixel);
                    gla.To = new GridLength(minMenuWidth, GridUnitType.Pixel);
                    MenuStoryboard.Begin();
                    MenuWidth = minMenuWidth;
                }
                else
                {                    
                    gla.From = new GridLength(minMenuWidth, GridUnitType.Pixel);
                    gla.To = new GridLength(menuWidthBeforeCollpase, GridUnitType.Pixel);
                    MenuStoryboard.Begin();
                    MenuWidth = menuWidthBeforeCollpase;
                }
            });

考虑WPF MVVM的设计模式:控制UI的行为通常写在ViewModel中,在点击折叠按钮时直接使用事件触发器完成。 

首先安装Microsoft.Expression.Interactions: 

 

 添加命名空间的引用:

   xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

Xaml中折叠按钮的写法:

 <md:PackIcon Cursor="Hand" ToolTip="折叠"  ToolTipService.InitialShowDelay="0" Background="Transparent" VerticalAlignment="Center" Visibility="{Binding IsMenuCollapse, Converter={StaticResource  boolToVisible}, ConverterParameter=true}"   HorizontalAlignment="Right"  Margin="10 0"  Kind="ArrowLeft" Width="25" Height="25" Foreground="Gainsboro"  >
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="MouseLeftButtonDown">
                                <i:InvokeCommandAction Command="{Binding MenuCollapseCommand}"/>
                                <ei:ControlStoryboardAction ControlStoryboardOption="Play" Storyboard="{StaticResource menuStoryBoard}">
                                    <!--<ei:ControlStoryboardAction.Storyboard>
                                        <Storyboard>
                                            <utility:GridLengthAnimation BeginTime="00:00:00" Completed="GridLengthAnimation_Completed" FillBehavior="Stop" Duration="0:0:0.3" Storyboard.TargetName="menuLeft" Storyboard.TargetProperty="Width"/>
                                        </Storyboard>
                                    </ei:ControlStoryboardAction.Storyboard>-->
                                </ei:ControlStoryboardAction>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </md:PackIcon>

此处的StoryBoard为:

<Storyboard x:Key="StoryBoard">
                <utility:GridLengthAnimation BeginTime="00:00:00" From="260" To="12" Completed="GridLengthAnimation_Completed" FillBehavior="Stop" Duration="0:0:0.3" Storyboard.TargetName="menuLeft" Storyboard.TargetProperty="Width"/>
            </Storyboard>

From 和 to 依赖属性可以绑定ViewModel的值,此处写死,仅Demo说明

写在最后:GridLengthAnimation 综合百度他人blog得来:附加于此,供参考

 internal class GridLengthAnimation : AnimationTimeline
    {
        static GridLengthAnimation()
        {
            FromProperty = DependencyProperty.Register("From", typeof(GridLength), typeof(GridLengthAnimation));
            ToProperty = DependencyProperty.Register("To", typeof(GridLength), typeof(GridLengthAnimation));
        }

        public static readonly DependencyProperty FromProperty;
        public GridLength From
        {
            get
            {
                return (GridLength)GetValue(GridLengthAnimation.FromProperty);
            }
            set
            {
                SetValue(GridLengthAnimation.FromProperty, value);
            }
        }

        public static readonly DependencyProperty ToProperty;
        public GridLength To
        {
            get
            {
                return (GridLength)GetValue(GridLengthAnimation.ToProperty);
            }
            set
            {
                SetValue(GridLengthAnimation.ToProperty, value);
            }
        }

        public override Type TargetPropertyType
        {
            get
            {
                return typeof(GridLength);
            }
        }

        protected override System.Windows.Freezable CreateInstanceCore()
        {
            return new GridLengthAnimation();
        }

        public override object GetCurrentValue(object defaultOriginValue,
            object defaultDestinationValue, AnimationClock animationClock)
        {
            double fromVal = ((GridLength)GetValue(GridLengthAnimation.FromProperty)).Value;
            double toVal = ((GridLength)GetValue(GridLengthAnimation.ToProperty)).Value;

            if (fromVal > toVal)
            {
                return new GridLength((1 - animationClock.CurrentProgress.Value) * (fromVal - toVal) + toVal,
                    ((GridLength)GetValue(GridLengthAnimation.FromProperty)).GridUnitType);
            }
            else
                return new GridLength(animationClock.CurrentProgress.Value * (toVal - fromVal) + fromVal,
                    ((GridLength)GetValue(GridLengthAnimation.ToProperty)).GridUnitType);
        }

        //public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
        //{
        //    double fromVal = ((GridLength)GetValue(FromProperty)).Value;
        //    double toVal = ((GridLength)GetValue(ToProperty)).Value;
        //    double progress = animationClock.CurrentProgress.Value;

        //    IEasingFunction easingFunction = EasingFunction;
        //    if (easingFunction != null)
        //    {
        //        progress = easingFunction.Ease(progress);
        //    }


        //    if (fromVal > toVal)
        //        return new GridLength((1 - progress) * (fromVal - toVal) + toVal, GridUnitType.Star);

        //    return new GridLength(progress * (toVal - fromVal) + fromVal, GridUnitType.Star);
        //}

        /// <summary>
        /// The <see cref="EasingFunction" /> dependency property's name.
        /// </summary>
        public const string EasingFunctionPropertyName = "EasingFunction";

        /// <summary>
        /// Gets or sets the value of the <see cref="EasingFunction" />
        /// property. This is a dependency property.
        /// </summary>
        public IEasingFunction EasingFunction
        {
            get
            {
                return (IEasingFunction)GetValue(EasingFunctionProperty);
            }
            set
            {
                SetValue(EasingFunctionProperty, value);
            }
        }

        /// <summary>
        /// Identifies the <see cref="EasingFunction" /> dependency property.
        /// </summary>
        public static readonly DependencyProperty EasingFunctionProperty = DependencyProperty.Register(
            EasingFunctionPropertyName,
            typeof(IEasingFunction),
            typeof(GridLengthAnimation),
            new UIPropertyMetadata(null));

    }

 

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值