由於最近一直在學習Windows Phone相關知識,而伴隨着WIN8的發布,新一代的編程使得很多語言使用唯一的核心庫“Winmd”以及可以基於WINRT之上的AppStore環境設計。
而MVVM是一種架構模式,主要在WPF、Silverlight和WP7開發里使用,它的目標是從視圖層移除幾乎所有代碼隱藏(code-behind)。交互設計師可以專注於使用XAML表達用戶體驗需求,然后創建和視圖模型的綁定,而視圖模型則是由應用程序開發者開發和維護的,最大好處之一是分離關注點,以便用戶體驗設計師和應用程序開發者可以並行工作。
稍微了解MVVM以后,我們就開始着手實現MVVM吧。實現的功能就挑選登錄功能吧。
項目結構如下:
首先我們創建一個LoginUser類,由於需要綁定到相關的控件上,且在控件的值或實體類屬性發生改變時,需要得到同步的更新,因此我們需要讓實體類實現INotifyPropertyChange接口,代碼如下:

1 public class BaseModel : INotifyPropertyChanged 2 { 3 public event PropertyChangedEventHandler PropertyChanged; 4 5 protected void OnPropertyChanged(string propertyName) 6 { 7 if (null != PropertyChanged) 8 { 9 PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 10 } 11 } 12 } 13 14 15 public class LoginUser : BaseModel 16 { 17 private string m_Name; 18 public string Name 19 { 20 set 21 { 22 if (null != value) 23 { 24 m_Name = value; 25 OnPropertyChanged("Name"); 26 } 27 } 28 get { return m_Name; } 29 } 30 31 private string m_Password; 32 public string Password 33 { 34 set 35 { 36 if (null != value) 37 { 38 m_Password = value; 39 OnPropertyChanged("Password"); 40 } 41 } 42 get { return m_Password; } 43 } 44 }
接着我們要使用xmal編碼界面,具體的UI代碼如下:

1 <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" 2 HorizontalAlignment="Center" 3 VerticalAlignment="Center"> 4 5 <Grid.RowDefinitions> 6 <RowDefinition Height="auto"/> 7 <RowDefinition Height="auto"/> 8 <RowDefinition Height="auto"/> 9 </Grid.RowDefinitions> 10 <Grid.ColumnDefinitions> 11 <ColumnDefinition Width="60"/> 12 <ColumnDefinition Width="120"/> 13 <ColumnDefinition Width="180"/> 14 </Grid.ColumnDefinitions> 15 16 <TextBlock Name="txtblkName" 17 Grid.Row="0" 18 Grid.Column="0" 19 VerticalAlignment="Center" 20 Text="用戶名"/> 21 <TextBox Name="txtName" 22 Grid.Row="0" 23 Grid.Column="1" 24 Grid.ColumnSpan="2"/> 25 26 <TextBlock Name="txtblkPwd" 27 Grid.Row="1" 28 Grid.Column="0" 29 VerticalAlignment="Center" 30 Text="密碼"/> 31 <TextBox Name="txtPwd" 32 Grid.Row="1" 33 Grid.Column="1" 34 Grid.ColumnSpan="2"/> 35 36 <Button Grid.Row="2" 37 Grid.Column="0" 38 Grid.ColumnSpan="2" 39 Content="登錄"/> 40 <Button Grid.Row="2" 41 Grid.Column="2" 42 Content="重置"/> 43 </Grid>
從XAML代碼中,我們需要綁定2個文本的Text以及要綁定2個Button的事件,對於要綁定到控件的對象必須實現ICommand接口。
登錄的時候,我們需要判斷用戶名密碼是否正確,如果正確的話,則執行登錄並跳轉到其他的頁面,如果帳號密碼不正確則要求提示,代碼如下:

1 public class LoginCommand : ICommand 2 { 3 private LoginUser m_User = null; 4 public event EventHandler CanExecuteChanged; 5 6 public LoginCommand(LoginUser user) 7 { 8 m_User = user; 9 } 10 11 public bool CanExecute(object parameter) 12 { 13 return true; 14 } 15 16 public void Execute(object parameter) 17 { 18 if (m_User.Name != "ahl5esoft" || m_User.Password != "123456") 19 { 20 MessageBox.Show("帳號或密碼錯誤!"); 21 } 22 else 23 { 24 MessageBox.Show("登錄成功"); 25 } 26 } 27 28 public void RaiseCanExecuteChanged() 29 { 30 if (null != CanExecuteChanged) 31 { 32 CanExecuteChanged(this, EventArgs.Empty); 33 } 34 } 35 }
重置按鈕跟登錄代碼差不多,只是Execute對LoginUser重新實例化了,這里就省略了。有以上的Command實現,我們不難發現,其實大部分的實現代碼都是差不多,只是實現的CanExecute、Execute方法有所不同,那么我們可以重構出一個基類,代碼如下:

