知道MVVM这个词已经很久,但是一直没有去认识一下它,恰好这几天正在做点Silverlight的东西玩,顺便就也见识一下MVVM。
准备工作:
1、Silverlight 4包括SDK 及 Silverlight 4 Toolkit April 2010
2、创建实体类(将在后面的DEMO中使用)People.cs:
public class People { public string Name { get; set; } public int Age { get; set; } }3、创建实体类相应View视图PeopleView.xaml:
<Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="姓名:" /> <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}" /> <TextBlock Grid.Row="1" Grid.Column="0" Text="年龄:" /> <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Age}" /> </Grid>4、创建ViewModel(PropertyChangedBase来自这里),使它实现INotifyPropertyChanged接口:
public class PeopleViewModel : PropertyChangedBase { public PeopleViewModel() { Peoples = new ObservableCollection<People>(); for (int i = 10; i < 30; i++) { Peoples.Add(new People { Name = "test" + i.ToString(), Age = i }); } } private People selectedPeople; public People SelectedPeople { get { return selectedPeople; } set { selectedPeople = value; this.NotifyPropertyChanged(p => p.SelectedPeople); } } public ObservableCollection<People> Peoples { get; set; } }
接下来开始DEMO
一、绑定DataGrid当前选中的行SelectedItem
创建DEMO1页面,Xaml代码为:
<UserControl x:Class="LWME.MVVMExperience.Views.Demo1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" xmlns:v="clr-namespace:LWME.MVVMExperience.Views" xmlns:vm="clr-namespace:LWME.MVVMExperience.ViewModel" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <UserControl.Resources> <vm:PeopleViewModel x:Key="vm1" /> </UserControl.Resources> <Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource vm1}"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="400" /> </Grid.ColumnDefinitions> <sdk:DataGrid Grid.Column="0" ItemsSource="{Binding Peoples}" SelectedItem="{Binding SelectedPeople, Mode=TwoWay}" /> <StackPanel Grid.Column="1" HorizontalAlignment="Center"> <TextBlock Text="当前选中:" /> <v:PeopleView DataContext="{Binding SelectedPeople}" /> </StackPanel> </Grid> </UserControl>
运行它,可以看到选择DataGrid某一行的时候,右边相应的详细信息也跟着变了,但这里并没有写任何代码,是不是很神奇呢?
二、绑定选择的多行SelectedItems
上面由于SelectedItem同时有get、set访问器,所以支持直接绑定。但是SelectedItems是不支持set访问器的,所以无法直接绑定。有人提到使用Command来解决这一问题,现在便用一下他的方法吧:
首先,实现ICommand接口:
public class RelayCommand<T> : ICommand { /// <summary> /// Occurs when changes occur that affect whether the command should execute. /// </summary> public event EventHandler CanExecuteChanged; Predicate<T> canExecute; Action<T> executeAction; bool canExecuteCache; /// <summary> /// Initializes a new instance of the <see cref="RelayCommand"/> class. /// </summary> /// <param name="executeAction">The execute action.</param> public RelayCommand(Action<T> executeAction) : this(executeAction, null) { } /// <summary> /// Initializes a new instance of the <see cref="RelayCommand"/> class. /// </summary> /// <param name="executeAction">The execute action.</param> /// <param name="canExecute">The can execute.</param> public RelayCommand(Action<T> executeAction, Predicate<T> canExecute) { this.executeAction = executeAction; this.canExecute = canExecute; } #region ICommand Members /// <summary> /// Defines the method that determines whether the command /// can execute in its current state. /// </summary> /// <param name="parameter"> /// Data used by the command. /// If the command does not require data to be passed, /// this object can be set to null. /// </param> /// <returns> /// true if this command can be executed; otherwise, false. /// </returns> public bool CanExecute(object parameter) { if (canExecute == null) return true; bool tempCanExecute = canExecute((T)parameter); if (canExecuteCache != tempCanExecute) { canExecuteCache = tempCanExecute; if (CanExecuteChanged != null) { CanExecuteChanged(this, EventArgs.Empty); } } return canExecuteCache; } /// <summary> /// Defines the method to be called when the command is invoked. /// </summary> /// <param name="parameter"> /// Data used by the command. /// If the command does not require data to be passed, /// this object can be set to null. /// </param> public void Execute(object parameter) { executeAction((T)parameter); } #endregion }
接下来,修改PeopleViewModel,定义一个Command属性以绑定到DataGrid的SelectionChange事件,同时添加其他需要用于多选的属性:
public class PeopleViewModel : PropertyChangedBase { public PeopleViewModel() { Peoples = new ObservableCollection<People>(); for (int i = 10; i < 30; i++) { Peoples.Add(new People { Name = "test" + i.ToString(), Age = i }); } SelectionChangedCommand = new RelayCommand<IList>(SelectionChanged); } private People selectedPeople; public People SelectedPeople { get { return selectedPeople; } set { selectedPeople = value; this.NotifyPropertyChanged(p => p.SelectedPeople); } } public ObservableCollection<People> Peoples { get; set; } #region 多选 private IEnumerable<People> selectedPeoples; public IEnumerable<People> SelectedPeoples { get { return selectedPeoples; } set { selectedPeoples = value; this.NotifyPropertyChanged(p => p.SelectedPeoples); } } private int numberOfItemsSelected; public int NumberOfItemsSelected { get { return numberOfItemsSelected; } set { numberOfItemsSelected = value; this.NotifyPropertyChanged(p => p.NumberOfItemsSelected); } } public RelayCommand<IList> SelectionChangedCommand { get; set; } private void SelectionChanged(IList items) { if (items != null) { SelectedPeoples = items.Cast<People>(); NumberOfItemsSelected = items.Count; } else { SelectedPeoples = null; NumberOfItemsSelected = 0; } } #endregion }
再定义一个Converter用于显示多个选择的People到textblock上:
public class SelectedPeoplesConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { IEnumerable<People> SelectedPeoples = value as IEnumerable<People>; if (SelectedPeoples != null) { return string.Join( "|", SelectedPeoples.Select(p => string.Format("姓名:{0},年龄:{1} ", p.Name, p.Age)) ); } return string.Empty; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
再添加我们的Demo2.xaml,这里Command的绑定用到了System.Windows.Interactivity.dll:
<UserControl x:Class="LWME.MVVMExperience.Views.Demo2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:v="clr-namespace:LWME.MVVMExperience.Views" xmlns:vm="clr-namespace:LWME.MVVMExperience.ViewModel" xmlns:cv="clr-namespace:LWME.MVVMExperience.Common" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <UserControl.Resources> <vm:PeopleViewModel x:Key="vm1" /> <cv:SelectedPeoplesConverter x:Key="pc1" /> </UserControl.Resources> <Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource vm1}"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="400" /> </Grid.ColumnDefinitions> <sdk:DataGrid x:Name="peoplesDataGrid" Grid.Column="0" ItemsSource="{Binding Peoples}" SelectedItem="{Binding SelectedPeople, Mode=TwoWay}"> <i:Interaction.Triggers> <i:EventTrigger EventName="SelectionChanged"> <i:InvokeCommandAction Command="{Binding SelectionChangedCommand, Mode=OneWay}" CommandParameter="{Binding SelectedItems, ElementName=peoplesDataGrid}" /> </i:EventTrigger> </i:Interaction.Triggers> </sdk:DataGrid> <StackPanel Grid.Column="1" HorizontalAlignment="Center"> <StackPanel Orientation="Horizontal"> <TextBlock Text=" 总共选中了:" /> <TextBlock Text="{Binding NumberOfItemsSelected}" /> <TextBlock Text="条记录,分别为:" /> </StackPanel> <TextBlock Text="{Binding SelectedPeoples, Converter={StaticResource pc1}}" TextWrapping="Wrap" /> <TextBlock Text=" 当前选中:" /> <v:PeopleView DataContext="{Binding SelectedPeople}" /> </StackPanel> </Grid> </UserControl>
运行一下,选中多个列的时候,右边同时显示出了多个被选中的People数据以及选中的数目。
总结一下,良好的分离使MVVM看起来蛮优雅的,而且熟悉了以后,有利于简化代码以及提高开发效率,over。