看了刘铁猛老师的《深入浅出WPF》视频后,对MVVM有了初步了解,但还是一知半解。真的是如刘老师所说,学习MVVM是一个顿悟的过程,就像当初理解面向对象和面向过程一样,书和视频只是一个引导,其中的内涵还需要自己去顿悟。
经过几天的对MVVM的学习,结合着MVVMLight,终于对MVVM有所领悟。这里只是一个视频中的小例子,我用自己的理解把它复制过来,然后用MVVMLight进行实现,加深对MVVM的理解。
无框架MVVM模式下的例子:
未绑定数据的View:
<Window x:Class="Test.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Button Content="Save" x:Name="Save" /> <Grid Grid.Row="1"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBox x:Name="tb1" Grid.Row="0" Background="LightBlue" FontSize="24" Margin="5"/> <TextBox x:Name="tb2" Grid.Row="1" Background="LightBlue" FontSize="24" Margin="5" /> <TextBox x:Name="tb3" Grid.Row="2" Background="LightBlue" FontSize="24" Margin="5" /> <Button x:Name="addButton" Grid.Row="3" Content="Add" Width="120" Height="80" /> </Grid> </Grid> </Window>
ViewModel:
INotifyProPertyChange接口:
INotifyPropertyChange:通知界面某一属性值发生了改变,类型为interface,如果不使用框架,则需要自己手动去实现该接口,MVVMLight已经封装了一个现成的类ObservableObject,可以直接调用,很方便。如果是初学MVVM还是自己手动去实现这个接口,否则用着MVVMLight也会是一头雾水。
//INotifyPropertyChanged 向客户端发出某一属性值已更改的通知。 class NotificationObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void RaisePropertyChange(string PropertyName) { if (this.PropertyChanged != null) { /* Invoke或者BeginInvoke方法都需要一个委托对象作为参数。 委托类似于回调函数的地址,因此调用者通过这两个方法就可以把需要调用的函 数地址封送给界面线程。这些方法里面如果包含了更改控件状态的代码, 那么由于最终执行这个方法的是界面线程, 从而避免了竞争条件,避免了不可预料的问题。 如果其它线程直接操作界面线程所属的控件, 那么将会产生竞争条件,造成不可预料的结果。 */ this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(PropertyName)); } } }
ICommand接口:
viewmodel中实现,在view中进行调用。
该接口包括2个方法和一个事件。CanExecute方法返回命令的状态——指示命令是否可执行,例如,文本框中没有选择任何文本,此时Copy命令是不用的,CanExecute则返回为false。Execute方法就是命令执行的方法,即处理程序。当命令状态改变时,会触发CanExecuteChanged事件。
同INotifyProPertyChange一样为interface类型,如果不使用用框架,需要手动去实现。MVVMLight已经实现该接口:RelayCommand
class DelegateCommand : ICommand { public bool CanExecute(object parameter) { //如果忽略了检查 CanExecute ,则永远都可以执行 if (this.CanExecuteFunc == null) { return true; } this.CanExecuteFunc(parameter); return true; } public event EventHandler CanExecuteChanged; public void Execute(object parameter) { if (this.ExecuteAction == null) { return; } this.ExecuteAction(parameter); } //参数为object 无返回值 public Action<object> ExecuteAction { get; set; } //参数为object 返回值为bool public Func<object, bool> CanExecuteFunc { get; set; } }
MainViewModel:
view调用的ViewModel,不要被名字Main迷惑,这个demo只有一个界面就是程序主界面(Mainwindow),如果有多个界面,view和viewmodel前缀相同,文件结构更加清晰。
该类继承了上面实现的Notification类,里面包含了数据Input1, Input2, Result,命令AddCommand,SaveCommand
class MainWindowViewmode : NotificationObject { public double input1; public double Input1 { get {return input1;} set { input1 = value;
//此处调用的是NotificationObject的RaiseProperty
this.RaisePropertyChange("Input1");
}
}
public double input2;
public double Input2
{
get { return input2; }
set
{
input2 = value;
this.RaisePropertyChange("Input2");
}
}
public double result;
public double Result
{
get { return result; }
set
{
result = value;
this.RaisePropertyChange("Result");
}
}
public DelegateCommand AddCommand { get; set; }
public DelegateCommand SaveCommand { get; set; }
private void Add(object parameter)
{
this.Result = this.Input1 + this.Input2;
}
private void Save(object parameter)
{
SaveFileDialog dlg = new SaveFileDialog();
dlg.ShowDialog();
}
public MainWindowViewmode()
{
this.AddCommand = new DelegateCommand();
this.AddCommand.ExecuteAction = new Action<object>(this.Add); this.SaveCommand = new DelegateCommand(); this.SaveCommand.ExecuteAction = new Action<object>(this.Save); } }
绑定数据和命令的view:
<Window x:Class="Test.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Button Content="Save" x:Name="Save" Command="{Binding SaveCommand}"/> <Grid Grid.Row="1"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBox x:Name="tb1" Grid.Row="0" Background="LightBlue" FontSize="24" Margin="5" Text="{Binding Input1}"/> <TextBox x:Name="tb2" Grid.Row="1" Background="LightBlue" FontSize="24" Margin="5" Text="{Binding Input2}"/> <TextBox x:Name="tb3" Grid.Row="2" Background="LightBlue" FontSize="24" Margin="5" Text="{Binding Result}"/> <Button x:Name="addButton" Grid.Row="3" Content="Add" Width="120" Height="80" Command="{Binding AddCommand}"/> </Grid> </Grid> </Window>