【转】Mvvm Light Toolkit for wpf/silverlight系列之数据绑定

Mvvm的框架的实现依赖于完善的数据绑定机制,因此熟练使用mvvm就必须熟练掌握WPF/SL的数据绑定机制。下面我们从几个方面来看看mvvm数据绑定与传统的.net控件使用方式有什么不一样;

 

一、给控件属性赋值

首先我们定义个公有的普通属性:

1 public string TextProperty { get; set; }

传统的.net控件的赋值都是在页面的后台代码中通过以下方式实现:

1  this.TextBox1.Text = TextProperty;

数据绑定方式需要在Xaml中的Text属性中添加绑定语法:

1 <TextBox x:Name="TextBox1" Text="{Binding TextProperty}"/>

如果TextProperty在后台更改了,要想在UI也反映更改,传统的方式仍然是重新给TextBox1.Text赋值,那么数据绑定方式该怎么做呢,这时需要在ViewModel中实现INotifyPropertyChanged接口,通过触发PropertyChanged事件达到通知UI更改的目的;这里我们定义的ViewModel都继承自ViewModelBase,ViewModelBase封装在MvvmLight框架中,它已经实现了INotifyPropertyChanged接口,因此我们在定义ViewModel属性时,只需要调用RaisePropertyChanged(PropertyName)就可以进行属性更改通知了;具备属性更改通知的属性定义如下:

 1        // DatePicker 选中日期
 2         private DateTime _SelectedDate;
 3         public DateTime SelectedDate
 4         {
 5             get
 6             {
 7                 return _SelectedDate;
 8             }
 9 
10             set
11             {
12                 if (_SelectedDate == value)
13                     return;
14 
15                 _SelectedDate = value;
16 
17                 RaisePropertyChanged("SelectedDate");
18             }
19         }

或者使用代码段mvvminpc自动生成ViewModel属性。

ViewModelBase中INotifyPropertyChanged接口实现部分如下:

 

 1         /// <summary>
 2         /// Occurs when a property value changes.
 3         /// </summary>
 4         public event PropertyChangedEventHandler PropertyChanged;
 5         
 6         [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate",
 7             Justification = "This cannot be an event")]
 8         protected virtual void RaisePropertyChanged(string propertyName)
 9         {
10             VerifyPropertyName(propertyName);
11 
12             var handler = PropertyChanged;
13 
14             if (handler != null)
15             {
16                 handler(this, new PropertyChangedEventArgs(propertyName));
17             }
18         }
19 
20         [Conditional("DEBUG")]
21         [DebuggerStepThrough]
22         public void VerifyPropertyName(string propertyName)
23         {
24             var myType = this.GetType();
25             if (myType.GetProperty(propertyName) == null)
26             {
27                 throw new ArgumentException("Property not found", propertyName);
28             }
29         }

两种方式实现起来都很简单,看起来都差不多,那么使用数据绑定有什么好处呢:

    1、绑定方式使业务逻辑和UI设计可以完全分离,因此没必要在后台代码xaml.cs中操作控件属性,而绑定的属性都定义在ViewModel中,因此我们完全可以将xaml.cs删除,ViewModel与View(UI)的关联通过UI控件的DataContext属性实现,通常我们都将ViewModel绑定到UI的顶层布局控件上(如Window,UserControl),从而使得ViewModel对整个UI可见。

    2、简单的赋值可能看不出绑定的优势,而且要让View反映ViewModel的更改还必须在ViewModel中实现INotifyPropertyChanged接口,但是在这种情况,只要属性更改了,View控件就会响应更改,ViewModel是面向属性进行编程,而传统方式需要面对控件编程,如果属性更改,需要查找对应的UI控件,然后重新设置控件属性;数据绑定在对列表数据绑定时,优势更为突出,比如数据源的数据更改了,传统方式必须重新绑定才能反映数据源的更改,而绑定方式数据源更改甚至数据源部分字段数据的更改,UI都可以自动响应更改

    3、传统方式必须给控件命名才能在后台代码使用,而绑定方式控件与ViewModel的属性绑定,使用ViewModel的属性跟使用控件的属性一样,可以不用给控件命名

 

二、读取控件属性值

