博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WPF MVVM UI分离之《交互与数据分离》
阅读量:6457 次
发布时间:2019-06-23

本文共 18079 字,大约阅读时间需要 60 分钟。

原文:

在我们使用WPF过程中,不可避免并且超级喜欢使用MVVM框架。

那么,使用MVVM的出发点是视觉与业务逻辑分离,即UI与数据分离

诸如下面的问题:

删除操作,假如需要先执行一部分数据的处理,然后删除界面列表中的子项,之后再执行其它数据的处理。请问此业务该放置于Xaml.cs文件,还是ViewModel中呢?

再如弹窗,提示框,设置列表的滚动等等。

此上一些操作,我们不应该把业务代码直接挪到cs文件中,因为删除操作绝大部分的代码都是数据的处理。所以,数据的部分放置在ViewModel中,一些交互放在cs文件中,就是很合理及有必要了。

单元测试,UI与交互的那部分mock模拟有点难度,也没必要去模拟。那么,我们是应该把数据与交互拆开,减少之间的耦合性。这样添加单元测试则更容易。

交互与数据分离 - 描述

首先MVVM,通过View与ViewModel的绑定,我们实现了UI与业务逻辑的分离。通俗一点,我们熟悉的Xaml与ViewModel文件中,代码之间的隔离。在此不详述~

而MVVM,不只是界面与逻辑,其实逻辑还可以拆分成交互与数据

即:Xaml 》Xaml.cs 》ViewModel

是的,按照上面的结构图,我们分成三部分:

  • 界面 用于界面呈现 ---- 如页面/控件/样式/模板等其它资源的初始化,动画的触发等。
  • 交互 用于与用户确认的交互或者界面复杂逻辑的处理 ---- 如弹窗/提示框/复杂动画的处理/设置列表的滚动等其它界面元素的视觉处理。
  • 数据 只是数据的处理 ---- 增删改查导入导出保存等只针对数据的操作,界面状态属性的保存与触发更改(绑定)。

交互与数据分离是怎样的?比如删除:

1. 界面删除按钮,绑定ViewModel中的DeleteCommand,当我们点击删除时,触发DeleteCommand.Execute

2. ViewModel中,先执行数据状态的判断,然后执行交互通知ShowDeleteWaringAction,调用xaml.cs文件中的确认提示框

3. 在Xaml.cs中添加依赖属性ShowDeleteWaring,绑定ViewModel中的ShowDeleteWaringAction.Progress。在属性更改中,处理提示框确认逻辑。

4. ViewModel中,等待ShowDeleteWaring弹框完成后,继续执行下面的业务。

5. 还有类似上面步骤的删除动画。。。

 

交互与数据分离 - 实现

使用场景:在WPF框架下开发时,一种基于MVVM的UI分离方案

解决方案:在业务逻辑处理过程中,新建一个交互处理线程,通知界面完成交互处理,同时后台逻辑保持同步等待。界面完成交互处理后,回调并执行后续的业务逻辑。

实现方案:

  • View中的依赖属性DependencyProperty,绑定ViewModel中属性“UIDelegateOperation”中的交互处理进度“UIDelegateProress”
  • 每次在ViewModel执行业务逻辑需要调用交互处理时,由UIDelegateOperation创建一个新的交互进度“UIDelegateProress”,触发属性变更,并设置“UIDelegateOperation”同步等待。
  • 当View中的属性变更事件执行完成后,回调并唤醒”UIDelegateOperation“,继续完成后面的业务逻辑。

1. 界面

在Xaml中添加附加属性,删除动画DeleteCoursewaresAnimation,删除确认框ShowDeleteWaring。并绑定ViewModel中对应的属性

1 
2
6

界面ListBox,列表子项ListBoxItemr的DataTemplate模板中,删除按钮绑定ViewModel中的DeleteCommand

1 

 2. ViewModel

ViewModel调用UIDelegateOperation交互处理时,根据是否需要同步等待,调用不同的函数 Start(),StartAsync(),StartWithResult(),StartWithResultAsync();

删除业务中,除了数据处理,还有俩个交互(删除确认框,删除元素动画)。

通过在同步调用删除确认框/删除元素动画后,再继续往下执行业务。

属性和字段字义:

定义命令

自定义命令,可以详细之前写的博客:

1 private DelegateCommand
_deleteCommand = null; 2 ///
3 /// 删除 4 /// 5 public DelegateCommand
DeleteCommand 6 { 7 get 8 { 9 if (_deleteCommand == null)10 {11 _deleteCommand = new DelegateCommand
(DeleteCourseware_OnExecute);12 13 }14 return _deleteCommand;15 } 16 }

