• 从一个登录页面浅淡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

     

     

  • 相关阅读:
    volcanol的工控博客
    volcanol的工控博客
    volcanol的工控博客
    volcanol的工控博客
    volcanol的工控博客
    volcanol的工控博客
    volcanol的工控博客
    volcanol的工控博客
    volcanol的工控博客
    volcanol的工控博客
  • 原文地址:https://www.cnblogs.com/Sunpire/p/1916933.html
Copyright © 2020-2023  润新知