通常在UI控件属性更改时,需要在逻辑处理的地方重新获取控件的值,传统方式必须找到控件,然后获取控件属性的值,而在数据绑定的方式中只需要在绑定语法中添加绑定方式Mode=TwoWay或OneWayToSource,这样,只要UI中绑定目标的值更改,就会触发ViewModel中属性的Set方法对属性进行赋值,保证任何时候使用属性都是UI的最新值,xaml中绑定语法如下:

1 <TextBox Text="{Binding BindText,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" />
2 <TextBox Text="{Binding BindText,UpdateSourceTrigger=PropertyChanged,Mode=OneWayToSource}"/>

注意:

    SL中不支持OneWayToSource,只能使用TwoWay;

    WPF中对于TextBox、Combobox等控件,默认(不写Mode)的绑定方式是TwoWay,而SL中好像默认都是OneWay,如果你搞不清楚,索性都写全了就不会混淆了;

    对于更新数据源的时机,TextBox默认都是LostFocus方式,也就是失去焦点时才会更新源,WPF中可以指定更新源的时机,Wpf示例中为TextBox属性更改时同时更新数据源,也就是每输一个字母,数据源都会更改一次,而SL中不支持UpdateSourceTrigger,因此只能使用默认的LostFocus方式。

 

本章示例中我们创建了3个文本框来测试绑定方式,3个文本框以不同绑定方式绑定到相同数据源,更改其中一个文本框,我们可以观察其他2个文本框的值变化情况

 

另一个示例中我演示了省市联动的下拉框绑定效果,WPF中xaml代码如下:

 

 1       <ComboBox x:Name="cmbProvince" Margin="3" ItemsSource="{Binding Provinces}" 
 2                 Width="100" SelectedItem="{Binding Province}"
 3                 DisplayMemberPath="ProvinceName" SelectedValuePath="Cities" SelectedValue="{Binding Cities}"/>
 4       <TextBlock Margin="3" Text="城市"/>
 5       <ComboBox Margin="3" ItemsSource="{Binding Cities}" Width="100"
 6                 DisplayMemberPath="CityName" SelectedValuePath="CityName" SelectedValue="{Binding City}" />
 7       <ComboBox Margin="3" ItemsSource="{Binding Cities}" 
 8                 DataContext="{Binding SelectedItem,ElementName=cmbProvince}" Width="100"
 9                 DisplayMemberPath="CityName" SelectedValuePath="CityName" 
10                 SelectedValue="{Binding DataContext.City,ElementName=LayoutRoot}" />

后台代码:

 

  1        // 省市列表
  2         public List<Model.Province> Provinces
  3         {
  4             get
  5             {
  6                 return new List<Model.Province>
  7                 {
  8                     new Model.Province{ 
  9                         ProvinceName="湖北",
 10                         Cities=new List<Model.City>{
 11                             new Model.City{CityName="武汉",Population=1000},
 12                             new Model.City{CityName="宜昌",Population=1000},
 13                             new Model.City{CityName="孝感",Population=1000},
 14                         } ,
 15                         Capital = "武汉"},
 16                     new Model.Province{ 
 17                         ProvinceName="广东",
 18                         Cities=new List<Model.City>{
 19                             new Model.City{CityName="深圳",Population=1000},
 20                             new Model.City{CityName="广州",Population=1000},
 21                             new Model.City{CityName="珠海",Population=1000},
 22                         } ,
 23                         Capital = "广州"},
 24                     new Model.Province{ 
 25                         ProvinceName="湖南",
 26                         Cities=new List<Model.City>{
 27                             new Model.City{CityName="长沙",Population=1000},
 28                             new Model.City{CityName="湘潭",Population=1000},
 29                             new Model.City{CityName="岳阳",Population=1000},
 30                         } ,
 31                         Capital = "长沙"},
 32                 };
 33             }
 34         }
 35 
 36        // 选中省份的城市列表
 37         private List<Model.City> _Cities;
 38         public List<Model.City> Cities
 39         {
 40             get { return _Cities; }
 41             set
 42             {
 43                 if (_Cities == value)
 44                     return;
 45 
 46                 _Cities = value;
 47 
 48                 RaisePropertyChanged("Cities");
 49 
 50                 // 列表更改时,默认选中省会城市
 51                 _City = Province.Cities.SingleOrDefault(p => p.CityName == Province.Capital);
 52                 RaisePropertyChanged("City");
 53 
 54                 if (Cities != null)
 55                 {
 56                     _RadioButtons = (from c in Cities
 57                                      select new ToggleButtonViewModel
 58                                      {
 59                                          Content = c.CityName,
 60                                          IsChecked = false,
 61                                      }).ToList();
 62 
 63                     _CheckButtons = (from c in Cities
 64                                      select new ToggleButtonViewModel
 65                                      {
 66                                          Content = c.CityName,
 67                                          IsChecked = false,
 68                                      }).ToList();
 69                 }
 70 
 71                 RaisePropertyChanged("RadioButtons");
 72                 RaisePropertyChanged("CheckButtons");
 73             }
 74         }
 75 
 76         // 选中省份
 77         private Model.Province _Province;
 78         public Model.Province Province
 79         {
 80             get { return _Province; }
 81             set
 82             {
 83                 if (_Province == value)
 84                     return;
 85 
 86                 _Province = value;
 87 
 88                 RaisePropertyChanged("Province");
 89             }
 90         }
 91 
 92         // 选中城市
 93         private Model.City _City;
 94         public Model.City City
 95         {
 96             get { return _City; }
 97             set
 98             {
 99                 if (_City == value)
100                     return;
101 
102                 _City = value;
103 
104                 RaisePropertyChanged("City");
105             }
106         }
107 
108         // 选中城市
109         private string _CityName;
110         public string CityName
111         {
112             get { return _CityName; }
113             set
114             {
115                 if (_CityName == value)
116                     return;
117 
118                 _CityName = value;
119 
120                 RaisePropertyChanged("CityName");
121             }
122         }

 

