【学习笔记】Silverlight框架:Jounce(6)——Command和ViewModel
MVVM支持可以说是每个框架的标配了。
Jounce的这个部分也是参考了其他的几个框架,这里面东西比较多:
ActionCommand基本上就是Prism里的DelegateCommand,不过只有个泛型的版本。
BaseNotify则类似Prism里的NotificationObject,里面多了个方法,比较新奇:
1 /// <summary>
2 /// Raise a property change and infer the frame from the stack
3 /// </summary>
4 /// <remarks>
5 /// May not work on some systems (64-bit, for example, not yet supported by Silverlight).
6 /// </remarks>
7 [MethodImpl(MethodImplOptions.NoInlining)]
8 public virtual void RaisePropertyChanged()
9 {
10 var frames = new System.Diagnostics.StackTrace();
11 for (var i = 0; i < frames.FrameCount; i++)
12 {
13 var frame = frames.GetFrame(i).GetMethod() as MethodInfo;
14 if (frame != null)
15 if (frame.IsSpecialName && frame.Name.StartsWith("set_"))
16 {
17 RaisePropertyChanged(frame.Name.Substring(4));
18 return;
19 }
20 }
21 throw new InvalidOperationException("NotifyPropertyChanged() can only by invoked within a property setter.");
22 }
可见这个方法只能在set里用,而且并不是任何环境下都能用的,别的框架里只有在MVVM Light中看到过。
BaseViewModel继承自BaseNotify并实现了IViewModel接口,IViewModel里定义了用于初始化、导航和控制VisualState的方法,是绑定ViewModel和View的基础,从代码上来看,如果要通过Jounce框架进行绑定的话,ViewModel需继承自BaseViewModel。
BaseEntityViewModel继承自BaseViewModel并实现了INotifyDataErrorInfo接口,提供用于验证的基本方法和一个CommitCommand用于提交。
在Jounce中对于ViewModel需添加ExportAsViewModelAttribute,View需添加ExportAsViewAttribute,两者的关联则通过ViewModelRoute进行。
还是看代码来的实在,新建个解决方案,结构如下:
EntityViewModel继承自BaseEntityViewModel,对应的View是EntityView,用于演示如何使用Validate和Committe,代码如下:
1 [ExportAsViewModel("EntityViewModel")]
2 public class EntityViewModel : BaseEntityViewModel
3 {
4 private string _name;
5 private string _age;
6
7 public string Name
8 {
9 get { return _name; }
10 set
11 {
12 _name = value;
13 this.RaisePropertyChanged();
14 this.ValidateName();
15 }
16 }
17
18 public string Age
19 {
20 get { return _age; }
21 set
22 {
23 _age = value;
24 this.RaisePropertyChanged();
25 this.ValidateAge();
26 }
27 }
28
29 private void ValidateName()
30 {
31 this.ClearErrors(() => this.Name);
32 if (string.IsNullOrWhiteSpace(this.Name))
33 {
34 this.SetError(() => this.Name, "请输入姓名!");
35 }
36 }
37
38 private void ValidateAge()
39 {
40 this.ClearErrors(() => this.Age);
41 if (string.IsNullOrWhiteSpace(this.Age))
42 {
43 this.SetError(() => this.Age, "请输入年龄!");
44 }
45 else if (!Regex.IsMatch(this.Age, "^[0-9]*$"))
46 {
47 this.SetError(() => this.Age, "年龄需为数字!");
48 }
49 }
50
51 protected override void ValidateAll()
52 {
53 this.ValidateName();
54 this.ValidateAge();
55 }
56
57 protected override void OnCommitted()
58 {
59 MessageBox.Show(string.Format("姓名:{0}\r\n年龄:{1}", this.Name, this.Age));
60
61 this.Name = string.Empty;
62 this.ClearErrors(() => this.Name);
63
64 this.Age = string.Empty;
65 this.ClearErrors(() => this.Age);
66 }
67 }
EntityViewModel定义了2个简单的属性和验证方法,并重写了基类的ValidateAll和OnCommitted方法,当CommitCommand被调用时,会先调用ValidateAll,如果验证通过则会调用OnCommitted。
1 <UserControl x:Class="JounceMVVM.EntityView"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6 mc:Ignorable="d"
7 d:DesignHeight="300" d:DesignWidth="400" >
8
9 <Grid x:Name="LayoutRoot" Background="White">
10 <Grid.RowDefinitions>
11 <RowDefinition Height="Auto"/>
12 <RowDefinition Height="Auto"/>
13 <RowDefinition Height="Auto"/>
14 </Grid.RowDefinitions>
15 <Grid.ColumnDefinitions>
16 <ColumnDefinition Width="Auto"/>
17 <ColumnDefinition Width="150"/>
18 </Grid.ColumnDefinitions>
19 <TextBlock Text="姓名" Margin="2"/>
20 <TextBox Margin="2" Grid.Column="1"
21 Text="{Binding Name,Mode=TwoWay,NotifyOnValidationError=True, ValidatesOnDataErrors=True}"/>
22
23 <TextBlock Text="年龄" Margin="2" Grid.Row="1"/>
24 <TextBox Margin="2" Grid.Column="1" Grid.Row="1"
25 Text="{Binding Age,Mode=TwoWay,NotifyOnValidationError=True, ValidatesOnDataErrors=True}"/>
26
27 <Button Grid.Row="2" Margin="5" Content="提交" Command="{Binding CommitCommand}"/>
28 </Grid>
29 </UserControl>
Xaml里无非是一些绑定。
1 public partial class EntityView : UserControl
2 {
3 public EntityView()
4 {
5 InitializeComponent();
6 }
7 }
需要注意的是,这里我们并没有为EntityView添加ExportAsViewAttribute,这个页面会被嵌套在MainPage里,并在那里进行导出。
1 <UserControl x:Class="JounceMVVM.MainPage"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6 xmlns:local="clr-namespace:JounceMVVM"
7 mc:Ignorable="d"
8 d:DesignHeight="300" d:DesignWidth="400">
9
10 <Grid x:Name="LayoutRoot" Background="White">
11 <StackPanel HorizontalAlignment="Left">
12 <Border BorderThickness="1" BorderBrush="Black" Margin="2">
13 <StackPanel Orientation="Horizontal">
14 <Button Content="刷新时间"
15 Margin="5" HorizontalAlignment="Left"
16 Command="{Binding RefreshTimeCommand}"/>
17 <TextBlock Margin="10" FontSize="20" Text="{Binding Time}" />
18 </StackPanel>
19 </Border>
20
21 <Border BorderThickness="1" BorderBrush="Black" Margin="2">
22 <local:EntityView x:Name="EntityView"/>
23 </Border>
24 </StackPanel>
25 </Grid>
26 </UserControl>
给嵌入的EntityView定义Name,并在后台代码中定义导出视图:
1 [ExportAsView(typeof(MainPage), IsShell = true)]
2 public partial class MainPage : UserControl
3 {
4 public MainPage()
5 {
6 InitializeComponent();
7 }
8
9 [ExportAsView("EntityView")]
10 public UserControl ExportEntityView
11 {
12 get { return this.EntityView; }
13 }
14 }
MainViewModel是直接继承自BaseViewModel,自定义了一个简单的Command并重写了ActivateView方法。
1 [ExportAsViewModel(typeof(MainViewModel))]
2 public class MainViewModel : BaseViewModel
3 {
4 public DateTime Time { get; private set; }
5
6 public ICommand RefreshTimeCommand { get; private set; }
7
8 public MainViewModel()
9 {
10 this.RefreshTimeCommand = new ActionCommand<object>(
11 p =>
12 {
13 this.Time = DateTime.Now;
14 this.RaisePropertyChanged(() => Time);
15 });
16 this.RefreshTimeCommand.Execute(null);
17 }
18
19 protected override void ActivateView(string viewName, IDictionary<string, object> viewParameters)
20 {
21 this.EventAggregator.Publish("EntityView".AsViewNavigationArgs());
22 }
23 }
最后还有Bindings用于指定ViewModel和View的关联。
1 public class Bindings
2 {
3 [Export]
4 public ViewModelRoute MainBinding
5 {
6 get
7 {
8 return ViewModelRoute.Create<MainViewModel, MainPage>();
9 }
10 }
11
12 [Export]
13 public ViewModelRoute EntityBinding
14 {
15 get
16 {
17 return ViewModelRoute.Create("EntityViewModel", "EntityView");
18 }
19 }
20 }
可以看到的是ViewModelRoute、ExportAsViewModelAttribute和ExportAsViewAttribute都提供了不同的重载方法。
ExportAsViewModelAttribute和ExportAsViewAttribute都是继承自ExportAttribute,所以会被MEF导出。
框架的粗略流程是这样的:先找到设置了IsShell = true的View,这里就是MainPage,通过事件机制激活,绑定到关联的MainViewModel,调用MainViewModel的ActivateView方法,再通过事件机制激活EntityView,绑定到关联的EntityViewModel。更具体的内部流程下次再记。
半路和尚 by 超时空饭盒 is licensed under a Creative Commons 署名-非商业性使用-相同方式共享 3.0 Unported License.
基于halfwaymonk.cnblogs.com上的作品创作。