MVVM 是一個強大的架構,基本從 WPF 開始,wr(我說的就是微軟)就提倡使用 MVVM。它能夠將界面和後臺分離,讓開發人員能夠不關心界面是怎樣,全心投入到後臺代碼編寫中。git
而後在編寫完後臺代碼後,能夠快速和界面設計師作出來的界面綁定到一塊兒,即便頻繁修改界面也幾乎不須要去修改後臺代碼。程序員
更讓人喜歡的是,他可讓咱們簡單地進行單元測試,由於咱們能夠不打開界面進行測試功能,方便了咱們的測試開發。github
UWP 雖然能夠直接在xaml.cs 寫邏輯可是咱們是推薦使用 MVVM 框架,寫一個本身的框架也很簡單。express
本文主要:如何在 UWP 使用 MVVM,如何作一個本身的框架。編程
MVVM 是 View、Model、 ViewModel 合起來的稱呼。數組
View 就是界面。軟件中,能夠這樣看,咱們看到的都是界面,看不到的就是後臺,在 UWP 中咱們說的 View 通常是 page UserControl 等。咱們寫界面時用 xaml 和 cs 結合起來,作出好看的效果。數據結構
ViewModel 是界面的抽象,這裏咱們不須要去理會界面長什麼樣,我只須要知道咱們須要給界面提供什麼。這就是說咱們能夠無論界面而將業務邏輯抽象出來。ViewModel 能夠簡單單元測試,由於咱們不須要打開界面。架構
Model 是核心邏輯,有些大神說, Model 只定義數據結構,有些大神說 model 寫核心邏輯,這個就仁者見仁智者見智了。我是將核心邏輯寫進 Model,若是以爲這樣不對,歡迎討論。併發
可是咱們如今的問題是怎麼讓 ViewModel 抽象 View,隨後簡單地把界面聯繫起來呢?app
使用 Binding 便可,這是 WPF 強大的地方,而 UWP 繼承併發揚了這些特性。
關於Model是屬於哪些代碼所放的地方,我找到一篇博客,在CodeProject,也是最近10月寫的,依照他的邏輯,是支持Model寫業務邏輯,ViewModel寫表示邏輯的見解。請看下面圖片,博客在:https://www.codeproject.com/Tips/813345/Basic-MVVM-and-ICommand-Usage-Example
咱們下面說下綁定。
咱們有多種方式綁定 ViewModel 。關於 ViewModel 實現的位置有下面幾種。
寫在xaml.cs,這是最簡單的方式,可使用代碼或在xaml綁定DataContent和ViewModel
寫成 xaml 靜態資源,這個方式咱們使用次數仍是比較多,可讓 Code 不寫代碼就能夠綁定 DataContent 和ViewModel
寫在一個 ViewModel 靜態類,咱們把其餘頁面的 ViewModel 統一寫到一個 MainViewModel ,並且他是靜態或只有一個實例,這樣能夠在任何地方調用到。
寫在 App.xaml 靜態資源。這個方式和寫在 xaml 差很少,只是能夠在 xaml 設置 Page 的 DataContent 。
寫在App.xaml一個靜態 ViewModelLocate 包括用到的 ViewModel 。這個方式是 MVVMLight 作的,我模仿他的想法,推薦使用這個方法。
下面我簡單介紹這幾種方式。
最簡單的方法,是在xaml.cs 寫一個 ViewModel ,假如咱們的 ViewModel 叫 Linmodel ,咱們能夠在 xaml.cs 寫相似下面的
public MainPage() { ViewModel = new LinModel(); this.InitializeComponent(); DataContext = ViewModel; } private LinModel ViewModel { set; get; }
咱們也能夠把 ViewModel 換成其餘名字,遇到須要具體什麼名稱就使用最好的。
注意咱們的ViewModel 實現的地方通常是在InitializeComponent以前,也就是放在類的構造的最前或直接以下面同樣
private LinModel ViewModel { set; get; }=new LinModel();
這個方式是6以後纔有的,初始化值能夠寫在自動屬性定義。 由於咱們須要的 ViewModel 幾乎不會修改,因此咱們還可以下面,去掉set 。不多會在實現 ViewModel 後在別的地方修改。可是咱們在後面會看到,咱們使用了頁面導航傳參,傳的是 ViewModel ,這時咱們就不能設置 set 去掉。但咱們能夠設置 private set;
private LinModel ViewModel { get; }
由於咱們不須要使用 public ,咱們就能夠這樣簡單寫 ViewModel ,除了須要記得咱們的ViewModel 的實現須要在InitializeComponent以前,還須要記得 DataContent 須要在InitializeComponent以後。
DataContent 的另外一個寫法是寫在 xaml ,很簡單,這個方法咱們要修改ViewModel 的訪問private爲public,下面代碼寫在頁面Page
DataContext="{Binding RelativeSource={RelativeSource Self},Path=ViewModel}"
RelativeSource 能夠綁定到xaml.cs,咱們就簡單能夠從 cs 得到 ViewModel
這是一個簡單的方法。
我建議你們把 DataContext 寫在 xaml ,至於爲什麼這樣是我推薦的,賣個關子,你們本身試試,把 DataContext 寫在xaml.cs和 xaml 中看下 xaml 的提示補全,就知道爲什麼推薦這個方法。
說完了簡單方法,咱們來講下
ViewModel 寫在 xaml ,xaml.cs不寫代碼這個方式。 ViewModel 須要有 static 的屬性,這個屬性的類就是ViewModel自己,也就是 ViewModel 能夠實現的只有一個。固然 static 不是必需的,咱們依靠靜態資源就能夠綁定到 ViewModel 的屬性,從而綁定 ViewModel 。
<Page.Resources> <view:LinModel x:Key="LinModel"></view:ViewModel> </Page.Resources> <Grid DataContext="{Binding Source={StaticResource LinModel},Path=ViewModel}"> </Grid>
public class LinModel { public LinModel() { ViewModel = this; } public static LinModel ViewModel { set; get; }//讓綁定能夠訪問 ViewModel ,其實這裏咱們也能夠不使用static }
注意咱們不能把 DC 寫在 Page ,若是寫在 Page ,運行Cannot find a Resource with the Name/Key
咱們用到 staticResource ,咱們爲了能夠在頁面使用 DataContent ,咱們能夠把靜態寫在App.xaml
<Application x:Class="JiHuangUWP.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:JiHuangUWP" xmlns:view="using:JiHuangUWP.ViewModel" RequestedTheme="Light"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary> <local:LinModel x:Key="LinModel"></local:LinModel> </ResourceDictionary> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application> <Page x:Class="Framework.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Framework" xmlns:view="using:Framework.ViewModel" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" DataContext="{Binding Source= {StaticResource LinModel},Path=ViewModel}" mc:Ignorable="d">
咱們這個寫法可讓 cs 不寫代碼,若是咱們有多個相同頁面,那麼咱們不可使用這個辦法。
咱們要把static去掉也是能夠,這是這樣咱們在 Code 就不能使用LinModel.ViewModel 得到 ViewModel 。咱們上面辦法是能夠再也不 Code 寫代碼,因此去掉static,其實影響幾乎沒有
public class LinModel { public LinModel() { ViewModel = this; } public /*static*/ LinModel ViewModel { set; get; } }
那麼去掉了 static ,是否是咱們就沒有辦法在 xaml.cs 得到 ViewModel ?在軟件開發中,怎麼能夠說不可能呢,咱們有一個簡單的方法。咱們不是從 DataContext 綁定 ViewModel ,那麼 DataContext 就是 ViewModel ,咱們拿出 DataContext 轉換,因而獲得 ViewModel 。注意 DC 寫的地方,千萬不要在一開始寫,若是發現你的 DC 是 Null ,那麼你寫的確定不對
InitializeComponent(); ViewModel = (LinModel) DataContext;
這是一個簡單方法,其實有一些比較難作,我將和你們說去作一個本身的框架。
咱們說完了在App.xaml 使用靜態資源,還沒說如何寫一個類,包含咱們的 ViewModel ,而後寫出靜態資源,咱們全部的 ViewModel 都從他這裏拿。
咱們下面開始說這個方法,這個方法是 MVVMLight 使用的,想要看 MVVMLight 入門的,請去看叔叔寫的入門
咱們定義個類,這個類叫 ViewModelLocater ,假如咱們有兩個 ViewModel ,一個是 AModel ,一個是 LinModel ,那麼咱們在 ViewModelLocater 寫兩個屬性。
public AModel AModel { set; get; } public LinModel LinModel { set; get; }
而後在 App.xaml 寫靜態 ViewModelLocater
<Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary> <view:ViewModelLocater x:Key="ViewModelLocater"></view:ViewModelLocater> </ResourceDictionary> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources>
這樣咱們就能夠在 APage 和 LinPage 的 Page 寫 DataContent 綁定ViewModel
<Page x:Class="Framework.View.APage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Framework.View" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" DataContext="{Binding Source={StaticResource ViewModelLocater},Path=AModel}" mc:Ignorable="d">
這樣,咱們就不須要在每一個 ViewModel 寫一個和類型是 ViewModel 的屬性。
固然,這個方法還可讓全部的 ViewModel 繼承一個類,作出 ViewModel 數組,保存全部的 ViewModel ,而後作一個索引,這樣在添加一個新的 ViewModel ,咱們只須要在數組添加一個,不須要添加一個屬性。那麼咱們每添加一個 ViewModel ,還要去手動添加數組一個 ViewModel 實在就得很差,有沒一個方法讓咱們的軟件自動去把全部的 ViewModel 添加到數組?固然有,請看反射獲取全部類
locater是我在MVVMLight學的,你們可使用這個方式。 -->咱們用使用反射,首先咱們須要知道反射是什麼?
Reflection ,中文翻譯爲反射。
這是 .Net 中獲取運行時類型信息的方式,.Net 的應用程序由幾個部分:‘程序集(Assembly)’、‘模塊(Module)’、‘類型(class)’組成,而反射提供一種編程的方式,讓程序員能夠在程序運行期得到這幾個組成部分的相關信息。
因此咱們可使用反射得到軟件的全部類,獲取所有 ViewModel 類。咱們能夠在 ViewModelLocater 使用 ViewModel 數組,使用反射得到全部 ViewModel ,知道添加他們到數組。好處是:咱們添加一個 ViewModel 類時,不須要去修改 ViewModelLocater 。
咱們須要一個識別 反射獲得的類是屬於咱們 ViewModel 的方法,很簡單,假如咱們的 ViewModel 是 LinModel ,咱們裏面有了 AModel 和BModel。
咱們定義一個 Attribute ,讓每一個 ViewModel 都使用咱們定義的 Attribute ,因而咱們知道了哪些就是 ViewModel 。咱們不使用判斷反射獲得的 Class 是否是繼承 ViewModelBase 的緣由是有一些 ViewModel 咱們是不放在 ViewModelLocater ,咱們只標識咱們要放在 ViewModelLocater 的ViewModel
public class ViewModelLocaterAttribute : Attribute { } [ViewModelLocaterAttribute] public class AModel:ViewModelBase { public override void OnNavigatedFrom(object obj) { throw new NotImplementedException(); } public override void OnNavigatedTo(object obj) { throw new NotImplementedException(); } } public class APage : Page { } //不放在ViewModelLocater public class ListModel:ViewModelBase { public override void OnNavigatedFrom(object obj) { throw new NotImplementedException(); } public override void OnNavigatedTo(object obj) { throw new NotImplementedException(); } } public class ViewModelLocater:ViewModelBase { public ViewModelLocater() { var applacationAssembly = Application.Current.GetType().GetTypeInfo().Assembly; foreach (var temp in applacationAssembly.DefinedTypes .Where(temp => temp.CustomAttributes.Any(t => t.AttributeType == typeof(LinModelAttribute)))) { var viewmodel = temp.AsType().GetConstructor(Type.EmptyTypes).Invoke(null); Type page=null; try { page= applacationAssembly.DefinedTypes.First(t => t.Name.Replace("Page", "") == temp.Name.Replace("Model", "")).AsType(); } catch { //InvalidOperationException //提醒沒有page //throw new Exception("沒有"+temp.Name.Replace("Model","")+"Page"); } ViewModel.Add(new ViewModelPage() { ViewModel = viewmodel as ViewModelBase, Page = page }); } } public override void OnNavigatedFrom(object obj) { } public override void OnNavigatedTo(object obj) { } }
var applacationAssembly = Application.Current.GetType().GetTypeInfo().Assembly; foreach (var temp in applacationAssembly.DefinedTypes .Where(temp => temp.CustomAttributes.Any(t => t.AttributeType == typeof(LinModelAttribute)))) { var viewmodel = temp.AsType().GetConstructor(Type.EmptyTypes).Invoke(null); }
上面的作法還把 ViewModel 和 Page 綁起來,咱們須要規定命名,規定命名就能夠簡單把 ViewModel 獲得他的 Page 。
咱們從Application.Current.GetType().GetTypeInfo().Assembly 得到全部 Assembly ,而後使用applacationAssembly. DefinedTypes 獲得類型,判斷類型是否是有咱們的AttributeWhere(temp => temp.CustomAttributes.Any(t => t.AttributeType == typeof(CodeStorageAttribute))))。
若是是的話,咱們就實現 ViewModel ,咱們須要把 TypeInfo 轉爲 Type ,好在有一個方法AsType(),若是從 type 實現構造,能夠去看我以前寫的博客。
咱們實現 ViewModel 以後,咱們須要把 ViewModel 對應的 Page 綁定,咱們判斷命名,若是名字符合,那麼就綁定。命名的方式是 ViewModel 個格式是xxModel, page 是xxPage
這個作法是一個框架:caliburn.micro作的,他可讓咱們不在 Page 寫 DataContent 綁定 ViewModel 就和我綁定了 ViewModel ,使用的方法也是我上面說的方法。在這裏我再次表達對H神敬意,是他讓我開啓了UWP的反射神器,今後走向深淵。
咱們開始說如何作一個本身的框架。
在上面使用綁定的方法,咱們能夠看到,咱們須要一個類來存放 page 和 ViewModel ,咱們的 ViewModel 之間的通訊比較難作,因而咱們爲了讓開發簡單,咱們作一個簡單的 ViewModel ,這個是核心,在程序運行就存在一個。
咱們寫一個類,這個類是保存 ViewModel 和View
這個類有Type Page頁面,ViewModelBase ViewModel,若是咱們有多個頁使用相同 ViewModel ,咱們須要使用 key 分開相同的ViewModel
咱們這個類就須要下面不多的屬性
public string Key { set; get; } public ViewModelBase ViewModel { set; get; } public Type Page { set; get; }
可是你們也看到,這個須要在使用前就實現 ViewModel ,若是咱們想要在使用 ViewModel 才實現,那麼咱們須要Type _viewModel,從 type 進行構造能夠去看我以前的博客
咱們在這個類寫方法 Navigate 判斷 ViewModel 是否實現,若是沒有,那麼從 type 進行構造。
若是咱們是在測試,沒有 UI ,那麼咱們不跳轉 Page ,請看代碼
public async Task Navigate(Frame content, object paramter) { if (ViewModel == null) { ViewModel = (ViewModelBase) _viewModel.GetConstructor(Type.EmptyTypes).Invoke(null); } ViewModel.OnNavigatedTo(paramter); # if NOGUI return; # endif await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { content.Navigate(Page); }); }
咱們在測試是沒有 UI ,咱們就不跳轉。可是使用跳轉能夠是在後臺,因此須要await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,他可讓後臺線程訪問UI。
咱們這個類須要ViewModelBase viewModel, Type page輸入
public ViewModelPage(Type viewModel, Type page) { _viewModel = viewModel; Page = page; }
或
public ViewModelPage(Type viewModel, Type page,string key=null) { _viewModel = viewModel; Page = page; }
而後咱們須要讓 ViewModel 繼承的類,他能夠很簡單,可是基本的幾個功能能夠跳轉、能夠被跳轉、能夠通訊的功能仍是寫在他這裏。
public abstract class ViewModelBase { }
咱們基本的 ViewModel 須要在屬性更改通知,我以前寫了一個類 https://github.com/lindexi/UWP/blob/master/uwp/src/ViewModel/NotifyProperty.cs
咱們須要繼承這個,除了這個以外,原來跳轉頁面的參數是寫在Page的 OnNavigatedTo ,但咱們想讓 ViewModel 知道咱們跳轉,咱們的 ViewModel 通訊須要INavigable
public interface INavigable { /// <summary> /// 不使用這個頁面 /// 清理頁面 /// </summary> /// <param name="obj"></param> void OnNavigatedFrom(object obj); /// <summary> /// 跳轉到 /// </summary> /// <param name="obj"></param> void OnNavigatedTo(object obj); }
全部的 ViewModel 繼承這個,爲什麼讓 ViewModel 繼承他,是由於咱們不想每次離開、使用都new 一個,咱們使用的是一個,一旦咱們不使用這個頁面,使用 From ,這樣讓頁面清理。能夠提升咱們的使用,在 MasterDetail ,老是切換頁面,能夠不須要實現那麼多的 ViewModel 。咱們還可使用他來保存咱們當前的使用,咱們所輸入,可是一旦輸入多了,這個並非很好用,主要看你是須要什麼。
若是咱們的 ViewModel 有頁面,能夠跳轉,咱們要繼承
public interface INavigato { Frame Content { set; get; } void Navigateto(Type viewModel, object parameter); }
Content 就是 ViewModel 能夠跳轉頁面,咱們的 Navigateto 提供 viewmodel 的 type 或 key ,輸入參數。這是在一個頁面裏能夠有跳轉使用,假如咱們使用的頁面是一個 MasterDetail ,咱們就須要兩個頁面,一個是列表,一個是內容,因而咱們就可使用他來跳轉。
咱們在 ViewModelBase 把 ViewModel 包含的頁面 ViewModel 數組
public List<ViewModelPage> ViewModel { set; get; } = new List<ViewModelPage>();
若是咱們的頁面 LinModel 存在多個能夠跳轉的頁面 AModel 、 BModel ,咱們就把他放進base. ViewModel ,須要跳轉,就遍歷 ViewModel ,拿出和輸入相同 type 、 key 的 ViewModel ,使用他的跳轉,由於咱們把 ViewModel 和 View 都放一個類,咱們直接使用類的跳轉就好。
public abstract class ViewModelBase : NotifyProperty, INavigable, INavigato { public List<ViewModelPage> ViewModel { set; get; } = new List<ViewModelPage>(); public Frame Content { set; get; } public abstract void OnNavigatedFrom(object obj); public abstract void OnNavigatedTo(object obj); public async void Navigateto(Type viewModel, object paramter) { _viewModel?.OnNavigatedFrom(null); ViewModelPage view = ViewModel.Find(temp => temp.ViewModel.GetType() == viewModel); await view.Navigate(Content, paramter); _viewModel = view.ViewModel; } //當前ViewModel private ViewModelBase _viewModel; }
咱們這樣寫如何綁定,咱們能夠經過跳轉頁面傳入 ViewModel ,咱們須要在 ViewModelPage 的 Navigate ,傳入對應的ViewModel
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { content.Navigate(Page,ViewModel); });
而後在頁面OnNavigatedTo的參數拿ViewModel,注意下面用的轉換,若是參數不是LinModel就好出異常,通常咱們拿的參數都是使用as。下面例子是故意這樣寫,在符合咱們的規範,是不會存在炸了的狀況。
protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); ViewModel = (LinModel) e.Parameter; }
這時,咱們須要 DataContent 就寫在 ViewModel 的後面
好啦,我把這個作出模板,你們能夠去下載
上面的模板適合於只有一個主界面,而後其餘頁面都是沒有跳轉。那麼咱們能夠作一個靜態的 ViewModel ,其餘頁面都直接從 ViewModel 中拿。
假如咱們有個頁面 APage , AModel ,那麼把 AModel 寫在ViewModel
咱們可使用在xaml DataContent 綁定拿到,因而xaml. cs 也簡單能夠拿到
<Page x:Class="Framework.View.APage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Framework.View" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" DataContext="{Binding Source={StaticResource ViewModel},Path=AModel}" mc:Ignorable="d"> public APage() { this.InitializeComponent(); ViewModel = (AModel) DataContext; } private AModel ViewModel { get; }
每一個頁面直接通訊都是主頁面傳進來,而頁面直接是沒有通訊,只有一個主頁面,主頁面能夠跳轉多個頁面。
這是簡單的漢堡。在個人應用,圖牀 https://www.microsoft.com/store/apps/9nblggh562r2 用到
開始是進入主頁面,主頁面有圖牀、信息、設置三個頁面,因而這個三個頁面都在主頁面,而這三個頁面都沒有跳轉頁面,因此他們能夠從 MainViewModel 拿到本身的 ViewModel 。他們的通訊都是跳轉主頁面傳給他們,三個頁面沒有傳輸信息。對於設置頁面,咱們是放在一個存儲數據類,因此咱們不須要傳參數,直接從存儲拿。
可是這個仍是沒解決在一個 ViewModel 裏面,存在多個 ViewModel 之間的通訊。
在個人私密密碼本
https://www.microsoft.com/store/apps/9nblggh5cc3g
個人建立密碼頁面須要和密碼本聯繫,在建立密碼建立一個密碼,就把密碼放到密碼本
因此咱們上面的不能作到,咱們須要添加一些新的。咱們不可讓兩個頁面直接聯繫,咱們須要讓一個頁面和他的上層聯繫,讓上層發給他要聯繫頁面。
關於這個是如何作,你們能夠看下面的 MasterDetail ,這個我放在後面,後面的纔是好的。
咱們用咱們上面寫的來作一個 MasterDetail ,我以前作了一個簡單
咱們須要作的:如何讓兩個頁面通訊
咱們的 B 頁面要和A通訊,咱們讓B發送信息到上一級頁面,由上一級頁面傳給A。
咱們須要一個信息,他是有發送者,目標、發送內容,發送了什麼
public class Message { public Message() { } /// <summary> /// 發送者 /// </summary> public ViewModelBase Source { set; get; } /// <summary> /// 目標 /// </summary> public string Goal { set; get; } public object Content { set; get; } /// <summary> /// 發送什麼信息 /// </summary> public string Key { set; get; } }
咱們還須要 ISendMessage 、IReceiveMessage
到時咱們的 MasterModel 就會有一個 ISendMessage 屬性,咱們會在 DetailMasterModel 中給他,固然咱們老是把 DetailMasterModel 做爲屬性,因此咱們可能在使用他的類給 MasterModel 的 ISendMessage 一個值,這個就是 IOC 。
咱們來寫這兩個,很簡單
interface ISendMessage { void SendMessage(Message message); } interface IReceiveMessage { void ReceiveMessage(Message message); }
咱們使用的發送具體的是使用 Master 的,因此咱們寫MasterSendMessage
public class MasterSendMessage : ISendMessage { public MasterSendMessage(Action<Message> sendMessage) { _sendMessage = sendMessage; } public void SendMessage(Message message) { _sendMessage?.Invoke(message); } private Action<Message> _sendMessage; }
到時咱們在 DetailMaster 中實現 MasterSendMessage 傳給MasterModel
咱們以個人密碼原本說,咱們有一個是左邊是一列密碼,右邊點擊是顯示內容。
那麼咱們是使用一個 ListModel 和 ContentModel ,咱們的數據是
public class KeySecret : NotifyProperty { public KeySecret() { } public string Name { set { _name = value; OnPropertyChanged(); } get { return _name; } } public string Key { set { _key = value; OnPropertyChanged(); } get { return _key; } } private string _key; private string _name; }
在 ListModel 有一個ObservableCollection<KeySecret> KeySecret
public ObservableCollection<KeySecret> KeySecret { set { _keySecret = value; OnPropertyChanged(); } get { return _keySecret; } } public ISendMessage SendMessage { set; get; }
在 ContentModel 有一個public KeySecret Key和接收。
CodeStorageModel 有 DetailMaster 、ContentModel ListModel
其中 DetailMaster 控制界面,他的功能你們能夠直接複製到本身的項目,不過還須要複製 MasterDetailPage ,複製好了,那麼須要修改的是<Frame x:Name="List" SourcePageType="local:ListPage"></Frame> <Frame x:Name="Content" SourcePageType="local:ContentPage"></Frame>把一個換爲本身的列表頁,一個換爲詳情。如何使用,我會在後面說。
在 CodeStorageModel 跳轉須要設置 ListModel 跳轉,咱們一開始就顯示,因而他也要,咱們須要把 MasterSendMessage 實現,給 list ,這樣就是一個 IOC 。
DetailMaster.Narrow(); MasterSendMessage temp=new MasterSendMessage(ReceiveMessage); ListModel = new ListModel() { SendMessage = temp }; ListModel.OnNavigatedTo(null); ContentModel = new ContentModel();
大神說除了 foreach ,不能使用 temp ,我這時也用了 temp ,是想告訴你們不要在使用。
這樣咱們須要在 CodeStorageModel 寫一個接收,還記得 DetailMasterModel 在點擊須要使用函數,咱們接收有時有不少,咱們須要判斷他的key,若是是」點擊列表」,那麼咱們須要佈局顯示。
public void ReceiveMessage(Message message) { if (message.Key == "點擊列表") { DetailMaster.MasterClick(); } if (message.Goal == nameof(ContentModel)) { ContentModel.ReceiveMessage(message); } }
ContentModel.ReceiveMessage 能夠把 key 改成點擊列表
public void ReceiveMessage(Message message) { if (message.Key == "點擊列表") { Key=message.Content as KeySecret; } }
咱們界面就不說了,直接去 https://github.com/lindexi/UWP/tree/cd1637bf31eb22a230390c205da93f840070c49d/uwp/src/Framework/Framework
我要講下修改,咱們發現咱們如今寫的兩個頁面通訊在 MasterDetail 有用,可是要肯定咱們的頁面,這樣很差,在上面咱們說能夠加功能不須要去修改寫好的,咱們須要作的是接收信息,不使用上面的。
你們去看代碼注意我是在新的 master 代碼和如今的不一樣,注意連接
如何使用個人 MasterDetail 框架,我下面和你們說。
首先是複製DetailMasterMode,關於這個是如何寫,我在以前的博客有說,若是但願知道如何製做一個DetailMaster
public class DetailMasterModel : NotifyProperty { public DetailMasterModel() { SystemNavigationManager.GetForCurrentView().BackRequested += BackRequested; Narrow(); } public int GridInt { set { _gridInt = value; OnPropertyChanged(); } get { return _gridInt; } } public int ZFrame { set { _zFrame = value; OnPropertyChanged(); } get { return _zFrame; } } public GridLength MasterGrid { set { _masterGrid = value; OnPropertyChanged(); } get { return _masterGrid; } } public GridLength DetailGrid { set { _detailGrid = value; OnPropertyChanged(); } get { return _detailGrid; } } public int ZListView { set { _zListView = value; OnPropertyChanged(); } get { return _zListView; } } public bool HasFrame { set; get; } public Visibility Visibility { set { _visibility = value; OnPropertyChanged(); } get { return _visibility; } } public void MasterClick() { HasFrame = true; Visibility = Visibility.Visible; Narrow(); } public void Narrow() { if (Window.Current.Bounds.Width < 720) { MasterGrid = new GridLength(1, GridUnitType.Star); DetailGrid = GridLength.Auto; GridInt = 0; if (HasFrame) { ZListView = 0; } else { ZListView = 2; } } else { MasterGrid = GridLength.Auto; DetailGrid = new GridLength(1, GridUnitType.Star); GridInt = 1; } } private GridLength _detailGrid; private int _gridInt; private GridLength _masterGrid; private Visibility _visibility = Visibility.Collapsed; private int _zFrame; private int _zListView; private void BackRequested(object sender, BackRequestedEventArgs e) { HasFrame = false; Visibility = Visibility.Collapsed; Narrow(); } }
而後把它放到ViewModel
public DetailMasterModel DetailMaster { set; get; }
在 ViewModel 構造使用DetailMaster.Narrow();還有標題欄AppViewBackButtonVisibility
DetailMaster = new DetailMasterModel(); SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible; DetailMaster.Narrow();
而後在界面複製下面代碼
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <VisualStateManager.VisualStateGroups > <VisualStateGroup CurrentStateChanged="{x:Bind View.DetailMaster.Narrow}"> <VisualState> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="720"/> </VisualState.StateTriggers> <VisualState.Setters > <!--<Setter Target="Img.Visibility" Value="Collapsed"></Setter>--> </VisualState.Setters> </VisualState> <VisualState> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowHeight="200"> </AdaptiveTrigger> </VisualState.StateTriggers> <VisualState.Setters > </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="{x:Bind View.DetailMaster.MasterGrid,Mode=OneWay}"></ColumnDefinition> <ColumnDefinition Width="{x:Bind View.DetailMaster.DetailGrid,Mode=OneWay}"></ColumnDefinition> </Grid.ColumnDefinitions> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Canvas.ZIndex="{x:Bind View.DetailMaster.ZListView,Mode=OneWay}"> <!--<Grid Background="Black"></Grid>--> <TextBlock Text="List" HorizontalAlignment="Center"></TextBlock> <Frame x:Name="List" SourcePageType="local:ListPage"></Frame> </Grid> <Grid Grid.Column="{x:Bind View.DetailMaster.GridInt,Mode=OneWay}" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Canvas.ZIndex="{x:Bind View.DetailMaster.ZFrame}"> <Image Source="ms-appx:///Assets/Strawberry_Adult_content_easyicon.net.png"></Image> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Visibility="{x:Bind View.DetailMaster.Visibility,Mode=OneWay}"> <TextBlock Text="content" HorizontalAlignment="Center"></TextBlock> <!--<Grid Background="#FF565500"></Grid>--> <Frame x:Name="Content" SourcePageType="local:ContentPage"></Frame> </Grid> </Grid> </Grid> </Grid>
注意把<Frame x:Name="List" SourcePageType="local:ListPage"></Frame>換爲列表頁面,和<Frame x:Name="Content" SourcePageType="local:ContentPage"></Frame>換爲內容,<Image Source="ms-appx:///Assets/Strawberry_Adult_content_easyicon.net.png"></Image>換爲本身的圖片
須要在xaml.cs寫 ViewModel 爲 view ,若是不是,那麼本身換名。
頁面的聯繫使用ISendMessage,和接收,他向 MasterDetailViewModel 發信息,讓 ContentModel 接收。
咱們須要和上面寫的同樣,傳入 MasterSendMessage 給他,讓他能夠發送信息。
而後判斷髮送信息,發給內容,具體能夠去看代碼,若是有不懂請發郵件或在評論,這很簡單
咱們寫 CodeStorageAttribute ,這個是咱們一個頁面,他包含的 ViewModel 。
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] sealed class CodeStorageAttribute : Attribute { }
咱們在 ListModel 和 ContentModel 寫CodeStorageAttribute
而後咱們能夠在CodeStorageModel
var applacationAssembly = Application.Current.GetType().GetTypeInfo().Assembly; foreach (var temp in applacationAssembly.DefinedTypes .Where(temp => temp.CustomAttributes.Any(t => t.AttributeType == typeof(CodeStorageAttribute)))) { var viewmodel = temp.AsType().GetConstructor(Type.EmptyTypes).Invoke(null); Type page = null; try { page = applacationAssembly.DefinedTypes.First( t => t.Name.Replace("Page", "") == temp.Name.Replace("Model", "")).AsType(); } catch { //InvalidOperationException //提醒沒有page //throw new Exception("沒有"+temp.Name.Replace("Model","")+"Page"); } ViewModel.Add(new ViewModelPage( viewmodel as ViewModelBase,page)); }
我修改ISendMessage
public interface ISendMessage { EventHandler<Message> SendMessageHandler { set; get; } }
判斷咱們的 ViewModel 是否是 ISendMessage ,頁面是先上一級發送,因此咱們把 SendMessageHandler 添加
foreach (var temp in ViewModel.Where(temp => temp.ViewModel is ISendMessage)) { ((ISendMessage)temp.ViewModel).SendMessageHandler += (s, e) => { ReceiveMessage(e); }; }
咱們刪除public ContentModel ContentModel public ListModel ListModel在 ListPage 和 Content ,咱們直接使用索引
在CodeStorageModel
public ViewModelBase this[string str] { get { foreach (var temp in ViewModel) { if (temp.Key == str) { return temp.ViewModel; } } return null; } }
修改ListPage dateContent
DataContext="{Binding Source={StaticResource ViewModel},Path=CodeStorageModel[ListModel]}"
ContentPage 的dateContent
DataContext="{Binding Source={StaticResource ViewModel},Path=CodeStorageModel[ContentModel]}"
在CodeStorageModel OnNavigatedTo
public override void OnNavigatedTo(object obj) { DetailMaster.Narrow(); foreach (var temp in ViewModel) { temp.ViewModel.OnNavigatedTo(null); } }
這樣咱們就不須要去寫一個 ListModel 在咱們寫 CodeStorageModel ,咱們也不知道哪一個頁面會發送,不知哪一個頁面接收,咱們直接在接收看信息發送的哪一個,找出,使用他的接收
public void ReceiveMessage(Message message) { if (message.Key == "點擊列表") { DetailMaster.MasterClick(); } foreach (var temp in ViewModel) { if (temp.Key == message.Goal) { var receive = temp.ViewModel as IReceiveMessage; receive?.ReceiveMessage(message); } } }
咱們能夠作的頁面的聯繫,咱們不知道咱們有哪些頁面,若是看到我寫錯請評論
所有源代碼
https://github.com/lindexi/UWP/tree/master/uwp/src/Framework/Framework
不想每次都本身寫不少類,能夠下載個人模板
下載後放在 C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\ProjectTemplates\CSharp\Windows Root\Windows UAP 的 文件夾裏
而後執行devenv /setup
咱們就能夠在新建項目使用模板