看了劉鐵猛老師的《深入淺出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>