可以看出,通过绑定到属性就可以完成下拉列表的联动,这里要注意的是SL中不能通过绑定到CityName来设置城市列表的选中项,如果省份列表更改,CityName就不能关联到Combobox的选中项,原因可能是SL通过引用查找选中项,WPF可以通过字符串相等来查找选中项,因此SL是通过SelectedItem来绑定选中项,可以看出来,在WPF中,我们的绑定可以比较随意,而在SL中要特别小心,一个小小的不同可能让你花很长时间找不到问题的原因,这时就要仔细看帮助文档了

 

SL设置下拉列表选中项方法:

 

1     // 列表更改时,默认选中省会城市
2     _City = Province.Cities.SingleOrDefault(p => p.CityName == "武汉");
3     RaisePropertyChanged("City");

 

WPF中除了以上方法,可以直接给_CityName赋值字符串来设置列表选中项

 

1     // 列表更改时,默认选中省会城市
2      _CityName = "武汉";
3     RaisePropertyChanged("CityName");

 

三、控件事件处理

传统方式处理控件事件都是在xaml中定义事件属性,然后在后台xaml.cs中添加事件处理方法:

Xaml:

1 <Button Content="按钮" Click="Button_Click" /> 

xaml.cs:

1 private void Button_Click(object sender, RoutedEventArgs e){}

在mvvm中,可以通过Command绑定进行按钮点击事件的处理:

xaml:

1 <Button Content="按钮" Command="{Binding ButtonCommand}"/>

ViewModel:

 1         // 按钮点击命令
 2         public ICommand ButtonCommand
 3         {
 4             get
 5             {
 6                 return new RelayCommand(
 7                     () => System.Windows.MessageBox.Show("当前时间:" + Clock)
 8                     );
 9             }
10         }

本示例中,可以通过点击Button按钮弹出MessageBox显示当前时钟。

当然,我们不光要处理Button的点击事件,还需要处理其他一些事件类型,我们会在下一章节专门介绍mvvm命令和事件

 

四、 列表类型控件的处理

这里的列表类型指的是包括Combobox、ListBox、Datagrid等所有能绑定到集合的控件,到这里我们更需要了解一下WPF/SL的控件的模板和样式与绑定之间的关系。

比如ListBox控件,依然绑定到之前定义的Provinces列表,Xaml中定义如下: 

1 <ListBox ItemsSource="{Binding Provinces}" DisplayMemberPath="ProvinceName"/> 

xaml定义的显示DisplayMemberPath就是列表要显示的字段名,执行结果显示:

湖北

广东

湖南

