• Silverlight的设计模式(MVVM)初体验


    这几天看了一些关于Silverlight的设计模式相关的文章,主流观点是:Silverlight + WCF + MVVM + Prism,而这几个都不太了解,汗啊~

    没关系,“没吃过猪肉还没见过猪跑吗”,既然我们自己的项目中没有用到,那就找一些别人写好的吧,边看边学,边学边用,人总是要不断前进的,俗话说,学习如逆水行舟,不进则退,得继续努力了!

    还是使用之前《Silverlight之路》中使用的项目,先以登录页面为入口,尝试使用MVVM进行开发,先建好对应的目录(当然,这个不是必须的)

     

    把之前的登录窗口放到对于View目录中,修改好对应的命名空间,这样,我们的View就差不多了,接下来看看我们的Model,它应该是什么呢?

    在我们后台的WCF中,用户登录会到TB_Common_User表中进行验证,于是在我们的实体框架中就有了一个对应的实体类型,那么它是不是就是我们的Model呢?说实话,刚接触的时候我一直以为他们是一回事。但显然,他们不是同一个概念。做为实体的Model,它只是一个数据类型,方便我们在项目中使用,它本身不存在任何逻辑;而做为行为的Model,也就是我们LoginModel,它的责任就要大的多,它负责为ViewModel提供数据,并处理来自ViewModel中的反馈与请求,可以说,它是整个功能的核心要素,想想看,不管是MVC、MVP还是MVVM,为什么它总在第一位呢?呵呵。

    知道了Model,那么VM又是什么呢?一个简单的理解:理论上讲,对于后台程序员来说,并不知道View的存在!因为在开发的过程中,View可能会由美工与前端程序员负责,而后台的业务逻辑等由后台程序员负责,为了并行开发和避免相互依赖,需要一个中间件来连接前后台,这就是ViewModel。因此,对于我们来说,ViewModel就是我们的View。至于它如何与View进行关联,这就是SL与WPF中的强大的Binding系统负责的事情了。

    由此可以想像,为了“不知道”View存在,就需要在ViewModel中建立View需要的所有数据,我们的目的就是要“以数据驱动界面”,可以设想一下,界面上任何需要后台控制的属性都需要在ViewModel中有与之对应的属性存在,事件也是一样,可以通过命令进行绑定(不过似乎SL中对命令的支持有限,只有一系列按钮可以支持)。

    有了上面的概念,回到我们的需求中,分析一下登录模块需要的功能流程:输入,验证输入,登录请求,登录验证,返回登录结果,下一步操作(成功则跳转,失败则提示)。对号入座:输入(View),验证输入(ViewModel),登录请求(Model),登录验证(WCF),返回登录结果(Model),下一步操作(ViewModel),责任分清之后可以进行实现了,不过这里有必要提一下验证输入这一块,在SL是可没有类似Web中那一系列验证控件(Tookit中有,但原理完全不同),SL中的验证可以通过IDataErrorInfo接口来实现,这个一会再说,先看一下代码吧,有时代码比文字更能说明问题。

    View Code
    public class LoginModel

    {

    WcfServiceClient client
    = new WcfServiceClient();



    public string UserName { set; get; }

    public string PassWord { set; get; }



    public LoginModel()

    {

    client.loginCompleted
    += new EventHandler<loginCompletedEventArgs>(client_loginCompleted);

    }



    void client_loginCompleted(object sender, loginCompletedEventArgs e)

    {

    Action
    <int> callback = e.UserState as Action<int>;

    if (callback != null)

    callback(e.Result);

    }



    public void RequestLogin(Action<int> callback)

    {

    client.loginAsync(UserName, PassWord, callback);

    }

    }

    LoginModel类包括两个属性与一个方法,RequestLogin的参数是一个Action,用来在验证登录后通知ViewModel做处理。再看看ViewModel的代码

    View Code
    public class LoginViewModel : INotifyPropertyChanged, IDataErrorInfo

    {

    public event PropertyChangedEventHandler PropertyChanged;



    private Model.LoginModel lm = new Model.LoginModel();



    public string UserName

    {

    get { return lm.UserName; }

    set

    {

    lm.UserName
    = value;

    if (PropertyChanged != null)

    PropertyChanged(
    this, new PropertyChangedEventArgs("UserName"));

    }

    }



    public string Password

    {

    get { return lm.PassWord; }

    set

    {

    lm.PassWord
    = value;

    if (PropertyChanged != null)

    PropertyChanged(
    this, new PropertyChangedEventArgs("Password"));

    }

    }



    private ICommand _RequestLoginCommand;



    public ICommand RequestLoginCommand

    {

    get

    {

    if (_RequestLoginCommand == null)

    _RequestLoginCommand
    = new Commands.DelegateCommand(RequestLogin);

    return _RequestLoginCommand;

    }

    }



    public void RequestLogin(object obj)

    {

    lm.RequestLogin(RequestLoginCallBack);

    }



    private void RequestLoginCallBack(int loginstate)

    {

    if (loginstate == 1)

    {

    View.Login l
    = App.Current.RootVisual as View.Login;

    l.Content
    = new MainPage();

    }

    }



    #region IDataErrorInfo 成员

    public string Error

    {

    get { return "出错啦!"; }

    }



    public string this[string columnName]

    {

    get

    {

    string result = null;

    int len = 0;



    switch (columnName)

    {

    case "UserName":

    // 设置Username属性的验证规则

    len
    = UserName.Length;

    if (len < 2 || len > 10)

    {

    result
    = "Username length must between 2 and 10";

    }

    break;

    case "Password":

    // 设置Pwd属性的验证规则

    len
    = Password.Length;

    if (len < 2 || len > 10)

    {

    result
    = "Pwd length must between 2 and 10";

    }

    break;

    }



    return result;

    }

    }

    #endregion

    }

    初一看好像代码很多,其实仔细看看的话,发现代码很简单。不过说实话,使用MVVM开发模式的话,一定会比平常的代码量要大的多,可是哪种模式不是这样呢?开发模式的优势不能简单的用纯代码量来衡量的。

    <TextBlock HorizontalAlignment="Right" Margin="0" TextWrapping="Wrap" Text="密码:" Foreground="#FFFDFBFB" Width="{Binding Width, ElementName=textBlock}" TextAlignment="Right" VerticalAlignment="Center"/>

    <PasswordBox HorizontalAlignment="Left" Width="{Binding Width, ElementName=txtUserName}" Height="28" VerticalAlignment="Center" Margin="0" x:Name="txtPassWord" Password="{Binding Password, Mode=TwoWay}" />

    <Button Content="登录" HorizontalAlignment="Left" Height="27" Margin="0,0,8,0" Width="51" x:Name="btnLogin" Command="{Binding RequestLoginCommand}" />

    这个LoginViewModel实现了INotifyPropertyChanged接口,用来双向绑定数据;实现了IDataErrorInfo接口用来验证输入(关于这两个接口的说明,请参考MSDN或其它详细说明)。这样,我们的VM就建立完成了,剩下的工作只要在view进行绑定就可以了,绑定如下

    在初始化时实例化一个LoginViewModel并赋值到View的DataContext就完成了。

    ViewModel.LoginViewModel lvm = new ViewModel.LoginViewModel();

    this.DataContext = lvm;

    实现效果如

      

    以上就是登录功能的MVVM实现。代码很好理解,最主要的工作其实就是分清责任,有两个基本思想,就是

    1、  Model是功能的核心,负责业务逻辑的实现。

    2、  ViewModel负责为View提供数据,对Model来说,它就是所有的前台。

    这就是我对MVVM的理解了,不知道是否正确,欢迎各个大牛指点拍砖!

    最后,这里有两个小问题,当我们第一次打个这个窗口时,我们希望用户名输入框获得焦点,但默认情况下不行,需要做一些小处理,即在Load事件中设置一下

    void Login_Loaded(object sender, RoutedEventArgs e)

    {

    HtmlPage.Plugin.Focus();

    this.txtUserName.Focus();

    }

    然后,当载入完成后,我们还没有输入时,因为不满足数据验证条件,它也会在加载完就给出错误提示,这也不太好,至少你得先让我输入之后再验证吧。这里我找了一个取巧的办法,我发现Click事件会在绑定的Command执行前被触发,因此,我把绑定的动作放到第一次点击登录按钮时,如

    void btnLogin_Click(object sender, RoutedEventArgs e)

    {

    if (this.DataContext == null)

    {

    ViewModel.LoginViewModel lvm
    = new ViewModel.LoginViewModel();

    lvm.UserName
    = this.txtUserName.Text;

    lvm.Password
    = this.txtPassWord.Password;

    this.DataContext = lvm;

    }

    }

    注意,在第一次绑定时,数据是会从数据源流向目标的,也就是说,我们需要在设置DataContext之前把输入的数据先设置到数据源中,否则如果没有lvm.UserName = this.txtUserName.Text;lvm.Password = this.txtPassWord.Password;这两句,在绑定时输入框就会被清空了。

  • 相关阅读:
    BZOJ3391: [Usaco2004 Dec]Tree Cutting网络破坏
    python总结二
    python总结一
    评论详解
    C++入门篇十三
    C++入门篇十二
    C++入门篇十一
    C++入门篇十
    C++入门篇九
    c++入门篇八
  • 原文地址:https://www.cnblogs.com/meteortent/p/2101182.html
Copyright © 2020-2023  润新知