微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

 

一、给控件属性赋值

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

[c-sharp]  view plain copy
  1. public string TextProperty { getset; }  

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

copy
    this.TextBox1.Text = TextProperty;  

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

[xhtml]  copy
    <TextBox x:Name="TextBox1" Text="{Binding TextProperty}"/>  

 

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

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

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

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

 

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

 

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

    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中绑定语法如下:

copy
    TextBox Text="{Binding BindText,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" />  
  1. />  

注意:

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

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

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

 

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

 

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

 

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

后台代码

copy
    // 省市列表  
  1. public List<Model.Province> Provinces  
  2.  {  
  3. get  
  4. return new List<Model.Province>  
  5.          {  
  6.              new Model.Province{   
  7.                  ProvinceName="湖北",  
  8.                  Cities=new List<Model.City>{  
  9.                      new Model.City{CityName="武汉",Population=1000},248); line-height:18px">                      new Model.City{CityName="宜昌",153); background-color:inherit; font-weight:bold">new Model.City{CityName="孝感",248); line-height:18px">                  } ,  
  10.                  Capital = "武汉"},108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px">                  ProvinceName="广东",153); background-color:inherit; font-weight:bold">new Model.City{CityName="深圳",153); background-color:inherit; font-weight:bold">new Model.City{CityName="广州",153); background-color:inherit; font-weight:bold">new Model.City{CityName="珠海",108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px">                  Capital = "广州"},108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px">                  ProvinceName="湖南",153); background-color:inherit; font-weight:bold">new Model.City{CityName="长沙",153); background-color:inherit; font-weight:bold">new Model.City{CityName="湘潭",153); background-color:inherit; font-weight:bold">new Model.City{CityName="岳阳",108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px">                  Capital = "长沙"},248); line-height:18px">          };  
  11.      }  
  12.  }  
  13. // 选中省份的城市列表  
  14. private List<Model.City> _Cities;  
  15. public List<Model.City> Cities  
  16. get { return _Cities; }  
  17. set  
  18. if (_Cities == value)  
  19. return;  
  20.          _Cities = value;  
  21.          RaisePropertyChanged("Cities");  
  22.          // 列表更改时,认选中省会城市  
  23.          _City = Province.Cities.SingleOrDefault(p => p.CityName == Province.Capital);  
  24.          RaisePropertyChanged("City");  
  25. if (Cities !=               _RadioButtons = (from c in Cities  
  26.                               select new ToggleButtonviewmodel  
  27.                               {  
  28.                                   Content = c.CityName,  
  29.                                   IsChecked = false,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px">                               }).ToList();  
  30.              _CheckButtons = (from c in Cities  
  31.                               select new ToggleButtonviewmodel  
  32.                               {  
  33.                                   Content = c.CityName,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px">                                   IsChecked =                                }).ToList();  
  34.          }  
  35.          RaisePropertyChanged("RadioButtons");  
  36.          RaisePropertyChanged("CheckButtons");  
  37.  // 选中省份  
  38. private Model.Province _Province;  
  39. public Model.Province Province  
  40. return _Province; }  
  41. if (_Province == value)  
  42.          _Province = value;  
  43.          RaisePropertyChanged("Province");  
  44. // 选中城市  
  45. private Model.City _City;  
  46. public Model.City City  
  47. return _City; }  
  48. if (_City == value)  
  49.          _City = value;  
  50. private string _CityName;  
  51. string CityName  
  52. return _CityName; }  
  53. if (_CityName == value)  
  54.          _CityName = value;  
  55.          RaisePropertyChanged("CityName");  
  56.  }  

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

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

 

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

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

copy
     _CityName = "武汉";  
  1. RaisePropertyChanged("CityName");  

 

三、控件事件处理

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

Xaml:

copy
    Button Content="按钮" Click="Button_Click" />   
 

xaml.cs:

copy
    void Button_Click(object sender, RoutedEventArgs e){}  

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

xaml:

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

viewmodel:

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

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

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

 

四、 列表类型控件的处理

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

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

 

copy
    ListBox ItemsSource="{Binding Provinces}" displayMemberPath="ProvinceName"/>   

 

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

湖北

广东

湖南

 

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

