知道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。