1 public class DelegateCommand : ICommand 2 { 3 private readonly Func<bool> m_canExecute; 4 private readonly Action m_execute; 5 public event EventHandler CanExecuteChanged; 6 7 public DelegateCommand(Action execute, Func<bool> canExecute = null) 8 { 9 if (null == execute) 10 throw new ArgumentNullException("execute", "Cannot be null"); 11 12 m_execute = execute; 13 m_canExecute = canExecute; 14 } 15 16 [DebuggerStepThrough] 17 public bool CanExecute(object parameter) 18 { 19 return null == m_canExecute || m_canExecute(); 20 } 21 22 public void Execute(object parameter) 23 { 24 m_execute(); 25 } 26 27 public void RaiseCanExecuteChanged() 28 { 29 if (null != CanExecuteChanged) 30 { 31 CanExecuteChanged(this, EventArgs.Empty); 32 } 33 } 34 }
以上我們用一個Action替代了Execute內部的實現,然后用Func<bool>來替代CanExecute的實現,但是大家這時候會發現,如果我們在綁定Command的時候傳入參數那該怎么辦呢,因此我們這里還要提供一個泛型版本的基類,來處理傳入參數的情況,代碼如下:

1 public class DelegateCommand<T> : ICommand 2 { 3 private readonly Predicate<T> m_canExecute; 4 private readonly Action<T> m_execute; 5 public event EventHandler CanExecuteChanged; 6 7 public DelegateCommand(Action<T> execute, Predicate<T> canExecute = null) 8 { 9 if (null == execute) 10 throw new ArgumentNullException("execute", "Cannot be null"); 11 12 m_execute = execute; 13 m_canExecute = canExecute; 14 } 15 16 public bool CanExecute(object parameter) 17 { 18 return null == m_canExecute || m_canExecute((T)parameter); 19 } 20 21 public void Execute(object parameter) 22 { 23 m_execute((T)parameter); 24 } 25 26 public void RaiseCanExecuteChanged() 27 { 28 if (null != CanExecuteChanged) 29 { 30 CanExecuteChanged(this, EventArgs.Empty); 31 } 32 } 33 }
完成了以上的Command基類以后,我們只要刪除原有的LoginCommand,並相應的修改LoginUserViewModel內的代碼,修改如下:

1 private ICommand m_LoginCommand = null; 2 public ICommand LoginCommand 3 { 4 get 5 { 6 if (null == m_LoginCommand) 7 { 8 m_LoginCommand = new DelegateCommand(() => 9 { 10 if (m_User.Name != "ahl5esoft" || m_User.Password != "123456") 11 { 12 MessageBox.Show("帳號或密碼錯誤!"); 13 } 14 else 15 { 16 MessageBox.Show("登錄成功"); 17 } 18 }); 19 } 20 return m_LoginCommand; 21 } 22 }
完成了ViewModel,我們只要將ViewModel賦值給Page的DataContext,並調整XAML內的綁定就將這個簡易的MVVM完成啦,XAML代碼如下:

1 <TextBlock Name="txtblkName" 2 Grid.Row="0" 3 Grid.Column="0" 4 VerticalAlignment="Center" 5 Text="用戶名"/> 6 <TextBox Name="txtName" 7 Grid.Row="0" 8 Grid.Column="1" 9 Grid.ColumnSpan="2" 10 Text="{Binding Path=User.Name, Mode=TwoWay}"/> 11 12 <TextBlock Name="txtblkPwd" 13 Grid.Row="1" 14 Grid.Column="0" 15 VerticalAlignment="Center" 16 Text="密碼"/> 17 <PasswordBox Name="txtPwd" 18 Grid.Row="1" 19 Grid.Column="1" 20 Grid.ColumnSpan="2" 21 Password="{Binding Path=User.Password, Mode=TwoWay}" />
以上在綁定TextBox以及PasswordBox的時候,我們選擇了TwoWay的方式,是為了在控件的值或LoginUser的值發生改變時,都能收到通知,使控件與LoginUser的值能達到同步。
另一方面,對於MVVM不足之處在於它對於UI操作比較簡單的情況有點殺雞用牛刀的感覺,數據綁定有點難以調試,以及大量使用數據綁定可能帶來性能問題等等,雖然有着眾多的不足,但是能理解它也是有好處的。
那么今天的簡易MVVM就到這里啦,由於大部分的代碼已經貼出,因此就不再提供源代碼了,如有什么誤解或者錯誤的觀點還望指出,謝謝。