提示框确认交互/删除动画交互

1 /// 2 /// 弹出删除确认窗口 3 /// 4 public IUIDelegateOperation
, MessageResult> ShowDeleteWaring { get; set; } = new IUIDelegateOperation
, MessageResult>();5 6 ///
7 /// 删除动画 8 /// 9 public IUIDelegateOperation
> DeleteCoursewaresAnimation { get; set; } = new IUIDelegateOperation
>();

删除逻辑:

1 ///  2 /// 删除 3 ///  4 ///  5 /// 
6 private async void DeleteCourseware_OnExecute(CoursewareListItem item) 7 { 8 await DeleteCoursewares(new List
() { item }); 9 }10 private async Task DeleteCoursewares(List
items)11 {12 if (items.Count == 0)13 {14 return;15 }16 17 //弹出删除确认窗口18 var messageResult = await ShowDeleteWaringShow.ExecuteWithResultAsync(items);19 if (messageResult == MessageResult.Positive)20 {21 //删除服务器数据 22 Response deleteResponse = await WebService.DeleteItemAsync(items);23 24 //删除失败25 if (!deleteResponse.Success)26 {27 Notification.ShowInfo(deleteResponse.Message);28 return;29 }30 //删除动画31 await DeleteCoursewaresAnimation.ExecuteAsync(items);32 33 //界面删除子项34 items.ForEach(item => ItemsSource.Remove(item));35 36 //退出编辑模式37 if (DocListState == EditStatus.Editing)38 {39 DocListState = EditStatus.Normal;40 }41 }42 }

 

3. Xaml.cs后台

  • 添加依赖属性后,通过属性变更触发,来完成弹出提示框/删除动画等交互。
  • 执行交互时,需要同步等待时,应将动画执行等转化为同步逻辑。

添加依赖属性 - 删除窗口

属性变更触发方法,应该是一个异步方法,里面的逻辑应该为同步执行。这样ViewModel中才能同步等待交互的完成,并执行之后的逻辑。

1 ///  2 /// 删除窗口 3 ///  4 public static readonly DependencyProperty ShowDeleteWaringShowProperty = DependencyProperty.Register( 5     "ShowDeleteWaringShow", typeof(UIDelegateProgress
, MessageResult>), typeof(CloudListView), new PropertyMetadata(default(UIDelegateProgress
, MessageResult>), 6 (d, e) => ((UIDelegateProgress
, MessageResult>)e.NewValue)?.StartAsync(((CloudListView)d).ShowDeleteWaringShow))); 7 8 private async Task
ShowDeleteWaringShow(List
items) 9 {10 var cmd = await DeleteWaringShow(items);11 return cmd.Result;12 }13 14 public static void SetShowDeleteWaringShow(DependencyObject element, UIDelegateProgress
, MessageResult> value)15 {16 element.SetValue(ShowDeleteWaringShowProperty, value);17 }18 19 public static UIDelegateProgress
, MessageResult> GetShowDeleteWaringShow(DependencyObject element)20 {21 return (UIDelegateProgress
, MessageResult>)element.GetValue(ShowDeleteWaringShowProperty);22 }

添加依赖属性 - 删除动画

