Toggle navigation
首页
关于TCPGAME
快乐分享
联系我们
C# WPF MVVM 动画(Animation)的正确使用姿势
Lonner
时间:2021-02-04 09:06:34
阅读:2515
### 前言 关于WPF的MVVM动画的资料,几乎都是创建线程来实现动画,这种方法资源开销极大,不是我认为合理的WPF MVVM 动画实现方式。但是找了许久都没有明确的使用教程或例子,经过自己在[微软官方文档](https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.media.animation.doubleanimation.-ctor?view=netframework-4.5.2&f1url=%3FappId%3DDev16IDEF1%26l%3DZH-CN%26k%3Dk(System.Windows.Media.Animation.DoubleAnimation.%2523ctor)的摸索,最终实现了MVVM的动画,中间虽然有些波折,但是还是完美的实现了MVVM动画。 ### 效果 ![效果](//tcpgame.com\mdimg\79ea6b85-da2a-4d23-9c10-4e4e6ba22271_1.gif "效果") ### 分析 这个展示元素就是2个控件: - TextBlock:显示文字描述 - Rectangle:一个可变长度带填充颜色的矩形 ###实现步骤 ####1. 新建一个用户控件 右键点击解决方案项目或项目内的文件夹,在弹出的邮件菜单中选中新建用户控件,并将其命名为`UCBAR.xaml`,添加一个TextBlock与Rectangle,调整一下位置,显示效果如下: ![界面控件](//tcpgame.com\mdimg\0ae576a6-7e7a-4fe3-8ece-028a857bb3e2_1.png "界面控件") 代码贴出来一下: ``` csharp <UserControl x:Class="WPFMVVMAnimation.UCBAR" 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:WPFMVVMAnimation" mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="300"> <Grid> <TextBlock Foreground="White" HorizontalAlignment="Left" Margin="0,0,0,0" TextWrapping="Wrap" Height="16" TextAlignment="Right" Text="属性名:" TextTrimming="CharacterEllipsis" VerticalAlignment="Center" Width="60"/> <Rectangle x:Name="RectView" Fill="GreenYellow" HorizontalAlignment="Left" Height="15" Margin="60,0,0,0" Stroke="Black" VerticalAlignment="Center" Width="100"/> </Grid> </UserControl> ``` ####2. 配置用户控件属性 设计界面中,按下F7按键,跳转到用户控件代码,根据MVVM绑定规则,我们需要建立两个DependencyProperty类型的属性,代码如下(放置在构造函数之前即可): ``` csharp //界面的属性名 public static readonly DependencyProperty TextProperty = DependencyProperty.Register( "Text", typeof(string), typeof(UCBAR), new PropertyMetadata("属性名")); public double Text { get { return (double)GetValue(TextProperty); } set { SetValue(TextProperty, value); } } //界面矩形的宽 public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof(double), typeof(UCBAR), new PropertyMetadata(30d)); public double Value { get { return (double)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } } ``` ####3. ValueProperty属性改变后执行动画 需要声明两个类变量,lastValue用来保存上一次的值,doubleAnimation就是动画对象: ``` private double lastValue; DoubleAnimation doubleAnimation; ``` 然后在构造函数中,初始化一下DoubleAnimation对象。以及将创建一个对ValueProperty变动的监听器**(关键)**,代码如下: ``` public UCColumnChart() { InitializeComponent(); //实例化DoubleAnimation doubleAnimation = new DoubleAnimation() { Duration = new TimeSpan(0, 0, 0, 0, 300), }; //动画结束后,保持最后的值 doubleAnimation.FillBehavior = FillBehavior.HoldEnd; //动画结束后的事件 doubleAnimation.Completed += DoubleAnimation_Completed; //重点 创建一个属性监器,并监听ValueProperty改变,如果ValueProperty改变了将触发SelfChanges方法 var descriptor = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(UCBAR.ValueProperty, typeof(UCBAR)); descriptor.AddValueChanged(this, SelfChanges); } ``` 在SelfChanges 方法中,将执行doubleAnimation动画,并将动画目标绑定到Rectangle控件元素中,之前命名为RectView。动画的From就是上次的值,TO就是本次新的值。动画完成后触发DoubleAnimation_Completed方法,只需将lastValue更新一下就可以了。贴上代码: ``` private void SelfChanges(object sender, EventArgs e) { doubleAnimation.From = lastValue; doubleAnimation.To = Value; RectView.BeginAnimation(WidthProperty, doubleAnimation); } private void DoubleAnimation_Completed(object sender, EventArgs e) { lastValue = (double)doubleAnimation.To; } ``` 然后别忘了界面上TextBlock控件元素绑定到TextProperty属性,这里绑定**与动画无关**,只是为了可以在调用控件时显示矩形前面的文字。完整的界面xaml代码: ``` <UserControl x:Class="WPFMVVMAnimation.UCBAR" x:Name="userControl" 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:WPFMVVMAnimation" mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="300"> <Grid> <TextBlock Foreground="White" HorizontalAlignment="Left" Margin="0,0,0,0" TextWrapping="Wrap" Height="16" TextAlignment="Right" Text="{Binding ElementName=userControl,Path=Text}" TextTrimming="CharacterEllipsis" VerticalAlignment="Center" Width="60"/> <Rectangle x:Name="RectView" Fill="GreenYellow" HorizontalAlignment="Left" Height="15" Margin="60,0,0,0" Stroke="Black" VerticalAlignment="Center" Width="100"/> </Grid> </UserControl> ``` ####4. 使用方式 与常规的MVVM绑定方式一致,这里就直接在Window中放一个**UCBAR**自定义控件为示例,先新建一个窗体的**ViewModl**命名为**VMMainWindow**(因为只是演示,所以没有独立封装**UCBAR**的**ViewModl**),代码如下: ``` using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; namespace WPFMVVMAnimation { public class VMMainWindow : INotifyPropertyChanged { #region 这部分实际开发是放在基类中的,为了方便演示,就放在这列,知道MVVM的不难理解 public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// 属性发生改变时调用该方法发出通知 /// </summary> /// <param name="propertyName">属性名称</param> public void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } protected virtual void SetAndNotifyIfChanged<T>(string propertyName, ref T oldValue, T newValue) { if (oldValue == null && newValue == null) return; if (oldValue != null && oldValue.Equals(newValue)) return; if (newValue != null && newValue.Equals(oldValue)) return; oldValue = newValue; RaisePropertyChanged(propertyName); } #endregion /* * 以下是两个属性,常规的MVVM ViewModel写法 */ private string _Text = "分数"; private double _Value = 0; public string Text { get { return _Text; } set { _Text = value; RaisePropertyChanged("Text"); } } public double Value { get { return _Value; } set { _Value = value; RaisePropertyChanged("Value"); } } //按钮事件 public ICommand CmdChangeValue { get { return new RelayCommand<object>(CmdChangeValueAction); } } private void CmdChangeValueAction(object obj) { Random random = new Random(); Value = (random.Next(0, 2000) / 10.0);//生成0到200带一个小数点的double数 } } public class RelayCommand<T> : ICommand where T : class { #region 字段 readonly Func<T, Boolean> _canExecute; readonly Action<T> _execute; #endregion #region 构造函数 public RelayCommand(Action<T> execute) : this(execute, null) { } public RelayCommand(Action<T> execute, Func<T, Boolean> canExecute) { if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canExecute = canExecute; } #endregion #region ICommand的成员 public event EventHandler CanExecuteChanged { add { if (_canExecute != null) CommandManager.RequerySuggested += value; } remove { if (_canExecute != null) CommandManager.RequerySuggested -= value; } } [DebuggerStepThrough] public Boolean CanExecute(Object parameter) { return _canExecute == null ? true : _canExecute((T)parameter); } public void Execute(Object parameter) { _execute(parameter as T); } #endregion } } ``` **MainWindow** 中代码: ``` <Window x:Class="WPFMVVMAnimation.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WPFMVVMAnimation" mc:Ignorable="d" Background="#333333" Title="MainWindow" Height="200" Width="400"> <Window.DataContext> <local:VMMainWindow/> </Window.DataContext> <Grid> <local:UCBAR Margin="0,50" VerticalAlignment="Top" Text="{Binding Text}" Value="{Binding Value}" /> <Button Content="改变一下值" Margin="0,0,00,50" VerticalAlignment="Bottom" HorizontalAlignment="Center" Command="{Binding CmdChangeValue}" /> </Grid> </Window> ``` Github源码链接 [https://github.com/TCPGAME/WPFMVVMAnimation](https://github.com/TCPGAME/WPFMVVMAnimation "源码链接")
上一章:C# 内嵌火狐浏览器GeckoWebBrowser 与JavaScript的交互
下一章:C# WPF MVVM实现百万级数据列表丝滑流畅加载显示