上篇我们已经了解了MVVM的框架结构和运行原理。这里我们来看一下伟大的双向数据绑定。
说到双向绑定,大家比较熟悉的应该就是AngularJS了,几乎所有的AngularJS 系列教程的开篇几章都要涉及到,真的是很好用。
表达的效果很简单:就是在界面的操作对数据模型的修改能实时反映到数据;而数据的变更能实时展现到界面。即视图数据模型(ViewModel)和视图(View)之间的双向绑定和触发。
我们来操作一个试试看:
第一步:先写一个Model,里面包含我们需要的数据信息,代码如下:
1 /// <summary>
2 /// 用户信息
3 /// </summary>
4 public class UserInfoModel : ObservableObject
5 {
6 private String userName;
7 /// <summary>
8 /// 用户名称
9 /// </summary>
10 public String UserName
11 {
12 get { return userName; }
13 set { userName = value; RaisePropertyChanged(()=>UserName); }
14 }
15
16 private Int64 userPhone;
17 /// <summary>
18 /// 用户电话
19 /// </summary>
20 public Int64 UserPhone
21 {
22 get { return userPhone; }
23 set { userPhone = value; RaisePropertyChanged(() => UserPhone); }
24 }
25
26 private Int32 userSex;
27 /// <summary>
28 /// 用户性别
29 /// </summary>
30 public Int32 UserSex
31 {
32 get { return userSex; }
33 set { userSex = value; RaisePropertyChanged(()=>UserSex); }
34 }
35
36 private String userAdd;
37 /// <summary>
38 /// 用户地址
39 /// </summary>
40 public String UserAdd
41 {
42 get { return userAdd; }
43 set { userAdd = value; RaisePropertyChanged(() => UserAdd); }
44 }
45 }
第二步:写一个ViewModel,包含了View所需要的命令和属性:
1 public class BothWayBindViewModel:ViewModelBase
2 {
3 public BothWayBindViewModel()
4 {
5 UserInfo = new UserInfoModel();
6 }
7
8 #region 属性
9
10 private UserInfoModel userInfo;
11 /// <summary>
12 /// 用户信息
13 /// </summary>
14 public UserInfoModel UserInfo
15 {
16 get { return userInfo; }
17 set { userInfo = value; RaisePropertyChanged(() => UserInfo); }
18 }
19
20 #endregion
21
22 #region 命令
23 #endregion
24 }
第三步:在ViewModelLocator中注册我们写好的ViewModel:SimpleIoc.Default.Register<BothWayBindViewModel>();
1 /*
2 In App.xaml:
3 <Application.Resources>
4 <vm:ViewModelLocator xmlns:vm="clr-namespace:MVVMLightDemo"
5 x:Key="Locator" />
6 </Application.Resources>
7
8 In the View:
9 DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelName}"
10
11 You can also use Blend to do all this with the tool's support.
12 See http://www.galasoft.ch/mvvm
13 */
14
15 using GalaSoft.MvvmLight;
16 using GalaSoft.MvvmLight.Ioc;
17 using Microsoft.Practices.ServiceLocation;
18
19 namespace MVVMLightDemo.ViewModel
20 {
21 /// <summary>
22 /// This class contains static references to all the view models in the
23 /// application and provides an entry point for the bindings.
24 /// </summary>
25 public class ViewModelLocator
26 {
27 /// <summary>
28 /// Initializes a new instance of the ViewModelLocator class.
29 /// </summary>
30 public ViewModelLocator()
31 {
32 ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
33
34 #region Code Example
35 ////if (ViewModelBase.IsInDesignModeStatic)
36 ////{
37 //// // Create design time view services and models
38 //// SimpleIoc.Default.Register<IDataService, DesignDataService>();
39 ////}
40 ////else
41 ////{
42 //// // Create run time view services and models
43 //// SimpleIoc.Default.Register<IDataService, DataService>();
44 ////}
45 #endregion
46
47 SimpleIoc.Default.Register<MainViewModel>();
48 SimpleIoc.Default.Register<WelcomeViewModel>();
49 SimpleIoc.Default.Register<BothWayBindViewModel>();
50 }
51
52 #region 实例化
53 public MainViewModel Main
54 {
55 get
56 {
57 return ServiceLocator.Current.GetInstance<MainViewModel>();
58 }
59 }
60
61 public WelcomeViewModel Welcome
62 {
63 get
64 {
65 return ServiceLocator.Current.GetInstance<WelcomeViewModel>();
66 }
67 }
68
69 public BothWayBindViewModel BothWayBind
70 {
71 get
72 {
73 return ServiceLocator.Current.GetInstance<BothWayBindViewModel>();
74 }
75 }
76
77 #endregion
78
79 public static void Cleanup()
80 {
81 // TODO Clear the ViewModels
82 }
83 }
84 }
第四步:编写View(注意标红的代码):
1 <Window x:Class="MVVMLightDemo.View.BothWayBindView"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 DataContext="{Binding Source={StaticResource Locator},Path=BothWayBind}"
5 Title="BothWayBindView" Height="300" Width="300">
6 <Grid>
7 <StackPanel Orientation="Vertical" Margin="10,10,0,0">
8 <StackPanel Orientation="Horizontal" >
9 <TextBlock Text="请输入姓名:" ></TextBlock>
10 <TextBox Text="{Binding UserInfo.UserName,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" Width="200" ></TextBox>
11 </StackPanel>
12
13 <StackPanel Margin="0,10,0,0" Orientation="Horizontal" >
14 <TextBlock Text="Hello " ></TextBlock>
15 <TextBlock Text="{Binding UserInfo.UserName}" ></TextBlock>
16 </StackPanel>
17
18 <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal" >
19 </StackPanel>
20
21 </StackPanel>
22 </Grid>
23 </Window>
效果如图所示(当修改输入框的内容的时候,对应绑定数据相应改变,并触发对UI的修改,所以下面那行文字也相应改变改变。):
前面我们已经了解到了,RaisePropertyChanged的作用是当数据源改变的时候,会触发PropertyChanged事件达到通知UI更改的目的(ViewModel => View)。
那View上的变化要怎么通知到数据源呢:
View中文本框绑定内容如下:{Binding UserInfo.UserName,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay},
大家会看到多了两个属性,一个是UpdateSourceTrigger,一个是Mode属性。
UpdateSourceTrigger的作用是 当做何种改变的时候通知数据源我们做了改变。
枚举类型 | 效果 |
Default | 默认值(默认为LostFocuse) |
Explicit | 当应用程序调用 UpdateSource 方法时生效 |
LostFocus | 失去焦点的时候触发 |
PropertyChanged | 数据属性改变的时候触发 |
这边我们直接使用 PropertyChanged,当UI数据改变的时候,我们再通知到数据源去做修改。
还有一个属性就是Mode,他有五个参数:
枚举类型 | 效果 |
OneWay | 源发生变化,数据就会从源流向目标 |
OneTime | 绑定会将数据从源发送到目标;但是,仅当启动了应用程序或 DataContext 发生更改时才会如此操作,因此,它不会侦听源中的更改通知。 |
OneWayToSource | 绑定会将数据从目标发送到源 |
TwoWay | 绑定会将源数据发送到目标,但如果目标属性的值发生变化,则会将它们发回给源 |
Default | 绑定的模式根据实际情况来定,如果是可编辑的就是TwoWay,只读的就是OneWay |
这边明显有很多种选择,明确一点的是,我们是想把View上的变化同步到ViewModel(Target => Source),所以使用OneWayToSource、TwoWay、Default或者不写都可以。
严谨点应该使用OneWayToSource。因为是文本框,属于可以编辑控件,所以 Default指向的是TwoWay。
下面还有一个TextBlock,仅仅用于显示的,所以不需要目标对源的修改,无需指定就默认是OneWay,当源改变的时候,会通知它进行修改。