如下图所示一个布局:点击向左按钮,折叠树形导航菜单面板,拖拽左右分割滑块(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));
}