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
咱们就能够在新建项目使用模板