copy
    ListBox ItemsSource="{Binding Provinces}">  
  1.     ListBox.ItemTemplate>  
  2.       DataTemplate         StackPanel Orientation="Horizontal"           TextBlock Text="{Binding ProvinceName}"           TextBlock Margin="5,2,0" Text="省会:"TextBlock Text="{Binding Capital}"</StackPanel   ListBox>  

显示结果如下:

 

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

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

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

WPF中定义如下:

 

copy
    Window.ResourcesCollectionViewSource x:Key="ProductsGroup" Source="{Binding Products}"CollectionViewSource.GroupDescriptionsPropertyGroupDescription PropertyName="ProductDate"        CollectionViewSource.sortDescriptions         scm:SortDescription PropertyName="ProductDate" Direction="Descending" scm:SortDescription PropertyName="ID" Direction="Ascending" CollectionViewSource ...  
  1. ...  
  2. DataGrid DataContext="{StaticResource ProductsGroup}" AutoGenerateColumns="False"    
  3.                 ItemsSource="{Binding}" SelectedItem="{Binding SelectedProduct}" CanUserAddRows="False"DataGrid.GroupStyleGroupStyle             GroupStyle.HeaderTemplate                                TextBlock  x:Name="txt"  Background="LightBlue" FontWeight="Bold"   
  4.                             Foreground="White" Margin="1" Padding="4,2"   
  5.                             Text="{Binding Name,StringFormat='生产日期:/{0/}'}" DataGrid.ColumnsDataGridTextColumn Binding="{Binding ID}" Header="编号" DataGridTextColumn Binding="{Binding Name}" Header="名称" DataGridTextColumn Binding="{Binding Desc}" Header="说明" DataGrid>  
 

SL中定义如下:

 

copy
    UserControl.Resources             sdk:DataGrid DataContext="{StaticResource ProductsGroup}" AutoGenerateColumns="False"    
  1.                 ItemsSource="{Binding}" SelectedItem="{Binding SelectedProduct}" Width="300" sdk:DataGrid.Columnssdk:DataGridTextColumn Binding="{Binding ID}" Header="编号" sdk:DataGridTextColumn Binding="{Binding Name}" Header="名称" sdk:DataGridTextColumn Binding="{Binding Desc}" Header="说明" sdk:DataGrid>  

后台代码

copy
    public List<Model.Product> Products  
  1.         {  
  2.                          {  
  3.                 new List<Model.Product>  
  4.                 {  
  5.                     new Model.Product{ID=11,Name="P1",ProductDate=new DateTime(2010,1,1),Desc="产品1"},248); line-height:18px">                     new Model.Product{ID=1,Name="P2",2),Desc="产品2"},153); background-color:inherit; font-weight:bold">new Model.Product{ID=13,Name="P3",Desc="产品3"},153); background-color:inherit; font-weight:bold">new Model.Product{ID=7,Name="P4",Desc="产品4"},153); background-color:inherit; font-weight:bold">new Model.Product{ID=21,Name="P5",Desc="产品5"},153); background-color:inherit; font-weight:bold">new Model.Product{ID=19,Name="P6",Desc="产品6"},153); background-color:inherit; font-weight:bold">new Model.Product{ID=41,Name="P7",3),Desc="产品7"},248); line-height:18px">                 };  
  6.             }  
  7.         }  

 

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

 

copy @H_502_2190@
    TreeView  x:Name="treeview1" ItemsSource="{Binding TreeData}"   TreeView.ItemTemplate     HierarchicalDataTemplate ItemsSource="{Binding Children}"TextBlock Text="{Binding NodeName}"HierarchicalDataTemplateTreeView>  

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

数据源的类定义如下:

copy
    class TreeNode  
  1. {  
  2. string NodeID { set; }  
  3. string NodeName { set; }  
  4. public List<TreeNode> Children {  }  

Treeview绑定显示结果如下:

 

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

 

copy
    ListBox x:Name="ListBox1" ItemsSource="{Binding ListBoxData}" Width="300"ListBox.ItemsPanelItemsPanelTemplateWrapPanel Width="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"Image Source="{Binding ImageSource}" Width="96"TextBlock HorizontalAlignment="Center" Text="{Binding Text}">  

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

 

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

 

本章示例代码如下:

 

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