ListBox其实有一个默认的ItemTemplate,它以一个Content控件来显示DisplayMemberPath定义的字段的内容,我们可以自定义ItemTemplate让ListBox显示更多的内容,注意ItemTemplate与DisplayMemberPath不能同时定义

 1     <ListBox ItemsSource="{Binding Provinces}">
 2         <ListBox.ItemTemplate>
 3           <DataTemplate>
 4             <StackPanel Orientation="Horizontal">
 5               <TextBlock Text="{Binding ProvinceName}"/>
 6               <TextBlock Margin="5,0,2,0" Text="省会:"/>
 7               <TextBlock Text="{Binding Capital}"/>
 8             </StackPanel>
 9           </DataTemplate>
10         </ListBox.ItemTemplate>
11       </ListBox>

显示结果如下:

 

我们可以将ListBox看成是集合数据的显示方式,一旦ItemsSource被绑定到集合List<Model.Province> ,那么ListBox的每个ItemTemplate的DataContext对应集合中的每个Model.Province对象,ItemTemplate的内容是呈现Model.Province对象的方式;这是我的理解方式,每个列表控件呈现数据方式不一样,但是数据源集合可以是一样的,UI设计人员则可以根据需要选择不同的数据显示方式。同样是List集合的数据源,让我们看看示例中另一种列表显示方式:

 

 

这个是一个完成分组和排序功能的Datagrid,同样只是简单的绑定到List集合,后台不用额外的代码,所有功能都在Xaml中完成:

 

首先在UI中定义CollectionViewSource资源,在这里定义排序和分组的规则

 

WPF中定义如下:

 1 <Window.Resources>
 2     <CollectionViewSource x:Key="ProductsGroup" Source="{Binding Products}">
 3       <CollectionViewSource.GroupDescriptions>
 4         <PropertyGroupDescription PropertyName="ProductDate" />
 5       </CollectionViewSource.GroupDescriptions>
 6       <CollectionViewSource.SortDescriptions>
 7         <scm:SortDescription PropertyName="ProductDate" Direction="Descending" />
 8         <scm:SortDescription PropertyName="ID" Direction="Ascending" />
 9       </CollectionViewSource.SortDescriptions>
10     </CollectionViewSource>
11   </Window.Resources>
12 
13 ...
14 ...
15 
16 <DataGrid DataContext="{StaticResource ProductsGroup}" AutoGenerateColumns="False"  
17                 ItemsSource="{Binding}" SelectedItem="{Binding SelectedProduct}" CanUserAddRows="False">
18         <DataGrid.GroupStyle>
19           <GroupStyle>
20             <GroupStyle.HeaderTemplate>
21               <DataTemplate>
22                 <TextBlock  x:Name="txt"  Background="LightBlue" FontWeight="Bold" 
23                             Foreground="White" Margin="1" Padding="4,2,0,2" 
24                             Text="{Binding Name,StringFormat='生产日期:/{0/}'}" />
25               </DataTemplate>
26             </GroupStyle.HeaderTemplate>
27           </GroupStyle>
28         </DataGrid.GroupStyle>
29         <DataGrid.Columns>
30           <DataGridTextColumn Binding="{Binding ID}" Header="编号" />
31           <DataGridTextColumn Binding="{Binding Name}" Header="名称" />
32           <DataGridTextColumn Binding="{Binding Desc}" Header="说明" />
33         </DataGrid.Columns>
34       </DataGrid>

SL中定义如下:

 1 <UserControl.Resources>
 2       <CollectionViewSource x:Key="ProductsGroup" Source="{Binding Products}">
 3           <CollectionViewSource.GroupDescriptions>
 4               <PropertyGroupDescription PropertyName="ProductDate" />
 5           </CollectionViewSource.GroupDescriptions>
 6           <CollectionViewSource.SortDescriptions>
 7               <scm:SortDescription PropertyName="ProductDate" Direction="Descending" />
 8               <scm:SortDescription PropertyName="ID" Direction="Ascending" />
 9           </CollectionViewSource.SortDescriptions>
10       </CollectionViewSource>
11   </UserControl.Resources>
12 
13 ...
14 ...
15 
16 <sdk:DataGrid DataContext="{StaticResource ProductsGroup}" AutoGenerateColumns="False"  
17                 ItemsSource="{Binding}" SelectedItem="{Binding SelectedProduct}" Width="300" >
18         <sdk:DataGrid.Columns>
19             <sdk:DataGridTextColumn Binding="{Binding ID}" Header="编号" />
20             <sdk:DataGridTextColumn Binding="{Binding Name}" Header="名称" />
21             <sdk:DataGridTextColumn Binding="{Binding Desc}" Header="说明" />
22         </sdk:DataGrid.Columns>
23       </sdk:DataGrid>

后台代码:

 1 public List<Model.Product> Products
 2         {
 3             get
 4             {
 5                 return new List<Model.Product>
 6                 {
 7                     new Model.Product{ID=11,Name="P1",ProductDate=new DateTime(2010,1,1),Desc="产品1"},
 8                     new Model.Product{ID=1,Name="P2",ProductDate=new DateTime(2010,1,2),Desc="产品2"},
 9                     new Model.Product{ID=13,Name="P3",ProductDate=new DateTime(2010,1,1),Desc="产品3"},
10                     new Model.Product{ID=7,Name="P4",ProductDate=new DateTime(2010,1,2),Desc="产品4"},
11                     new Model.Product{ID=21,Name="P5",ProductDate=new DateTime(2010,1,1),Desc="产品5"},
12                     new Model.Product{ID=19,Name="P6",ProductDate=new DateTime(2010,1,1),Desc="产品6"},
13                     new Model.Product{ID=41,Name="P7",ProductDate=new DateTime(2010,1,3),Desc="产品7"},
14                 };
15             }
16         }

最后还有一种特殊的列表控件,那就是TreeView ,TreeView 用一个专门绑定层次关系的ItemTemplate来显示树状结构,它就是HierarchicalDataTemplate,WPF模板定义方式如下:

 

1       <TreeView  x:Name="treeview1" ItemsSource="{Binding TreeData}">
2         <TreeView.ItemTemplate>
3           <HierarchicalDataTemplate ItemsSource="{Binding Children}">
4             <TextBlock Text="{Binding NodeName}"/>
5           </HierarchicalDataTemplate>
6         </TreeView.ItemTemplate>
7       </TreeView>

SL中定义方法一样,不同是TreeView所在的命名控件不一样

数据源的类定义如下:

1     public class TreeNode
2     {
3         public string NodeID { get; set; }
4         public string NodeName { get; set; }
5 
6         public List<TreeNode> Children { get; set; }
7     }

Treeview绑定显示结果如下:

 

使用ItemsControl控件还可以用来处理动态生成控件的情况,只需将控件的属性映射到Model属性,然后在ItemTemplate中将控件属性绑定到Model属性,就可以动态创建控件列表;通过自定义ItemsControl的ItemsPanel模板,就可以控制ItemsControl内的控件排列方式,比如示例中使用WrapPanel来显示一个图片浏览器,当一行显示的图片总宽度超过WrapPanel宽度,就会自动换行显示,WPF中xaml代码如下:

 

 1       <ListBox x:Name="ListBox1" ItemsSource="{Binding ListBoxData}" Width="300">
 2         <ListBox.ItemsPanel>
 3           <ItemsPanelTemplate>
 4             <WrapPanel Width="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"/>
 5           </ItemsPanelTemplate>
 6         </ListBox.ItemsPanel>
 7         <ListBox.ItemTemplate>
 8           <DataTemplate>
 9             <StackPanel>
10               <Image Source="{Binding ImageSource}" Width="96"/>
11               <TextBlock HorizontalAlignment="Center" Text="{Binding Text}"/>
12             </StackPanel>
13           </DataTemplate>
14         </ListBox.ItemTemplate>
15       </ListBox>

WrapPanel 的宽度绑定到它的父元素ListBox的宽度,这么写的好处是ItemsPanelTemplate可以当作公共资源定义在资源文件中供多处同时使用,而在SL中,由于不支持FindAncestor绑定语法,需要指定父元素的ElementName来绑定,或者直接使用数字

 

本章主要介绍MvvmLight中数据绑定的实现,但并不涉及WPF/SL数据绑定的全部内容,下章我们将主要介绍MvvmLight中命令和事件的用法。

本章示例代码如下:

 http://download.csdn.net/source/3254233

原文:http://blog.csdn.net/duanzilin/article/details/6399365

 

 

转载于:https://www.cnblogs.com/smlusm/archive/2013/05/13/3076569.html

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 扫一扫,分享海报

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值