1 public static readonly DependencyProperty DeleteCoursewaresAnimationProperty = DependencyProperty.Register( 2     "DeleteCoursewaresAnimation", typeof(UIDelegateProgress
>), typeof(CloudListView), new PropertyMetadata(default(UIDelegateProgress
>), 3 (d, e) => ((UIDelegateProgress
>)e.NewValue)?.StartAsync(((CloudListView)d).ExecuteDeleteCoursewaresAnimation))); 4 5 private async Task ExecuteDeleteCoursewaresAnimation(List
coursewares) 6 { 7 List
storyboards = new List
(); 8 foreach (var courseware in coursewares) 9 {10 var listBoxItem = DocumentsControl.ItemContainerGenerator.ContainerFromItem(courseware) as ListBoxItem;11 var border = listBoxItem?.VisualDescendant
();12 var storyboard = (Storyboard)border?.Resources["ItemRemovedStoryboard"];13 if (storyboard == null)14 {15 //如果找不到storyBoard,则中断动画的执行。因为删除多个Item,只执行一半的动画,界面会闪现俩次。16 return;17 }18 storyboards.Add(storyboard);19 }20 //删除界面课件21 await AsynchronousTransferHelper.ExecuteStoryboradAsync(storyboards);22 }23 24 public static void SetDeleteCoursewaresAnimation(DependencyObject element, UIDelegateProgress
> value)25 {26 element.SetValue(DeleteCoursewaresAnimationProperty, value);27 }28 29 public static UIDelegateProgress
> GetDeleteCoursewaresAnimation(DependencyObject element)30 {31 return (UIDelegateProgress
>)element.GetValue(DeleteCoursewaresAnimationProperty);32 }

动画的执行,怎么转为有同步等待呢?动画完成只有通过触发事件Completed才能确定。

如何将动画转化为同步,可参考之前写的博客:

1 ///  2 /// 执行动画 3 ///  4 ///  5 /// 
6 public static async Task ExecuteStoryboradAsync([NotNull] Storyboard storyboard) 7 { 8 if (storyboard == null) throw new ArgumentNullException(nameof(storyboard)); 9 10 AutoResetEvent autoResetEvent = new AutoResetEvent(false);11 12 storyboard.Completed += OnStoryboardCompleted;13 storyboard.Begin();14 15 void OnStoryboardCompleted(object sender, EventArgs e)16 {17 storyboard.Completed -= OnStoryboardCompleted;18 autoResetEvent.Set();19 }20 21 await Task.Run(() => { autoResetEvent.WaitOne(); });22 }

4. 交互处理辅助类 UIDelegateOperation 

在UIDelegateOperation内部,每次调用时,都会新建一个UIDelegateProgress(委托进度)。委托进度,是界面交互的处理~

UIDelegateOperation:

1 ///   2     /// UI交互处理-提供可调用UI交互的操作  3     ///   4     public class UIDelegateOperation : BindableObject, IUIDelegateAction  5     {  6         private UIDelegateProgress _delegateProgress;  7   8         public UIDelegateProgress DelegateProgress  9         { 10             get => _delegateProgress; 11             private set 12             { 13                 _delegateProgress = value; 14                 OnPropertyChanged(); 15             } 16         } 17  18         ///  19         /// 执行 20         ///  21         public void Execute() 22         { 23             var delegateProgress = new UIDelegateProgress(); 24             delegateProgress.ProgressCompleted += () => 25             { 26                 _delegateProgress = null; 27             }; 28             DelegateProgress = delegateProgress; 29         } 30  31         ///  32         /// 异步执行 33         /// 交互处理完成并回调 34         ///  35         public async Task ExecuteAsync() 36         { 37             AutoResetEvent autoResetEvent = new AutoResetEvent(false); 38  39             var delegateProgress = new UIDelegateProgress(); 40             delegateProgress.ProgressCompleted += () => 41             { 42                 _delegateProgress = null; 43  44                 autoResetEvent.Set(); 45             }; 46             DelegateProgress = delegateProgress; 47             await Task.Run(() => { autoResetEvent.WaitOne(); }); 48         } 49     } 50  51     ///  52     /// UI交互处理-提供可同步调用UI交互的操作 53     ///  54     /// 
输入/输出类型
55 public class UIDelegateAction
: BindableObject, IUIDelegateAction
56 { 57 private UIDelegateProgress
_delegateProgress; 58 59 public UIDelegateProgress
DelegateProgress 60 { 61 get => _delegateProgress; 62 private set 63 { 64 _delegateProgress = value; 65 OnPropertyChanged(); 66 } 67 } 68 ///
69 /// 执行 70 /// 71 public void Execute(T parameter) 72 { 73 var delegateProgress = new UIDelegateProgress
(parameter); 74 delegateProgress.ProgressCompleted += () => 75 { 76 _delegateProgress = null; 77 }; 78 DelegateProgress = delegateProgress; 79 } 80 ///
81 /// 异步执行 82 /// 交互处理完成并回调 83 /// 84 public async Task ExecuteAsync(T parameter) 85 { 86 AutoResetEvent autoResetEvent = new AutoResetEvent(false); 87 88 var delegateProgress = new UIDelegateProgress
(parameter); 89 delegateProgress.ProgressCompleted += () => 90 { 91 _delegateProgress = null; 92 93 autoResetEvent.Set(); 94 }; 95 DelegateProgress = delegateProgress; 96 97 await Task.Run(() => { autoResetEvent.WaitOne(); }); 98 } 99 100 ///
101 /// 异步执行并返回结果102 /// 103 public async Task
ExecuteWithResultAsync()104 {105 AutoResetEvent autoResetEvent = new AutoResetEvent(false);106 107 var delegateProgress = new UIDelegateProgress
();108 delegateProgress.ProgressCompleted += () =>109 {110 _delegateProgress = null;111 112 autoResetEvent.Set();113 };114 DelegateProgress = delegateProgress;115 116 await Task.Run(() => { autoResetEvent.WaitOne(); });117 118 return delegateProgress.Result;119 }120 }121 122 ///
123 /// UI交互处理-提供可同步调用UI交互的操作124 /// 125 ///
输入类型
126 ///
输出类型
127 public class UIDelegateAction
: BindableObject, IUIDelegateAction
128 {129 private UIDelegateProgress
_delegateProgress;130 131 public UIDelegateProgress
DelegateProgress132 {133 get => _delegateProgress;134 private set135 {136 _delegateProgress = value;137 OnPropertyChanged();138 }139 }140 ///
141 /// 执行142 /// 143 public void Execute(TInput parameter)144 {145 var delegateProgress = new UIDelegateProgress
(parameter);146 delegateProgress.ProgressCompleted += () =>147 {148 _delegateProgress = null;149 };150 DelegateProgress = delegateProgress;151 }152 153 ///
154 /// 执行并返回结果155 /// 156 public TOut ExecuteWithResult(TInput parameter)157 {158 var delegateProgress = new UIDelegateProgress
(parameter);159 delegateProgress.ProgressCompleted += () =>160 {161 _delegateProgress = null;162 };163 DelegateProgress = delegateProgress;164 return delegateProgress.Result;165 }166 167 ///
168 /// 异步执行并返回结果169 /// 170 public async Task
ExecuteWithResultAsync(TInput parameter)171 {172 var delegateProgress = new UIDelegateProgress
(parameter);173 await SetDelegateProgress(delegateProgress);174 return delegateProgress.Result;175 }176 private async Task SetDelegateProgress(UIDelegateProgress
delegateProgress)177 {178 AutoResetEvent autoResetEvent = new AutoResetEvent(false);179 180 delegateProgress.ProgressCompleted += () =>181 {182 _delegateProgress = null;183 autoResetEvent.Set();184 };185 DelegateProgress = delegateProgress;186 await Task.Run(() => { autoResetEvent.WaitOne(); });187 }188 }189 190 ///
191 /// UI交互处理接口192 /// 193 public interface IUIDelegateAction194 {195 196 UIDelegateProgress DelegateProgress { get; }197 198 ///
199 /// 执行200 /// 201 void Execute();202 203 ///
204 /// 异步执行205 /// 206 Task ExecuteAsync();207 }208 209 ///
210 /// UI交互处理接口211 /// 212 ///
输入/输出类型
213 public interface IUIDelegateAction
214 {215 UIDelegateProgress
DelegateProgress { get; }216 217 ///
218 /// 执行219 /// 220 void Execute(T parameter);221 222 ///
223 /// 异步执行224 /// 225 Task ExecuteAsync(T parameter);226 227 ///
228 /// 异步执行并返回结果229 /// 230 Task
ExecuteWithResultAsync();231 }232 233 ///
234 /// UI交互处理接口235 /// 236 ///
输入类型
237 ///
输出类型
238 public interface IUIDelegateAction
239 {240 UIDelegateProgress
DelegateProgress { get; }241 242 ///
243 /// 执行244 /// 245 void Execute(TInput parameter);246 247 ///
248 /// 执行并返回结果249 /// 250 TOut ExecuteWithResult(TInput parameter);251 252 ///
253 /// 异步执行并返回结果254 /// 255 Task
ExecuteWithResultAsync(TInput parameter);256 }
View Code

UIDelegateProgress:

1     ///   2     /// 委托进度  3     ///   4     public class UIDelegateProgress  5     {  6         public event Action ProgressCompleted;  7   8         ///   9         /// UI委托处理 10         ///  11         ///  12         public async void StartAsync(Func
uiTask) 13 { 14 try 15 { 16 await uiTask.Invoke(); 17 } 18 catch (InvalidOperationException e) 19 { 20 Log.Error("UI交互处理,产生异常!", e); 21 } 22 finally 23 { 24 ProgressCompleted?.Invoke(); 25 } 26 } 27 28 ///
29 /// UI委托处理 30 /// 31 ///
32 public void Start(Action uiTask) 33 { 34 try 35 { 36 uiTask.Invoke(); 37 } 38 catch (InvalidOperationException e) 39 { 40 Log.Error("UI交互处理,产生异常!", e); 41 } 42 finally 43 { 44 ProgressCompleted?.Invoke(); 45 } 46 } 47 } 48 49 ///
50 /// 委托进度 51 /// 52 public class UIDelegateProgress
53 { 54 public event Action ProgressCompleted; 55 56 ///
57 /// 输入参数 58 /// 59 public T Parameter { get; set; } 60 61 ///
62 /// 输出参数 63 /// 64 public T Result { get; set; } 65 66 public UIDelegateProgress() 67 { 68 69 } 70 public UIDelegateProgress(T parameter) 71 { 72 Parameter = parameter; 73 } 74 75 ///
76 /// UI委托处理 77 /// 78 ///
79 public void Start(Action
uiTask) 80 { 81 try 82 { 83 uiTask.Invoke(Parameter); 84 } 85 catch (InvalidOperationException e) 86 { 87 Log.Error("UI交互处理,产生异常!", e); 88 } 89 finally 90 { 91 ProgressCompleted?.Invoke(); 92 } 93 } 94 95 ///
96 /// UI委托处理 97 /// 98 ///
99 public async void StartAsync(Func
uiTask)100 {101 try102 {103 await uiTask.Invoke(Parameter);104 }105 catch (InvalidOperationException e)106 {107 Log.Error("UI交互处理,产生异常!", e);108 }109 finally110 {111 ProgressCompleted?.Invoke();112 }113 }114 115 ///
116 /// UI委托处理117 /// 118 ///
119 public void Start(Func
uiTask)120 {121 try122 {123 Result = uiTask.Invoke();124 }125 catch (InvalidOperationException e)126 {127 Log.Error("UI交互处理,产生异常!", e);128 }129 finally130 {131 ProgressCompleted?.Invoke();132 }133 }134 135 ///
136 /// UI委托处理137 /// 138 ///
139 public async void StartAsync(Func
> uiTask)140 {141 try142 {143 Result = await uiTask.Invoke();144 }145 catch (InvalidOperationException e)146 {147 Log.Error("UI交互处理,产生异常!", e);148 }149 finally150 {151 ProgressCompleted?.Invoke();152 }153 }154 }155 156 ///
157 /// 委托进度158 /// 159 public class UIDelegateProgress
160 {161 public event Action ProgressCompleted;162 163 ///
164 /// 输入参数165 /// 166 public TInput Parameter { get; set; }167 168 ///
169 /// 输出参数170 /// 171 public TOut Result { get; set; }172 173 public UIDelegateProgress(TInput parameter)174 {175 Parameter = parameter;176 }177 178 ///
179 /// UI委托处理180 /// 181 ///
182 public async void StartAsync(Func
> uiTask)183 {184 try185 {186 Result = await uiTask.Invoke(Parameter);187 }188 catch (InvalidOperationException e)189 {190 Log.Error("UI交互处理,产生异常!", e);191 }192 finally193 {194 ProgressCompleted?.Invoke();195 }196 }197 198 ///
199 /// UI委托处理200 /// 201 ///
202 public void Start(Func
uiTask)203 {204 try205 {206 uiTask.Invoke();207 }208 catch (InvalidOperationException e)209 {210 Log.Error("UI交互处理,产生异常!", e);211 }212 finally213 {214 ProgressCompleted?.Invoke();215 }216 }217 218 ///
219 /// UI委托处理220 /// 221 ///
222 public void Start(Func
uiTask)223 {224 try225 {226 Result = uiTask.Invoke(Parameter);227 }228 catch (InvalidOperationException e)229 {230 Log.Error("UI交互处理,产生异常!", e);231 }232 finally233 {234 ProgressCompleted?.Invoke();235 }236 }237 }
View Code

 

关键字:UI分离,交互与数据分离,动画同步,单元测试

转载地址:http://wenzo.baihongyu.com/

你可能感兴趣的文章
Maven入门实战笔记-11节[7-11]
查看>>
jQuery新的事件绑定机制on()
查看>>
MATLAB——zeros
查看>>
程序入口地址的直接定制表【 (1) 清屏(2) 设置前景色 (3) 设置背景色 (4) 向上滚动一行】...
查看>>
EF Code First 学习笔记:表映射
查看>>
C# Stream 和 byte[] 之间的转换[转]
查看>>
云计算里AWS和Azure的探究(3)
查看>>
软件工程讲义 0 微博上的软件工程
查看>>
socket与模拟http请求
查看>>
深入浅出讲解:php的socket通信
查看>>
【C】——C深入探讨——switch语句的default位置【转】
查看>>
【ML】PAC learning model
查看>>
是不让页面重新渲染元素~·
查看>>
List的ToLookup 分组方法
查看>>
Eclipse安装Tomcat插件全攻略
查看>>
最近学习安卓中总结的一些知识点
查看>>
jvisualVm用法
查看>>
方法、脚本-Pig Grunt之简单命令及实例说明-by小雨
查看>>
硬件原理图和实物对比理解_zigbee模块
查看>>
深入理解Java:注解(Annotation)--注解处理器
查看>>