zoukankan      html  css  js  c++  java
  • [uwp]MVVM之MVVMLight,一个登录注销过程的简单模拟

    之前学MVVM,从ViewModelBase,RelayCommand都是自己瞎写,许多地方处理的不好,接触到MVVMLigth后,就感觉省事多了。

    那么久我现在学习MVVMLight的收获,简单完成以下一个Demo

    Demo主要功能是:

      用户在登录界面登陆,登录成功后跳转到另一个页面,同时把登录时的用户信息作为参数传递过去,然后用户可以选择注销,注销时会弹出对话框,让用户选择是否真的注销,如果是,就真的注销,回退到               登录页面,否则就不做任何处理。

    功能很简洁,接下来就用MVVMLight来实现,另外我的开发环境是vs2015,项目类型是windows10通用应用,由于mvvmlight并未对win10通用应用项目做适配,所以不会像wpf项目那样,在工程中自动添加ViewModel文件夹和全局资源ViewModelLocator,所以需要我们手动添加,不过这个过程也很简单。

    一.为项目安装MVVMLightLibs(通过vs中的NuGet包管理器)

     

      安装成功后,就可以使用了。

    二.创建ViewModelLocator

    自己创建一个类ViewModelLocator可以直接放在项目根目录,也可以放在ViewModel里面,具体放哪儿仁者见仁,智者见智。我们后面用到的页面导航,ViewModel和View绑之间的定都是以这个类为基础

       public class ViewModelLocator
        {
            public ViewModelLocator()
            {
                ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
            }
        }

    现在看起来很简单,待会会给他加东西的。

    三.确定View

     在这个demo中,我们主要有LoginView和MainView两个页面,前者是登录页面,后者是登录成功后的页面。

     LoginView的布局代码如下:

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <Grid Margin="24">
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                </Grid.RowDefinitions>
                <TextBox Grid.Row="0" Header="用户名" Text="{Binding Username,Mode=TwoWay}"/>
                <TextBox Grid.Row="1" Header="密码" Text="{Binding Password,Mode=TwoWay}"/>
                <TextBox Grid.Row="2"></TextBox>
                <Button Grid.Row="3" HorizontalAlignment="Stretch" Content="登录" Command="{Binding LoginCommand}"></Button>
            </Grid>
        </Grid>

    MainView的布局代码如下:

    <Grid Margin="36" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <StackPanel>
                <TextBlock>
                <Run FontSize="36" Text="欢迎你:"></Run>
                <Run FontSize="72" Foreground="Purple" Text="{Binding Username,Mode=TwoWay}"></Run>
                </TextBlock>
                <TextBox></TextBox>
            </StackPanel>
            <Button Content="注销" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
                    Command="{Binding LogoffCommand}"></Button>
        </Grid>

    布局很简单,也很好理解,但是我们目前还没有把View和ViewModel绑定到一起,因为我们ViewModel还没搞呢。

    四.创建ViewModel

    接下来就是ViewModel了。这个工程简单,所以也只有两个ViewModel,分别是LoginViewModel和MainViewModel,相信从名字就能看出他他们和View的对应关系了。

    在LoginViewModel中,对应LoginView,添加Username和Password两个属性,并且添加一个命令LoginCommand.

    一定要让LoginViewModel继承ViewModelBase。。(在红色波浪线处敲击键盘Shift+alt+F10自动添加命名空间哦)

     public class LoginViewModel:ViewModelBase
        {
            private string _username;
            private string _password;
    
            public string Username
            {
                get
                {
                    return _username;
                }
    
                set
                {
                    Set(ref _username, value);
                }
            }
    
            public string Password
            {
                get
                {
                    return _password;
                }
    
                set
                {
                    Set(ref _password, value);
                }
            }
    
            public ICommand LoginCommand { get; set; }
            private void Login()
            {
                User model = new User { Username = Username.Trim(), Password = Password.Trim() };
                if (true==AuthenticateHelper.Authenticate(model))
                {
                    var navigation = ServiceLocator.Current.GetInstance<INavigationService>();
                    navigation.NavigateTo("Main",model);
                    
                    ViewModelLocator.Main.User = model;
                }
                else
                {
                    GalaSoft.MvvmLight.Messaging.Messenger.Default.Send<string>("用户名或密码错误!!!");
                }
            }
            public LoginViewModel()
            {
                LoginCommand = new RelayCommand(Login);
            }
        }
    View Code

    除了Login方法的具体实现目前有些模糊外,其他的理解起来都很容易。

    至于MainViewModel就更简单了

    public class MainViewModel:ViewModelBase
        {
            private string _username;
            public string Username
            {
                get
                {
                    return _username;
                }
    
                set
                {
                    Set(ref _username, value);
                }
            }
    
            private Model.User _user;
            public User User
            {
                get
                {
                    return _user;
                }
    
                set
                {
                    _user = value;
                    Username = value.Username;
                }
            }
    
            public MainViewModel()
            {
                Username = "CQ";
                LogoffCommand = new RelayCommand(Logoff);
    
            }
            public ICommand LogoffCommand { get; set;}
            private void Logoff()
            {
                GalaSoft.MvvmLight.Messaging.Messenger.Default.Send<object>("确认注销吗?");
            }
        }
    View Code

    里面有两点可能造成困惑,一是User属性,它是一个Model类,用于页面传递参数等;二是Logoff方法的实现,这个和Login方法一样。待会儿再说。

    五.Model

    写到这儿我自己都觉着一头雾水了。。。没办法,继续填坑

    MVVM,我们已经有了View和ViewModel,那么最后一个Model呢?刚才提过了,Model其实就是用来传递参数用一下,在这个demo中没什么大用处。

    具体可以下载源码查看。

    六.现在重头戏才来!!!

    如果自己写ViewModel和View的绑定,会怎么写呢?

    大概是在View对应的.cs类构造函数中来一句

    this.DataContext=new ViewModel();

    这样的确可行,但是在MVVMLight中,是通过SimpleIoc,用一个简单的IOC容器来实现对实例的创建和销毁。即有IOC容器为我们创建ViewModel实例,然后用它作为View的数据源。

    (这块儿可以具体百度一下。我也正在学习)

    所以ViewModelLocator的代码就成下面这个样子了

    public class ViewModelLocator
        {
            public ViewModelLocator()
            {
                ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
    
                SimpleIoc.Default.Register<LoginViewModel>();
                SimpleIoc.Default.Register<MainViewModel>();
            }
    
            public static LoginViewModel _login;
            public static LoginViewModel Login
            {
                get
                {
                    if (_login == null)
                        _login = ServiceLocator.Current.GetInstance<LoginViewModel>();
                    return _login;
                }
            }
    
            private static MainViewModel _main;
            public static MainViewModel Main
            {
                get
                {
                    if (_main == null)
                        _main = ServiceLocator.Current.GetInstance<MainViewModel>();
                    return _main;
                }
            }
        }

    可以看到,在构造函数中向SimpleIoc注册了两个给定的类型LoginViewModel和MainViewModel.

    然后定义了两个静态的只读属性Main和Login。他们的就是用来和具体的View绑定用的。至于为什么是静态的呢?目的是在页面导航的时候方便传递参数而做的。

    那么究竟怎么吧LoginView和Login绑定?(以下方法是mvvmlight框架默认的行为,但在win10通用项目中没有自动生成,所以手动来实现

    在App.xaml中添加一个全局资源,代码如下

    <Application
        x:Class="LoginDemo.App"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:LoginDemo"
        RequestedTheme="Light">
        <Application.Resources>
            <ResourceDictionary>
                <local:ViewModelLocator x:Key="Locator"></local:ViewModelLocator>
            </ResourceDictionary>
        </Application.Resources>
    </Application>

    这样做的好处是在整个工程中都可以使用ViewModelLocator的实例

    接下来就是具体的绑定环节了。。我们以LoginView和Login属性的绑定为例

    LoginView中只需要添加如下代码:

     <Page.DataContext>
            <Binding Path="Login" Source="{StaticResource Locator}"></Binding>
        </Page.DataContext>

    嗯,就这么简单,其实刚才为什么把ViewModelLocator作为全局资源,目的就是在其他地方引用方便。

    至此。demo已经可以编译运行了。(注意,在app.xaml.cs中,把启动页面设置为LoginView)

    但是却并没有实现登录验证和页面导航的功能。。

    不过不用担心,我们主体框架已经搭好,其他的都简单了。

    七.登录验证

    在LoginViewModel中我们见到了Login方法定义如下:

    private void Login()
            {
                User model = new User { Username = Username.Trim(), Password = Password.Trim() };
                if (true==AuthenticateHelper.Authenticate(model))
                {
                    var navigation = ServiceLocator.Current.GetInstance<INavigationService>();
                    navigation.NavigateTo("Main",model);
                    
                    ViewModelLocator.Main.User = model;
                }
                else
                {
                    GalaSoft.MvvmLight.Messaging.Messenger.Default.Send<string>("用户名或密码错误!!!");
                }
            }

    首先把用户名和密码初始化一个User的实例,然后调用Authenticate方法验证,如果验证通过,就导航到MainView,如果失败,就弹出一个消息框,说你失败了!!!

    因为是demo嘛。Authenticate我就简单的用了一个if来判断用户名密码是否和预定义的一致(预定义的就是“cq”,"cq"),一致就返回true,表示通过!否则就false..

    这没什么好说的。重点还是验证通过后的导航和验证失败的消息提醒。

    八.页面导航

    针对于导航,MVVMLight也有自己的一套方法,他提供了一个接口INavigationService和方法NavigationService,我们要做的就是继续到IOC容器中注册“一个用于导航的工厂类型”

    于是乎,ViewModelLocator变成了下面这样

    public class ViewModelLocator
        {
            public ViewModelLocator()
            {
                ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
    
                SimpleIoc.Default.Register<LoginViewModel>();
                SimpleIoc.Default.Register<MainViewModel>();
    
                var navigationService = this.InitNavigationService();
                SimpleIoc.Default.Register(() => navigationService);
            }
    
            public INavigationService InitNavigationService()
            {
                NavigationService navigationService = new NavigationService();
                navigationService.Configure("Login", typeof(LoginView));
                navigationService.Configure("Main", typeof(MainView));
                return navigationService;
            }
    
            public static LoginViewModel _login;
            public static LoginViewModel Login
            {
                get
                {
                    if (_login == null)
                        _login = ServiceLocator.Current.GetInstance<LoginViewModel>();
                    return _login;
                }
            }
    
            private static MainViewModel _main;
            public static MainViewModel Main
            {
                get
                {
                    if (_main == null)
                        _main = ServiceLocator.Current.GetInstance<MainViewModel>();
                    return _main;
                }
            }
        }

    navigationService的Configure方法用来添加一个键值对,从而建立起一个字符串和View的对应关系。以便于在需要导航时,只需要传递一个对应的字符串,就可以实现到具体页面的导航。

    var navigationService = this.InitNavigationService();
                SimpleIoc.Default.Register(() => navigationService);

    这两行代码是注册”一个用于导航服务的工厂类型“到IOC容器中,需要导航的时候,通过ServiceLocator.Current.GetInstance<INavigationService>()获取导航服务的工厂的实例,然后通过之前配置的不同的字符串来实现到不同页面的额导航。

    所以在登录成功后,导航代码如下:

    var navigation = ServiceLocator.Current.GetInstance<INavigationService>();
     navigation.NavigateTo("Main",model);
                    
     ViewModelLocator.Main.User = model;//之前说定义Main为静态目的就是为了方便传递参数,具体用途就在这儿

    通过如上代码,就可以实现导航了。

    九.怎么弹出消息提示框呢?

    在我们code-behind模式中,我们是这样做的

    await new MessageDialog("Hello world!").ShowAsync();

    但是在MVVM中该怎么做呢?显然不能在ViewModel中写吧。那应该就还是写在View对应的.cs文件中了。那么写在View对应的.cs中后,怎么才能触发它呢?

    对此,我们找MVVMLight的Messager来帮忙,可以在Login中登录失败的if-else分支中,添加如下代码

    GalaSoft.MvvmLight.Messaging.Messenger.Default.Send<string>("用户名或密码错误!!!");

    这代表发送的消息类型是字符串,消息内容就是”“号里面的了。

    只有发送不行啊。。。必须有个接收机制吧。。接收的怎么写呢?写在哪儿呢?

    在那儿接收,当然就写在哪儿了。。需要在View中接收,就写在View对应的Page的构造函数里面。写法如下:

            public LoginView()
            {
                this.InitializeComponent();
                GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<string>(this, MessageBox);
            }
            private async void MessageBox(string msg)
            {
                await new MessageDialog(msg).ShowAsync();
            }

    对了,其实就是注册一个对string类型消息的监听(不知道用”监听“这个词好不好,实际是通过广播进行的,具体可以百度)

    然后还要添加一个处理消息的委托方法MessageBox,没错,它里面实现了弹出一个MessageDialog的功能。

    值得注意的是,注册消息的类型可以是各种各样的,但都是通过匹配具体的类型来实现消息广播的。

    九.上面是弹出一个消息框,那么接下来就弹出一个可以交互的消息框,启示就一个确认和一个返回按钮。。

    按理来说,这个应该是点击注销的时候弹出的确认对话框,所以把注册这个消息的代码应该放在MainView对应得.cs文件中。

    具体如下:

    public MainView()
            {
                this.InitializeComponent();
                GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<object>(this,true, LogoffMessage);
              
            }
            public async void LogoffMessage(object param)
            {
                
                MessageDialog msg = new MessageDialog(param as string);
                UICommand yes = new UICommand("确定", (o) =>
                 {
                     var navigation = ServiceLocator.Current.GetInstance<INavigationService>();
                     navigation.GoBack();
                 });
                UICommand no = new UICommand("返回", (o) =>
                {
                });
                msg.Commands.Add(yes);
                msg.Commands.Add(no);
    
                 var re=await msg.ShowAsync();
                if (re == yes)
                {
                    GalaSoft.MvvmLight.Messaging.Messenger.Default.Unregister<object>(this, LogoffMessage);
                }
            }

    和之前基本无差别,只不过是把之前的MessaeDialog多加了两个UICommand而已。。

    但有两点要注意:

    1.我们注册两个消息,类型应该要不一样,否则会串的。。。

    2.但完成注销工作后,记得取消对消息的监听,否则第二次注册的时候会弹出两次对话框的!!

    至此。整个Demo就完成了。。。嗯。感觉写的还挺烂的。

    最后,我把关键点提一下:

    1.ViewModelBase

    2.RelayCommand

    3.Messager

    4.NavigationService

    5.SimpleIoc基本上就是这五点。

    哦。。差点把源代码忘了。。点击”我是代码“下载。

  • 相关阅读:
    PCMan:Ubuntu下的最快的文件管理器
    Android 不需要root权限也可卸载系统应用的adb命令
    Directory Opus --- 布局灵活的文件管理,支持文件预览,强烈推荐
    Charles 抓包网络接口请求 修改请求参数 修改返回数据
    做直播app功能测试,怎么开展?
    Python字典内置函数和方法
    pycharm2020.3安装破解版激活教程 附:无限重置时间插件(mac windows linux)
    if not用法
    nvm安装与使用
    Python批量安装.Txt文件中的类库包
  • 原文地址:https://www.cnblogs.com/cjw1115/p/5060652.html
Copyright © 2011-2022 走看看