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