[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基本上就是這五點。

 

哦。。差點把源代碼忘了。。點擊”我是代碼“下載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM