从一个登录页面浅淡MVVM(二)

如上图,增加了一个 LoginViewModel.cs 文件,放在 ViewModels 目录中,这个文件就是 LoginPage 的 ViewModel 。

一个 UI 对应一个 ViewModel ,这就是 MVVM 的要求,在 ASP.NET MVC 2 中,便是类似这样的。

下面是这个 ViewModel 的部分代码: 

LoginViewModel.cs的字段与属性
[System.Diagnostics.DebuggerStepThroughAttribute()]
public class LoginViewModel : System.ComponentModel.INotifyPropertyChanged
{
#region 字段
private string ValidationCodeField;

private string GivenValidationCodeField;

private string MessageField;

private bool IsDoneField;

private LoginProxy.LoginServiceClient client;
#endregion

#region 属性
public User Data
{
get; private set;
}

[Required]
[StringLength(
4, MinimumLength = 4)]
[Display(Name
= "校验码", Description = "不区分大小写")]
[RegularExpression(
"^[a-zA-Z0-9]*$", ErrorMessage = "只能输入字母、数字")]
public string ValidationCode
{
get
{
return this.ValidationCodeField;
}
set
{
if ((object.ReferenceEquals(this.ValidationCodeField, value) != true))
{
this.ValidateProperty("ValidationCode", value);
if (!object.Equals(value, this.GivenValidationCodeField))
{
throw new NotSupportedException("请按照给定校验码输入");
}
this.ValidationCodeField = value;
this.RaisePropertyChanged("ValidationCode");
}
}
}
public string GivenValidationCode
{
    ...
}
/// <summary>
/// 用于代表消息,包括异常
/// </summary>
public string Message
{
    ... //在 setter 中 RaisePropertyChanged()
}
/// <summary>
/// 用于表示登录是否成功
/// </summary>
public bool IsDone
{
....//在 setter 中 RaisePropertyChanged()
}
#endregion

#region 构造函数
public LoginViewModel()
{
this.Data = new User();
}
#endregion
.......

}
 

 

在 ViewModel 中,把第一阶段中用到的 User 、ValidationModel 、LoginProxy.LoginServiceClient 全部整合

到了一起,并且增加了 Message 和 IsDone 两个属性,旨在通过 Binding 能在 UI 中呈现出 ViewModel 中的状态变化。

对Message和IsDone的控制
this.client.LoginCompleted += (sender, e) =>
{
if (e.Error == null)
{
if (e.Result == 1)
{
// 登录成功
this.HandleMessage("登录成功");
this.IsDone = true;
}
else if (e.Result == 0)
{
// 用户名或密码错误
this.HandleMessage("用户名或密码错误");
}
else if (e.Result == 4)
{
// 校验码失效
this.HandleMessage("校验码失效");
}
}
else
{
// 处理异常
this.HandleException(e.Error);
}
};

/// <summary>
/// 简单的消息处理
/// </summary>
/// <param name="msg"></param>
void HandleMessage(string msg)
{
this.Message = string.Format("消息:{0}", msg);
}
/// <summary>
/// 简单的异常处理
/// </summary>
/// <param name="ex"></param>
void HandleException(Exception ex)
{
this.Message = string.Format("异常:{0}" , ex.Message);
}

 

同时对UI公开了取得校验码和登录的两个异步方法

对View公开的方法
#region 公开的方法
public void GenerateValidationCodeAsync()
{
if (this.NeedInitializeClient())
{
this.InitializeClient();
}
this.client.GenerateValidationCodeAsync();
}

public void LoginAsync()
{
if (this.NeedInitializeClient())
{
this.InitializeClient();
}
this.client.LoginAsync(this.Data, this.ValidationCode);
}
#endregion

 

 在 View 部分,Xaml 中的变化不大,只是绑定路径变化了,

如 txtUserName 的从 Text="{Binding UserName... 变为 Text="{Binding Data.UserName...

 

而在 cs 变分,代码明显变少了:

LoginPage.cs
public partial class LoginPage : Page
{
LoginViewModel loginVM;

public LoginPage()
{
InitializeComponent();
this.loginVM = new LoginViewModel();
this.loginVM.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(loginVM_PropertyChanged);
this.Loaded+=new RoutedEventHandler(LoginPage_Loaded);
}

void loginVM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsDone")
{
if (this.loginVM.IsDone)
{
// 登录成功,执行跳转
}
}
else if (e.PropertyName == "Message")
{
// 可以在 UI 上将 Message 也和 TextBlock 等进行绑定,以显示消息
MessageBox.Show(this.loginVM.Message);
}
}


void LoginPage_Loaded(object sender, RoutedEventArgs e)
{
this.loginVM.GenerateValidationCodeAsync();
this.DataContext = this.loginVM;
}

private void btnChangeValidationCode_Click(object sender, RoutedEventArgs e)
{
this.loginVM.GenerateValidationCodeAsync();
}

// 对 validationSummary1 进行判断的这段代码是否该移入 ViewModel 中?
// 如果移进去了,则会造成 ViewModel 依赖于 UI,
// 如果仅是把 validationSummary1、LayoutRoot 作为参数传递,对于复杂的UI,可能要传递多个这样的参数
private void btnLogin_Click(object sender, RoutedEventArgs e)
{
if (this.validationSummary1.HasErrors)
{
this.validationSummary1.Focus();
return;
}
else
{
// 扩展方法,校验各个控件的数据绑定
this.LayoutRoot.Children.ValidateSource();
if (this.validationSummary1.HasErrors)
{
this.validationSummary1.Focus();
return;
}
}

this.loginVM.LoginAsync();
}
}

这里通过 LoginViewModel.PropertyChanged 来呈现 ViewModel 中的状态变化,这只是一种示意,

如果你愿意,也可以在 ViewModel 中增加一些 event 等来实现。

 

这里,我拿不定主意的是 btnLogin_Click() 里面的 Validate 代码,该不该把这些代码移入 ViewModel 中,

是不是该让 ViewModel 依赖于 View ?个人觉得 ViewModel 还是不要依赖于 View 的为好。

 

从职责的角度来看,我更偏向于把 Validation 和 Binding 统一看成是在 View 中实现的,但是这样就会在

View 中书写一些代码(或者也许是有办法能在Xaml中指定,但是我还不知道?),尽管如此,在第三阶段中,

还是尝试了把 Validation 看作是 ViewModel 的职责,在 ViewModel 中增加属性,把 Validation 彻底放

在 ViewModel 中 ---- 这样做仅是为了让 View 的代码更少。

 

Silverlight 4.0 为 ButtonBase 控件增加了 Command 依赖项属性,这样可以在 Xaml 中进行更多的绑定,

更彻底的分离 View 和 ViewModel。

 

补充,还有一点,刚刚在其他的博主的文章中看到:

  View Model有以下三个部分组成

  1、属性:一个事物,它的类型可以是一个字符型,也可以是一个对象。实现接口INotifyPropertyChanged,那么任何UI元素绑定到这个属性,不管这个属性什么时候改变都能自动和UI层交互。

  2、集合:事物的集合,它的类型一般是ObservableCollection,因此,任何UI元素绑定到它,不管这个集合什么时候改变,都可以自动的与UI交互。

  3、Commands:一个可以被触发的事件,并且可以传递一个类型为Object的参数。但是前提是要实现接口ICommand。

来自于 http://www.cnblogs.com/888h/archive/2010/12/24/1915214.html

 

我不知道这种描述是不是官方的,如果是,那我这个例子中的 ViewModel 中就没有 集合 了,难道这样就不能作为 ViewModel 了?

对此补充的补充:博主天神一已过来发表了意见,也回答了这个问题,在此对天神一表示感谢!

 

三、MVVM模式,并使用Command

 

 

posted @ 2010-12-25 21:38  Sunpire  阅读(3753)  评论(1编辑  收藏  举报