浅谈WPF中的MVVM
一些基础知识
- WPF中的一个重要特性就是数据绑定(data binding),简单的说就是你有一些数据要显示给用户,你可以把数据和xaml进行绑定。
- WPF由2部分组成:描述GUI布局和效果的xaml文件,和xaml关联的cs文件。
- 如果你想最大程度上的复用你的代码,最好的方法就是使用MVVM(Model、View、ViewModel)模式,这样可以保证你的View部分包含的代码最少
一些关键点
- 你需要一个集合来保存数据,这个集合是ObservableCollection,而不能用List,Dictionary等。ObservableCollection的特点是在添加项、移除项或刷新整个列表时,此集合将提供通知
- 包括Window在内的所有的WPF控件都有一个DataContext
- 接口INotifyPropertyChanged是数据变化和GUI之间的桥梁
实例
再多的描述也不如一个实例更能够说明问题
我创建了一个Song类,用于存储一首歌曲中的信息,包括了歌曲名和歌手名
public class Song
{
private string _songTitle;
private string _artistName;
public string SongTitle
{
get { return _songTitle; }
set { _songTitle = value; }
}
public string ArtistName
{
get { return _artistName;}
set { _artistName = value; }
}
}
设置DataContext方法有两种:
在xaml中:
<Window.DataContext>
<local:SongViewModel />
</Window.DataContext>
或者在cs文件中设置DataContext
public partial class MainWindow : Window
{
SongViewModel _viewModel = new SongViewModel();
public MainWindow()
{
InitializeComponent();
base.DataContext = _viewModel;
}
}
在WPF中,这个Song类就是Model,而GUI则是View,ViewModel把这2层之间的数据进行关联。
然后我们继续创建ViewModel层
这个ViewModel层需要继承INotifyPropertyChanged接口,这样这个类中的某个属性值发生变化,我们会接到通知。
public class SongViewModel : INotifyPropertyChanged
{
private Song _song;
public Song Song
{
get { return _song; }
set { _song = value; }
}
public string ArtistName
{
get { return Song.ArtistName; }
set
{
if (Song.ArtistName != value)
{
Song.ArtistName = value;
RaisePropertyChanged("ArtistName");
}
}
}
public string SongTitle
{
get { return Song.SongTitle; }
set
{
if (Song.SongTitle != value)
{
Song.SongTitle = value;
RaisePropertyChanged("SongTitle");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
上面的代码中,首先检查属性的值是否真的发生变化if (Song.SongTitle != value),这样做可以在复杂的项目中,稍微提升一下性能,如果属性发生变化了,会引发PropertyChanged事件。
现在Model,ViewModel都已经完成,只差View了。MainWindow.xaml文件如下
<Window x:Class="WVVM.MainWindow"
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:local="clr-namespace:WVVM"
mc:Ignorable="d"
Title="MainWindow" Height="201.288" Width="380.794">
<Window.DataContext>
<local:SongViewModel></local:SongViewModel>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="SongTitle"></Label>
<Label Grid.Column="0" Grid.Row="1" Content="Artist:"></Label>
<Label Grid.Column="1" Grid.Row="1" Content="{Binding ArtistName}"></Label>
<Label Grid.Column="1" Grid.Row="0" Content="{Binding SongTitle}"></Label>
<Button Grid.Column="0" Grid.Row="2" Name="BtUpdate" Content="Update" Click="BtUpdate_OnClick"></Button>
</Grid>
</Window>
xaml文件中,Button按钮有一个click事件,在这个事件中更新了viewMode的属性。
CS文件代码如下
private SongViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = (SongViewModel) base.DataContext;
}
private void BtUpdate_OnClick(object sender, RoutedEventArgs e)
{
_viewModel.ArtistName = "Adele";
_viewModel.SongTitle = "Hello";
}
像上文那样直接在GUI文件中绑定事件(Button的click事件),存在很多问题,WPF提供了一个更好的方法那就是ICommand。许多控件都有Command属性,它的绑定和Content,ItemSource的绑定方式相同。
继承ICommand需要实现2个方法:bool CanExecute(用于判断是否能够执行)和void Execute(在这里写你需要的代码,如更改SongTitle等)。
我建立了一个RelayCommand类来实现ICommand接口,代码如下
public class RelayCommand :ICommand
{
private readonly Func<Boolean> _canExecute;
private readonly Action _execute;
public RelayCommand(Action execute) : this(execute,null)
{
}
public RelayCommand(Action execute, Func<Boolean> canExecute)
{
if(execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
[DebuggerStepThrough]
public Boolean CanExecute(Object parameter)
{
return _canExecute == null ? true : _canExecute();
}
public void Execute(Object parameter)
{
_execute();
}
}
相应的,在SongViewModel类中,增加如下代码
void UpdateSongExecute()
{
//在这里执行更新数据的操作
ArtistName = "One Direction";
SongTitle = "Taken";
}
bool CanUpdateSongExecute()
{
//在这里检查是否可以更新数据,我直接返回true了
return true;
}
public ICommand UpdateSong
{
get { return new RelayCommand(UpdateSongExecute,CanUpdateSongExecute);}
}
此时,MainWindow.xaml文件中的Button,就不需要写click事件了,只需绑定Command即可
<Button Grid.Column="0" Grid.Row="2" Name="BtUpdate" Content="Update" Command="{Binding UpdateSong}"></Button>