WPF MVVM UI分离之《交互与数据分离》 基础才是重中之重~delegate里的Invoke和BeginInvoke 将不确定变为确定系列~目录(“机器最能证明一切”) 爱上MVC3系列~全局异常处理与异常日志 基础才是重中之重~lock和monitor的区别 将不确定变成确定~我想监视我的对象,如果是某个值,就叫另一些方法自动运行 将不确定变成确定~LINQ DBML模型可以对
WPF MVVM UI分离之《交互与数据分离》
在我们使用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 <UserControl.Style>
2 <Style TargetType="editing:CloudListView">
3 <Setter Property="DeleteCoursewaresAnimation" Value="{Binding DeleteCoursewaresAnimation.DelegateProgress}" />
4 <Setter Property="ShowDeleteWaringShow" Value="{Binding ShowDeleteWaring.DelegateProgress}" />
5 </Style>
6 </UserControl.Style>
界面ListBox,列表子项ListBoxItemr的DataTemplate模板中,删除按钮绑定ViewModel中的DeleteCommand
1 <Button x:Name="DeleteButton"
2 Command="{Binding ElementName=TheCloudDocsList,Path=DataContext.DeleteCommand}"
3 CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent},Path=DataContext }"
4 Content="删除" Style="{StaticResource Style.Button}" />
2. ViewModel
ViewModel调用UIDelegateOperation交互处理时,根据是否需要同步等待,调用不同的函数 Start(),StartAsync(),StartWithResult(),StartWithResultAsync();
删除业务中,除了数据处理,还有俩个交互(删除确认框,删除元素动画)。
通过在同步调用删除确认框/删除元素动画后,再继续往下执行业务。
属性和字段字义:
定义命令
自定义命令,可以详细之前写的博客:自定义Command
1 private DelegateCommand<CoursewareListItem> _deleteCommand = null;
2 /// <summary>
3 /// 删除
4 /// </summary>
5 public DelegateCommand<CoursewareListItem> DeleteCommand
6 {
7 get
8 {
9 if (_deleteCommand == null)
10 {
11 _deleteCommand = new DelegateCommand<CoursewareListItem>(DeleteCourseware_OnExecute);
12
13 }
14 return _deleteCommand;
15 }
16 }
提示框确认交互/删除动画交互
1 /// <summary>
2 /// 弹出删除确认窗口
3 /// </summary>
4 public IUIDelegateOperation<List<CoursewareListItem>, MessageResult> ShowDeleteWaring { get; set; } = new IUIDelegateOperation<List<CoursewareListItem>, MessageResult>();
5
6 /// <summary>
7 /// 删除动画
8 /// </summary>
9 public IUIDelegateOperation<List<CoursewareListItem>> DeleteCoursewaresAnimation { get; set; } = new IUIDelegateOperation<List<CoursewareListItem>>();
删除逻辑:
1 /// <summary>
2 /// 删除
3 /// </summary>
4 /// <param name="item"></param>
5 /// <returns></returns>
6 private async void DeleteCourseware_OnExecute(CoursewareListItem item)
7 {
8 await DeleteCoursewares(new List<CoursewareListItem>() { item });
9 }
10 private async Task DeleteCoursewares(List<CoursewareListItem> 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 /// <summary>
2 /// 删除窗口
3 /// </summary>
4 public static readonly DependencyProperty ShowDeleteWaringShowProperty = DependencyProperty.Register(
5 "ShowDeleteWaringShow", typeof(UIDelegateProgress<List<CoursewareListItem>, MessageResult>), typeof(CloudListView), new PropertyMetadata(default(UIDelegateProgress<List<CoursewareListItem>, MessageResult>),
6 (d, e) => ((UIDelegateProgress<List<CoursewareListItem>, MessageResult>)e.NewValue)?.StartAsync(((CloudListView)d).ShowDeleteWaringShow)));
7
8 private async Task<MessageResult> ShowDeleteWaringShow(List<CoursewareListItem> items)
9 {
10 var cmd = await DeleteWaringShow(items);
11 return cmd.Result;
12 }
13
14 public static void SetShowDeleteWaringShow(DependencyObject element, UIDelegateProgress<List<CoursewareListItem>, MessageResult> value)
15 {
16 element.SetValue(ShowDeleteWaringShowProperty, value);
17 }
18
19 public static UIDelegateProgress<List<CoursewareListItem>, MessageResult> GetShowDeleteWaringShow(DependencyObject element)
20 {
21 return (UIDelegateProgress<List<CoursewareListItem>, MessageResult>)element.GetValue(ShowDeleteWaringShowProperty);
22 }
添加依赖属性 - 删除动画
1 public static readonly DependencyProperty DeleteCoursewaresAnimationProperty = DependencyProperty.Register(
2 "DeleteCoursewaresAnimation", typeof(UIDelegateProgress<List<CoursewareListItem>>), typeof(CloudListView), new PropertyMetadata(default(UIDelegateProgress<List<CoursewareListItem>>),
3 (d, e) => ((UIDelegateProgress<List<CoursewareListItem>>)e.NewValue)?.StartAsync(((CloudListView)d).ExecuteDeleteCoursewaresAnimation)));
4
5 private async Task ExecuteDeleteCoursewaresAnimation(List<CoursewareListItem> coursewares)
6 {
7 List<Storyboard> storyboards = new List<Storyboard>();
8 foreach (var courseware in coursewares)
9 {
10 var listBoxItem = DocumentsControl.ItemContainerGenerator.ContainerFromItem(courseware) as ListBoxItem;
11 var border = listBoxItem?.VisualDescendant<Border>();
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<List<CoursewareListItem>> value)
25 {
26 element.SetValue(DeleteCoursewaresAnimationProperty, value);
27 }
28
29 public static UIDelegateProgress<List<CoursewareListItem>> GetDeleteCoursewaresAnimation(DependencyObject element)
30 {
31 return (UIDelegateProgress<List<CoursewareListItem>>)element.GetValue(DeleteCoursewaresAnimationProperty);
32 }
动画的执行,怎么转为有同步等待呢?动画完成只有通过触发事件Completed才能确定。
如何将动画转化为同步,可参考之前写的博客:C# 异步转同步
1 /// <summary>
2 /// 执行动画
3 /// </summary>
4 /// <param name="storyboard"></param>
5 /// <returns></returns>
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 /// <summary>
2 /// UI交互处理-提供可调用UI交互的操作
3 /// </summary>
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 /// <summary>
19 /// 执行
20 /// </summary>
21 public void Execute()
22 {
23 var delegateProgress = new UIDelegateProgress();
24 delegateProgress.ProgressCompleted += () =>
25 {
26 _delegateProgress = null;
27 };
28 DelegateProgress = delegateProgress;
29 }
30
31 /// <summary>
32 /// 异步执行
33 /// 交互处理完成并回调
34 /// </summary>
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 /// <summary>
52 /// UI交互处理-提供可同步调用UI交互的操作
53 /// </summary>
54 /// <typeparam name="T">输入/输出类型</typeparam>
55 public class UIDelegateAction<T> : BindableObject, IUIDelegateAction<T>
56 {
57 private UIDelegateProgress<T> _delegateProgress;
58
59 public UIDelegateProgress<T> DelegateProgress
60 {
61 get => _delegateProgress;
62 private set
63 {
64 _delegateProgress = value;
65 OnPropertyChanged();
66 }
67 }
68 /// <summary>
69 /// 执行
70 /// </summary>
71 public void Execute(T parameter)
72 {
73 var delegateProgress = new UIDelegateProgress<T>(parameter);
74 delegateProgress.ProgressCompleted += () =>
75 {
76 _delegateProgress = null;
77 };
78 DelegateProgress = delegateProgress;
79 }
80 /// <summary>
81 /// 异步执行
82 /// 交互处理完成并回调
83 /// </summary>
84 public async Task ExecuteAsync(T parameter)
85 {
86 AutoResetEvent autoResetEvent = new AutoResetEvent(false);
87
88 var delegateProgress = new UIDelegateProgress<T>(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 /// <summary>
101 /// 异步执行并返回结果
102 /// </summary>
103 public async Task<T> ExecuteWithResultAsync()
104 {
105 AutoResetEvent autoResetEvent = new AutoResetEvent(false);
106
107 var delegateProgress = new UIDelegateProgress<T>();
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 /// <summary>
123 /// UI交互处理-提供可同步调用UI交互的操作
124 /// </summary>
125 /// <typeparam name="TInput">输入类型</typeparam>
126 /// <typeparam name="TOut">输出类型</typeparam>
127 public class UIDelegateAction<TInput, TOut> : BindableObject, IUIDelegateAction<TInput, TOut>
128 {
129 private UIDelegateProgress<TInput, TOut> _delegateProgress;
130
131 public UIDelegateProgress<TInput, TOut> DelegateProgress
132 {
133 get => _delegateProgress;
134 private set
135 {
136 _delegateProgress = value;
137 OnPropertyChanged();
138 }
139 }
140 /// <summary>
141 /// 执行
142 /// </summary>
143 public void Execute(TInput parameter)
144 {
145 var delegateProgress = new UIDelegateProgress<TInput, TOut>(parameter);
146 delegateProgress.ProgressCompleted += () =>
147 {
148 _delegateProgress = null;
149 };
150 DelegateProgress = delegateProgress;
151 }
152
153 /// <summary>
154 /// 执行并返回结果
155 /// </summary>
156 public TOut ExecuteWithResult(TInput parameter)
157 {
158 var delegateProgress = new UIDelegateProgress<TInput, TOut>(parameter);
159 delegateProgress.ProgressCompleted += () =>
160 {
161 _delegateProgress = null;
162 };
163 DelegateProgress = delegateProgress;
164 return delegateProgress.Result;
165 }
166
167 /// <summary>
168 /// 异步执行并返回结果
169 /// </summary>
170 public async Task<TOut> ExecuteWithResultAsync(TInput parameter)
171 {
172 var delegateProgress = new UIDelegateProgress<TInput, TOut>(parameter);
173 await SetDelegateProgress(delegateProgress);
174 return delegateProgress.Result;
175 }
176 private async Task SetDelegateProgress(UIDelegateProgress<TInput, TOut> 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 /// <summary>
191 /// UI交互处理接口
192 /// </summary>
193 public interface IUIDelegateAction
194 {
195
196 UIDelegateProgress DelegateProgress { get; }
197
198 /// <summary>
199 /// 执行
200 /// </summary>
201 void Execute();
202
203 /// <summary>
204 /// 异步执行
205 /// </summary>
206 Task ExecuteAsync();
207 }
208
209 /// <summary>
210 /// UI交互处理接口
211 /// </summary>
212 /// <typeparam name="T">输入/输出类型</typeparam>
213 public interface IUIDelegateAction<T>
214 {
215 UIDelegateProgress<T> DelegateProgress { get; }
216
217 /// <summary>
218 /// 执行
219 /// </summary>
220 void Execute(T parameter);
221
222 /// <summary>
223 /// 异步执行
224 /// </summary>
225 Task ExecuteAsync(T parameter);
226
227 /// <summary>
228 /// 异步执行并返回结果
229 /// </summary>
230 Task<T> ExecuteWithResultAsync();
231 }
232
233 /// <summary>
234 /// UI交互处理接口
235 /// </summary>
236 /// <typeparam name="TInput">输入类型</typeparam>
237 /// <typeparam name="TOut">输出类型</typeparam>
238 public interface IUIDelegateAction<TInput, TOut>
239 {
240 UIDelegateProgress<TInput, TOut> DelegateProgress { get; }
241
242 /// <summary>
243 /// 执行
244 /// </summary>
245 void Execute(TInput parameter);
246
247 /// <summary>
248 /// 执行并返回结果
249 /// </summary>
250 TOut ExecuteWithResult(TInput parameter);
251
252 /// <summary>
253 /// 异步执行并返回结果
254 /// </summary>
255 Task<TOut> ExecuteWithResultAsync(TInput parameter);
256 }
UIDelegateProgress:
1 /// <summary>
2 /// 委托进度
3 /// </summary>
4 public class UIDelegateProgress
5 {
6 public event Action ProgressCompleted;
7
8 /// <summary>
9 /// UI委托处理
10 /// </summary>
11 /// <param name="uiTask"></param>
12 public async void StartAsync(Func<Task> 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 /// <summary>
29 /// UI委托处理
30 /// </summary>
31 /// <param name="uiTask"></param>
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 /// <summary>
50 /// 委托进度
51 /// </summary>
52 public class UIDelegateProgress<T>
53 {
54 public event Action ProgressCompleted;
55
56 /// <summary>
57 /// 输入参数
58 /// </summary>
59 public T Parameter { get; set; }
60
61 /// <summary>
62 /// 输出参数
63 /// </summary>
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 /// <summary>
76 /// UI委托处理
77 /// </summary>
78 /// <param name="uiTask"></param>
79 public void Start(Action<T> 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 /// <summary>
96 /// UI委托处理
97 /// </summary>
98 /// <param name="uiTask"></param>
99 public async void StartAsync(Func<T, Task> uiTask)
100 {
101 try
102 {
103 await uiTask.Invoke(Parameter);
104 }
105 catch (InvalidOperationException e)
106 {
107 Log.Error("UI交互处理,产生异常!", e);
108 }
109 finally
110 {
111 ProgressCompleted?.Invoke();
112 }
113 }
114
115 /// <summary>
116 /// UI委托处理
117 /// </summary>
118 /// <param name="uiTask"></param>
119 public void Start(Func<T> uiTask)
120 {
121 try
122 {
123 Result = uiTask.Invoke();
124 }
125 catch (InvalidOperationException e)
126 {
127 Log.Error("UI交互处理,产生异常!", e);
128 }
129 finally
130 {
131 ProgressCompleted?.Invoke();
132 }
133 }
134
135 /// <summary>
136 /// UI委托处理
137 /// </summary>
138 /// <param name="uiTask"></param>
139 public async void StartAsync(Func<Task<T>> uiTask)
140 {
141 try
142 {
143 Result = await uiTask.Invoke();
144 }
145 catch (InvalidOperationException e)
146 {
147 Log.Error("UI交互处理,产生异常!", e);
148 }
149 finally
150 {
151 ProgressCompleted?.Invoke();
152 }
153 }
154 }
155
156 /// <summary>
157 /// 委托进度
158 /// </summary>
159 public class UIDelegateProgress<TInput, TOut>
160 {
161 public event Action ProgressCompleted;
162
163 /// <summary>
164 /// 输入参数
165 /// </summary>
166 public TInput Parameter { get; set; }
167
168 /// <summary>
169 /// 输出参数
170 /// </summary>
171 public TOut Result { get; set; }
172
173 public UIDelegateProgress(TInput parameter)
174 {
175 Parameter = parameter;
176 }
177
178 /// <summary>
179 /// UI委托处理
180 /// </summary>
181 /// <param name="uiTask"></param>
182 public async void StartAsync(Func<TInput, Task<TOut>> uiTask)
183 {
184 try
185 {
186 Result = await uiTask.Invoke(Parameter);
187 }
188 catch (InvalidOperationException e)
189 {
190 Log.Error("UI交互处理,产生异常!", e);
191 }
192 finally
193 {
194 ProgressCompleted?.Invoke();
195 }
196 }
197
198 /// <summary>
199 /// UI委托处理
200 /// </summary>
201 /// <param name="uiTask"></param>
202 public void Start(Func<TOut> uiTask)
203 {
204 try
205 {
206 uiTask.Invoke();
207 }
208 catch (InvalidOperationException e)
209 {
210 Log.Error("UI交互处理,产生异常!", e);
211 }
212 finally
213 {
214 ProgressCompleted?.Invoke();
215 }
216 }
217
218 /// <summary>
219 /// UI委托处理
220 /// </summary>
221 /// <param name="uiTask"></param>
222 public void Start(Func<TInput, TOut> uiTask)
223 {
224 try
225 {
226 Result = uiTask.Invoke(Parameter);
227 }
228 catch (InvalidOperationException e)
229 {
230 Log.Error("UI交互处理,产生异常!", e);
231 }
232 finally
233 {
234 ProgressCompleted?.Invoke();
235 }
236 }
237 }
关键字:UI分离,交互与数据分离,动画同步,单元测试
基础才是重中之重~delegate里的Invoke和BeginInvoke
Invoke和BeginInvoke都是调用委托实体的方法,前者是同步调用,即它运行在主线程上,当Invode处理时间长时,会出现阻塞的情况,而BeginInvod是异步操作,它会从新开启一个线程,所以不会租塞主线程,在使用BeginInvoke时,如果希望等待执行的结果 ,可以使用EndInvoke来实现,这在.net framework4.5之后,被封装成了async+await来实现,代码更简洁,更容易理解。
delegate void test();
static void Main(string[] args)
{
test ts = new test(TestDelegate);
ts.Invoke(); //不会产生新线程
Console.WriteLine("hello");
}
internal static void TestDelegate()
{
Thread.Sleep(1000);
}
此时,在主线程中出现了1秒的租塞,因为Invoke是同步的。
下面再来看一下BeginInvoke的实现
delegate string test();
static void Main(string[] args)
{
test ts = new test(TestDelegate);
IAsyncResult result = ts.BeginInvoke(null,null); //会在新线程中执行
string resultstr=ts.EndInvoke(result);
Console.WriteLine(resultstr);
}
internal static string TestDelegate()
{
Thread.Sleep(1000);
return "hello"
}
上面的代码会在新线程中执行,并且平会对主线程产生租塞,同时它可以获取自己的返回值,使用EndInvoke实现!
感谢阅读!小小的知识点我们也要好好理解。
将不确定变为确定系列~目录(“机器最能证明一切”)
本系列文章主要是我在工作中,遇到一些不能主观判断的问题,最后在电脑上去证明我的理解是否正确,这也是题目“将不确定变成确定”的由来。
记得我在上大学时,老师说过一句话:“机器最能证明一切”,这句话现在看来,确实很经典。
将不确定变为确定系列~目录(“机器最能证明一切”)
第二回 将不确定变成确定~我想监视我的对象,如果是某个值,就叫另一些方法自动运行
第四回 将不确定变成确定~LINQ查询两种写法,性能没有影响,优化查询应该是“按需查询”
第五回 将不确定变成确定~LINQ DBML模型可以对应多个数据库吗
第七回 将不确定变为确定~一切归总为“二”(C#中的位运算有啥用)
第八回 将不确定变为确定~接口应该是什么
第九回 将不确定变为确定~表达式树是否可以有个集合,条件过滤有了新方向
第十回 将不确定变为确定~表达式树是否可以有个集合,条件过滤有了新方向续(新增了OR和AND查询)
第十一回 将不确定变为确定~整形变量是否可以进行位运算(像枚举类型一样)
第十二回 将不确定变为确定~static被翻译成静态,你是否赞同
第十三回 将不确定变为确实~请自己搞清楚异常页面的设置方法(网上那些资料说的基本都有问题!)
第十四回 基础才是重中之重~老赵写的CodeTimer是代码性能测试的利器
第十五回 将不确定变为确定~LINQ查询包含对不同数据上下文上所定义项的引用
第十六回 将不确定变为确定~真的是SqlDataReader引起的超时?
第十七回 将不确定变为确定~SQLSERVER是否可以进行位运算?
第十八回 将不确定变为确定~从DBML文件中是否可以快速定位到指定类?
第十九回 将不确定变为确定~头压缩是否有必要,MVC如何实现头压缩
第二十回 将不确定变为确定~Linq-Distinct()方法是否可以对复杂结果集进行去重?
第二十一回 将不确定变为确定系列~Linq的批量操作靠的住吗?
第二十二回 将不确定变为确定~MVC3的ValidateInput属性失灵了
第二十三回 将不确定变为确定~异常被抛出的顺序
第二十四回 将不确定变为确定~对象被new后什么时候会抛System.NullReferenceException
第二十五回 将不确定变为确定~Linq to SQL不能随机排序吗?
第二十六回 将不确定变为确定~transactionscope何时提升为分布式事务?
第二十七回 将不确定变为确定~transactionscope何时提升为分布式事务~续
第二十八回 将不确定变为确定~transactionscope何时提升为分布式事务~再续(避免引起不必要的MSDTC)
第二十九回 将不确定变为确定~transactionscope何时提升为分布式事务~大结局
第三十回 将不确定变为确定~Flag特性的枚举是否可以得到Description信息
第三十一回 将不确定变为确定~Razor视图中是否可以嵌套JS代码
第三十二回 将不确定变为确定~DateTime.MinValue和MaxValue引发的异常
第三十四回 将不确定变为确定~Linq的Group是否可以根据多个字段进行分组
第三十五回 将不确定变为确定~感谢异或,是你让我彻底摆脱“否定式”
第三十六回 将不确定变为确定~类中的属性何时被执行
第三十七回 将不确定变为确定~transactionscope何时提升为分布式事务~SQL2005与SQL2008不同
第三十八回 将不确定变为确定~transactionscope何时提升为分布式事务?(sql2005数据库解决提升到MSDTC的办法)
第三十九回 将不确定变成确定~Uri文本文件不用浏览器自动打开,而是下载到本地
爱上MVC3系列~全局异常处理与异常日志
在MVC3网站的global.asax中的Application_Start方法里,有这样一段代码
1 RegisterGlobalFilters(GlobalFilters.Filters);
它的主要使用是将全局过滤器进行注册,而全局过滤器可以在RegisterGlobalFilters这个方法里进行设置,如代码:
1 /// <summary>
2 /// 全局过滤器(特性)
3 /// </summary>
4 /// <param name="filters"></param>
5 public static void RegisterGlobalFilters(GlobalFilterCollection filters)
6 {
7 // ExceptionLogAttribute继承自HandleError,主要作用是将异常信息写入日志系统中
8 filters.Add(new Web.Commons.Attributes.ExceptionLogAttribute());
9 //默认的异常记录类
10 filters.Add(new HandleErrorAttribute());
11 }
当我们设置完上面两块后,现在如果想记异常日志,那我们需要完善一下ExceptionLogAttribute这个类,看代码:
1 /// <summary>
2 /// 异常持久化类
3 /// </summary>
4 public class ExceptionLogAttribute : HandleErrorAttribute
5 {
6 /// <summary>
7 /// 触发异常时调用的方法
8 /// </summary>
9 /// <param name="filterContext"></param>
10 public override void OnException(ExceptionContext filterContext)
11 {
12
13 string message = string.Format("消息类型:{0}<br>消息内容:{1}<br>引发异常的方法:{2}<br>引发异常的对象:{3}<br>异常目录:{4}<br>异常文件:{5}"
14 , filterContext.Exception.GetType().Name
15 , filterContext.Exception.Message
16 , filterContext.Exception.TargetSite
17 , filterContext.Exception.Source
18 , filterContext.RouteData.GetRequiredString("controller")
19 , filterContext.RouteData.GetRequiredString("action"));
20 VLog.VLogFactory.CreateVLog().ErrorLog(message); //TODO:将 ex 错误对象记录到系统日志模块
21 base.OnException(filterContext);
22 }
23 }
大家可以看到,在上面类中,有个CreateVLog的方法,它是干什么用的呢,实事上,它就是我们的日志功能类,可以对日志进行不同类型的持久化,这我会在单独一讲中去说明它。
今天主要就是MVC3中的全局异常的记录方法,呵呵。
基础才是重中之重~lock和monitor的区别
Monitor的介绍
1.Monitor.Enter(object)方法是获取锁,Monitor.Exit(object)方法是释放锁,这就是Monitor最常用的两个方法,当然在使用过程中为了避免获取锁之后因为异常,致锁无法释放,所以需要在try{} catch(){}之后的finally{}结构体中释放锁(Monitor.Exit())。
2.Monitor的常用属性和方法:
- Enter(Object) 在指定对象上获取排他锁。
- Exit(Object) 释放指定对象上的排他锁。
- IsEntered 确定当前线程是否保留指定对象锁。
- Pulse 通知等待队列中的线程锁定对象状态的更改。
- PulseAll 通知所有的等待线程对象状态的更改。
- TryEnter(Object) 试图获取指定对象的排他锁。
- TryEnter(Object, Boolean) 尝试获取指定对象上的排他锁,并自动设置一个值,指示是否得到了该锁。
- Wait(Object) 释放对象上的锁并阻止当前线程,直到它重新获取该锁。
Lock的介绍
1.Lock关键字实际上是一个语法糖,它将Monitor对象进行封装,给object加上一个互斥锁,A进程进入此代码段时,会给object对象加上互斥锁,此时其他B进程进入此代码段时检查object对象是否有锁?如果有锁则继续等待A进程运行完该代码段并且解锁object对象之后,B进程才能够获取object对象为其加上锁,访问代码段。
2.Lock关键字封装的Monitor对象结构如下:
try
{
Monitor.Enter(obj);
dosomething();
}
catch(Exception ex)
{
}
finally
{
Monitor.Exit(obj);
}
3. lock的对象应该是私有的静态对象
private static object obj = new object();
public void something()
{
lock (obj)
{
dosomething();
}
}
Monitor和Lock的区别
1.Lock是Monitor的语法糖。
2.Lock只能针对引用类型加锁。
3.Monitor能够对值类型进行加锁,实质上是Monitor.Enter(object)时对值类型装箱。
感谢各位的阅读!
将不确定变成确定~我想监视我的对象,如果是某个值,就叫另一些方法自动运行
名称有点饶,不是很好理解,但我喜欢这种大白话,不喜欢书所翻译过来的话,呵呵!
今天要把一个不确定的问题解决,问题是:一个程序中,有一个属性,如果它为true时,我希望把另一些方法自动运行,这是可以通过订阅事件来实现的,对吗?经过我的测试确实是这样的,呵呵。
事件一个一直叫我们头痛的话题,一个能不用就不用的东西,我们程序员为什么那么怕“事件”呢?我来分析几个原因
1 对本身的概念不是很理解
2 对它的作用不是很清晰,可能书上说不到点上,个人认为
3 平时用的少,所以对它更加陌生
今天,我就和大家一起再学习一个C#的事件
一说事件,就不行不说委托,这两者到底是什么关系呢,在我看来,委托就是一个类,而事件就是这个类的一个实例,呵呵,这样大家就容易理解了吧
事件由事件数据源,事件所发生的类和事件订阅者们组成,“事件订阅者们”就是说,一个事件可以被多个订阅都订阅。
开始写代码了,代码最能说明问题:
事件源类:
1 /// <summary>
2 /// 事件源
3 /// </summary>
4 internal class KeyEventArgs : EventArgs
5 {
6 private char keyChar;
7 public KeyEventArgs(char keyChar)
8 : base()
9 {
10 this.keyChar = keyChar;
11 }
12
13 public char KeyChar
14 {
15 get
16 {
17 return keyChar;
18 }
19 }
20 }
一都是以EventArgs 结尾的,其中EventArgs 本身它是所有事件源类的基类,它不提供任何事件源信息,如果有个性化的事件信息,需要去派生它
接下来看,发生事件的类,我们的事件就在这里发生,在什么时候,什么情况下发生,都来自这里。
1 /// <summary>
2 /// 事件发生的类
3 /// </summary>
4 internal class KeyInputMonitor
5 {
6 // 创建一个委托,返回类型为avoid,两个参数
7 public delegate void KeyDownEventHandler(object sender, KeyEventArgs e);
8 // 将创建的委托和特定事件关联,在这里特定的事件为OnKeyDown
9 public event KeyDownEventHandler OnKeyDown;
10
11 public void Run()
12 {
13 bool finished = false;
14 do
15 {
16 Console.WriteLine("Input a char");
17 string response = Console.ReadLine();
18
19 char responseChar = (response == "") ? ' ' : char.ToUpper(response[0]);
20 switch (responseChar)
21 {
22 case 'X':
23 finished = true;
24 break;
25 default:
26 // 得到按键信息的参数
27 KeyEventArgs keyEventArgs = new KeyEventArgs(responseChar);
28 // 触发事件
29 OnKeyDown(this, keyEventArgs);
30 break;
31 }
32 } while (!finished);
33 }
34 }
功能就是输入一个字符,当为X时,退出,当不为X时,去触发一个事件,事件源数据为“输入的字符”
到这里,这个事件还没有任何功能,就相当于,我去卖东西,东西已经摆在台上了,但还没有人来买,好,现在是时候有顾客来了
1 /// <summary>
2 /// 显示中文的接收类
3 /// </summary>
4 internal class EventReceiverForChina
5 {
6 public EventReceiverForChina(KeyInputMonitor monitor)
7 {
8 // 产生一个委托实例并添加到KeyInputMonitor产生的事件列表中
9 monitor.OnKeyDown += new KeyInputMonitor.KeyDownEventHandler(this.Echo);
10 }
11 private void Echo(object sender, KeyEventArgs e)
12 {
13 Console.WriteLine("您输入的字条是: {0}", e.KeyChar);
14 }
15 }
这里订阅事件时,我们使用+=就可以了,事实上就是建立一个委托类型的新事件实例而以。
在前台调用时,可以这样:
1 // 实例化一个事件发送器,并声明一个EventReceiverForChina类型的订阅者 2 KeyInputMonitor monitor = new KeyInputMonitor(); 3 EventReceiverForChina eventReceiverForChina = new EventReceiverForChina(monitor); 4 monitor.Run();
运行的结果就是当你去Run()时,eventReceiverForChina 类型时的某个方法也被执行了,怎么样,实现了我们今天的话题了吧,其实这就是事件的订阅机制,事实上在软件开发中非常有用。 需要注意的是,一般事件的返回类型都是void,当然,这也是很正常的,因为事件就是去做某些事件,它不知道管后果的。呵呵。
祝您晚——来个好梦吧!
回到目录
将不确定变成确定~LINQ DBML模型可以对应多个数据库吗
答案是肯定的,一个DBML模型可以对应多个数据库,只要数据库中的表与模型中定义的表结构完成相同,就可以这个技术,我们可以用来开发一些通用的功能模块,如通过后台管理模块,我们将一些通用表进行抽象,如,对用户,角色,部门,菜单等进行抽象,将它的模块建立在我们的公用项目中,然后对这个模型进行操作,在建立DBContext上下文时,我们需要保留一个连接字符串,即,真正的项目中用哪个库,我们这个串就指定哪个库就行了。
如图,后台表结构
然后,它个模块可以用别对应包含这几个表结构的数据库,呵呵
通用后台系统我会在之后的文章中给大家讲到,今天先看一下它的图像,呵呵
大家可以看到,对于每一个项目的功能和作用,今天我就不说了,我会再下一篇文章中单独说它,总之,今天要知识的就是:一个DBML可以对应多个数据库,前提是数据库的表结构好和DBML模型中存在的表结构相同。





浙公网安备 33010602011771号