之前學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); } }
除了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>("確認注銷嗎?"); } }
里面有兩點可能造成困惑,一是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基本上就是這五點。
哦。。差點把源代碼忘了。。點擊”我是代碼“下載。
