搭建簡易MVVM項目


  由於最近一直在學習Windows Phone相關知識,而伴隨着WIN8的發布,新一代的編程使得很多語言使用唯一的核心庫“Winmd”以及可以基於WINRT之上的AppStore環境設計。

  而MVVM是一種架構模式,主要在WPF、Silverlight和WP7開發里使用,它的目標是從視圖層移除幾乎所有代碼隱藏(code-behind)。交互設計師可以專注於使用XAML表達用戶體驗需求,然后創建和視圖模型的綁定,而視圖模型則是由應用程序開發者開發和維護的,最大好處之一是分離關注點,以便用戶體驗設計師和應用程序開發者可以並行工作。

  稍微了解MVVM以后,我們就開始着手實現MVVM吧。實現的功能就挑選登錄功能吧。

  項目結構如下:

  

  首先我們創建一個LoginUser類,由於需要綁定到相關的控件上,且在控件的值或實體類屬性發生改變時,需要得到同步的更新,因此我們需要讓實體類實現INotifyPropertyChange接口,代碼如下:

View Code
 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代碼如下:

View Code
 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接口。

  登錄的時候,我們需要判斷用戶名密碼是否正確,如果正確的話,則執行登錄並跳轉到其他的頁面,如果帳號密碼不正確則要求提示,代碼如下:

View Code
 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方法有所不同,那么我們可以重構出一個基類,代碼如下:

View Code
 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的時候傳入參數那該怎么辦呢,因此我們這里還要提供一個泛型版本的基類,來處理傳入參數的情況,代碼如下:

View Code
 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內的代碼,修改如下:

View Code
 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代碼如下:

View Code
 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就到這里啦,由於大部分的代碼已經貼出,因此就不再提供源代碼了,如有什么誤解或者錯誤的觀點還望指出,謝謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM