搭建简易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就到这里啦,由于大部分的代码已经贴出,因此就不再提供源代码了,如有什么误解或者错误的观点还望指出,谢谢。

posted @ 2012-09-14 10:38  ahl5esoft  阅读(3061)  评论(12编辑  收藏  举报