搭建简易MVVM项目

由于最近一直在学习Windows Phone相关知识,而伴随着WIN8的发布,新一代的编程使得很多语言使用唯一的核心库“Winmd”以及可以基于WINRT之上的AppStore环境设计。

  而MVVM是一种架构模式,主要在WPF、Silverlight和WP7开发里使用,它的目标是从视图层移除几乎所有代码隐藏(code-behind)。交互设计师可以专注于使用XAML表达用户体验需求,然后创建和视图模型的绑定,而视图模型则是由应用程序开发者开发和维护的,最大好处之一是分离关注点,以便用户体验设计师和应用程序开发者可以并行工作。

  稍微了解MVVM以后,我们就开始着手实现MVVM吧。实现的功能就挑选登录功能吧。

  项目结构如下:

  

  首先我们创建一个LoginUser类,由于需要绑定到相关的控件上,且在控件的值或实体类属性发生改变时,需要得到同步的更新,因此我们需要让实体类实现INotifyPropertyChange接口,代码如下:

View Code
public class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string propertyName)
{
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}


public class LoginUser : BaseModel
{
private string m_Name;
public string Name
{
set
{
if (null != value)
{
m_Name = value;
OnPropertyChanged("Name");
}
}
get { return m_Name; }
}

private string m_Password;
public string Password
{
set
{
if (null != value)
{
m_Password = value;
OnPropertyChanged("Password");
}
}
get { return m_Password; }
}
}

  接着我们要使用xmal编码界面,具体的UI代码如下:

View Code
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"
HorizontalAlignment="Center"
VerticalAlignment="Center">

<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="180"/>
</Grid.ColumnDefinitions>

<TextBlock Name="txtblkName"
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Center"
Text="用户名"/>
<TextBox Name="txtName"
Grid.Row="0"
Grid.Column="1"
Grid.ColumnSpan="2"/>

<TextBlock Name="txtblkPwd"
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Text="密码"/>
<TextBox Name="txtPwd"
Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="2"/>

<Button Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Content="登录"/>
<Button Grid.Row="2"
Grid.Column="2"
Content="重置"/>
</Grid>

  从XAML代码中,我们需要绑定2个文本的Text以及要绑定2个Button的事件,对于要绑定到控件的对象必须实现ICommand接口。

  登录的时候,我们需要判断用户名密码是否正确,如果正确的话,则执行登录并跳转到其他的页面,如果帐号密码不正确则要求提示,代码如下:

View Code
public class LoginCommand : ICommand
{
private LoginUser m_User = null;
public event EventHandler CanExecuteChanged;

public LoginCommand(LoginUser user)
{
m_User = user;
}

public bool CanExecute(object parameter)
{
return true;
}

public void Execute(object parameter)
{
if (m_User.Name != "ahl5esoft" || m_User.Password != "123456")
{
MessageBox.Show("帐号或密码错误!");
}
else
{
MessageBox.Show("登录成功");
}
}

public void RaiseCanExecuteChanged()
{
if (null != CanExecuteChanged)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}

  重置按钮跟登录代码差不多,只是Execute对LoginUser重新实例化了,这里就省略了。有以上的Command实现,我们不难发现,其实大部分的实现代码都是差不多,只是实现的CanExecute、Execute方法有所不同,那么我们可以重构出一个基类,代码如下:

View Code
public class DelegateCommand : ICommand
{
private readonly Func<bool> m_canExecute;
private readonly Action m_execute;
public event EventHandler CanExecuteChanged;

public DelegateCommand(Action execute, Func<bool> canExecute = null)
{
if (null == execute)
throw new ArgumentNullException("execute", "Cannot be null");

m_execute = execute;
m_canExecute = canExecute;
}

[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return null == m_canExecute || m_canExecute();
}

public void Execute(object parameter)
{
m_execute();
}

public void RaiseCanExecuteChanged()
{
if (null != CanExecuteChanged)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}

  以上我们用一个Action替代了Execute内部的实现,然后用Func<bool>来替代CanExecute的实现,但是大家这时候会发现,如果我们在绑定Command的时候传入参数那该怎么办呢,因此我们这里还要提供一个泛型版本的基类,来处理传入参数的情况,代码如下:

View Code
public class DelegateCommand<T> : ICommand
{
private readonly Predicate<T> m_canExecute;
private readonly Action<T> m_execute;
public event EventHandler CanExecuteChanged;

public DelegateCommand(Action<T> execute, Predicate<T> canExecute = null)
{
if (null == execute)
throw new ArgumentNullException("execute", "Cannot be null");

m_execute = execute;
m_canExecute = canExecute;
}

public bool CanExecute(object parameter)
{
return null == m_canExecute || m_canExecute((T)parameter);
}

public void Execute(object parameter)
{
m_execute((T)parameter);
}

public void RaiseCanExecuteChanged()
{
if (null != CanExecuteChanged)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}

  完成了以上的Command基类以后,我们只要删除原有的LoginCommand,并相应的修改LoginUserViewModel内的代码,修改如下:

View Code
private ICommand m_LoginCommand = null;
public ICommand LoginCommand
{
get
{
if (null == m_LoginCommand)
{
m_LoginCommand = new DelegateCommand(() =>
{
if (m_User.Name != "ahl5esoft" || m_User.Password != "123456")
{
MessageBox.Show("帐号或密码错误!");
}
else
{
MessageBox.Show("登录成功");
}
});
}
return m_LoginCommand;
}
}

  完成了ViewModel,我们只要将ViewModel赋值给Page的DataContext,并调整XAML内的绑定就将这个简易的MVVM完成啦,XAML代码如下:

View Code
<TextBlock Name="txtblkName"
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Center"
Text="用户名"/>
<TextBox Name="txtName"
Grid.Row="0"
Grid.Column="1"
Grid.ColumnSpan="2"
Text="{Binding Path=User.Name, Mode=TwoWay}"/>

<TextBlock Name="txtblkPwd"
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Text="密码"/>
<PasswordBox Name="txtPwd"
Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="2"
Password="{Binding Path=User.Password, Mode=TwoWay}" />

  以上在绑定TextBox以及PasswordBox的时候,我们选择了TwoWay的方式,是为了在控件的值或LoginUser的值发生改变时,都能收到通知,使控件与LoginUser的值能达到同步。

  另一方面,对于MVVM不足之处在于它对于UI操作比较简单的情况有点杀鸡用牛刀的感觉,数据绑定有点难以调试,以及大量使用数据绑定可能带来性能问题等等,虽然有着众多的不足,但是能理解它也是有好处的

posted @ 2012-09-14 13:24  超级塞亚人  阅读(172)  评论(0)    收藏  举报