在上一篇博文《[UWP]不那么好用的ContentDialog》中我们讲到了ContentDialog在复杂场景下使用的几个令人头疼的弊端。那么,就让我们在这篇博文里开始愉快的造轮子之旅吧!

首先要向大家说明:这篇博文主要还是写的构建Picker时的思考过程,如果不感兴趣的,可以直接略过这篇,阅读下一篇《[UWP]如何使用Picker实现一个简单的ColorPicker弹窗》。

首先针对上篇文章中讲到的ContentDialog的几个缺点,先来梳理一下我们对新的弹窗层组件的需求:

  • 能实现正常Dialog的弹出关闭流程(必须的,不然怎么来替代ContentDialog?);
  • 可以支持同时弹出多层弹窗(解决ContentDialog的弊病);
  • 适用于MVVM框架,提供给ViewModel层调用接口(越简单越友好越好,松耦合);
  • 友好的返回值(方便调用方直接获取想要的结果);
  • 要实现ContentDialog类似的可定制化能力(弹出位置,自定义UI,动画等)。

下面的文章里,为了方便描述,我把实现上诉功能的新弹窗层组件称之为Picker(类似于UWP文件选择器的叫法)。

简化一下需求,我们的Picker主要有三个功能规划:

  1. Picker的调用呈现方式为弹出一个类似于ContentDialog的弹窗,但是可以多层级的调用(即多层弹窗);
  2. Picker的业务逻辑实现层遵循共同的调用约束(即实现Picker定义的相关接口),基于此约束实现的MVVM页均可实现Picker的调用方式;
  3. 实现Picker约束的相关类本身实现独立的Pick功能(即调用后返回特定类型值)。

我们简单的类比下,上面对Picker的功能规划其实有点类似于Windows系统中的文件查看逻辑。

在Windows系统中,可以处理某类格式文件(例如.mp4文件)的程序可以向系统申明支持该格式,当系统需要处理这类文件时(例如双击打开.mp4文件),将会调用这个程序(或者询问用户使用哪一个支持该类文件的程序)来处理该文件。

有了明确的功能规划,我们就可以来思考如何实现了。

如何实现Picker的界面层呈现

我们已经说过了,ContentDialog的内部实现其实是依赖Popup,因此我们同样可以用Popup来承载Picker的界面显示。

Popup本身是一个非常基础的组件(UWP中另外一个常用的组件Flyout也是基于Popup实现的),它在UWP应用的可视化树中是处于最顶层的(PopupRoot),且呈现在应用最前面(可以理解为具有最大值的ZIndex)。基于Popup我们可以实现很多不依赖程序主界面的UI功能,并且不受ContentDialog只能同时显示一个的限制。

这样的话就非常简单了,我们可以构建一个ObjectPicker类,它提供类似ContentDialog类中ShowAsync的方法,调用此方法时创建一个新的Popup,然后将我们要显示的界面元素作为其Child添加到界面上(实际上,结合MVVM框架逻辑,我们使用Frame装载目标页面),在用户完成Pick操作后关闭Popup,并且返回操作结果。

呈现过程的核心,三句代码足以:

    var popup = new Popup();
//ToDo 装载Picker界面
popup.IsOpen = true;
//ToDo 处理Pick交互操作
popup.IsOpen = false;

当然,涉及到具体实现时,由于我们要考虑到界面呈现的UI交互、位置及动画,实际代码会更多一点。

ObjectPicker类只负责呈现目标页面,它不负责处理任何的业务逻辑。

如何实现Picker的业务逻辑

在我们的规划中,Picker的实际业务逻辑是在Picker页面的逻辑层代码,即ViewModel层中实现的。这样的话,什么时候Picker可以关闭是由其ViewModel决定的。为了使ViewModel的处理结果可以通知到Picker,我们需要让实现Picker功能的ViewModel遵循一个共同的约束。

我们定义这样一个泛型接口:

public interface IObjectPicker<T>
{
event EventHandler<ObjectPickedEventArgs<T>> ObjectPicked;
event EventHandler Canceled;
}

其中T代表这个ViewModel支持的Pick对象类型,当用户完成Pick操作时触发ObjectPicked事件,用户取消Pick操作时触发Canceled事件,ObjectPicker类将在Show的时候注册这两个事件来接受ViewModel的处理结果。

如何优雅的调用Picker

当我们实现了一个Picker以后,如何来调用它呢?使用最简单的方法,直接在需要调用Picker的地方new一个ObjectPicker,然后调用ShowAsync来获取返回值?

这看起来没什么问题,但是实际应用中,我们的Picker可能有很多个(文字输入框,颜色选取器等)。我们希望有一个类似于Windows系统注册表的服务,它提供:

  • 不同类型的Picker注册接口及管理;
  • 提供简单友好的Picker调用接口,并且返回Pick结果。

这样的话,我们可以简单的实现一个ObjectPickerService,通过它,我们的调用逻辑会非常的简单:

var pickRet = await pickerService.PickSingleObjectAsync<Color>(typeof(TestColorPickerViewModel).FullName);

这个例子可以在我的开源项目HHChaosToolkit中的Picker部分看到(GitHub链接点这里),同时也是我的下篇博文的主要内容。

结尾

这篇文章到这里就结束了,主要还是讲如何构建Picker组件的思考过程,以及我基于对MVVM结构的理解,如何来梳理Picker的架构。下一篇文章我将结合例子介绍一下Picker的实例实现及其调用流程。

写这篇博文着实有些难产,如果有什么地方写的不够好,或者难以理解,欢迎大家指正!谢谢阅读!

[UWP]使用Popup构建UWP Picker的更多相关文章

  1. Win10 UWP系列:更新UWP时注意的问题——TargetDeviceFamily

    前几天把CurrencyExchanger提交到微软参加Master认证,结果没有通过,反馈了一些错误,看来微软检查还是比较仔细的. 错误主要有: Visual feedback helps user ...

  2. UWP入门教程1——UWP的前世今生

    目录 引言 设备族群 UI 和通用输入模式 通用控件和布局面板 工具 自适应扩展 通用输入处理 引言 在本篇文章中,可以掌握以下知识: 设备族群,如何决定目标设备 新的UI控件和新面板帮助你适应不同的 ...

  3. win10 uwp 如何开始写 uwp 程序

    本文告诉大家如何创建一个 UWP 程序. 这是一系列的 uwp 入门博客,所以写的很简单 本文来告诉大家如何创建一个简单的程序 安装 VisualStudio 在开始写 UWP 需要安装 Visual ...

  4. UWP 新手教程1——UWP的前世今生

    文件夹 引言 设备族群 UI 和通用输入模式 通用控件和布局面板 工具 自适应扩展 通用输入处理 引言 在本篇文章中,可以掌握下面知识: 设备族群,怎样决定目标设备 新的UI控件和新面板帮助你适应不同 ...

  5. [UWP]使用Picker实现一个简单的ColorPicker弹窗

    在上一篇博文<[UWP]使用Popup构建UWP Picker>中我们简单讲述了一下使用Popup构建适用于MVVM框架下的弹窗层组件Picker的过程.但是没有应用实例的话可能体现不出P ...

  6. [UWP]实现一个轻量级的应用内消息通知控件

    在UWP应用开发中,我们常常有向用户发送一些提示性消息的需求.这种时候我们一般会选择MessageDialog.ContentDialog或者ToastNotification来完成功能. 但是,我们 ...

  7. [UWP]了解模板化控件(10):原则与技巧

    1. 原则 推荐以符合以下原则的方式编写模板化控件: 选择合适的父类:选择合适的父类可以节省大量的工作,从UWP自带的控件中选择父类是最安全的做法,通常的选择是Control.ContentContr ...

  8. [UWP 自定义控件]了解模板化控件(10):原则与技巧

    1. 原则 推荐以符合以下原则的方式编写模板化控件: 选择合适的父类:选择合适的父类可以节省大量的工作,从UWP自带的控件中选择父类是最安全的做法,通常的选择是Control.ContentContr ...

  9. 是时候 UWP 了 !

    什么是 UWP? 很多程序员都有一个梦想:希望自己开发的软件能够轻而易举的在所有平台上运行,而不是把同样的需求,用不同的技术.工具重新开发才能够运行在所有平台上.这就是跨平台,很多软件从业者都在为这个 ...

随机推荐

  1. spring 之 BeanDefinition & BeanDefinitionParser

    xml bean factory 的解析过程的 堆栈大概是这样的: at org.springframework.beans.factory.xml.NamespaceHandlerSupport.f ...

  2. 201772020113李清华《面向对象程序设计(java)》第一周学习总结

    201772020113<面向对象程序设计(java)>第一周学习总结 第一部分:课程准备部分 填写课程学习 平台注册账号, 平台名称 注册账号 博客园:www.cnblogs.com b ...

  3. leetcode5

    public class Solution { private int lo, maxLen; public String LongestPalindrome(String s) { int len ...

  4. python中的多进程与多线程(二)

    1.使用多线程可以有效利用CPU资源,线程享有相同的地址空间和内存,这些线程如果同时读写变量,导致互相干扰,就会产生并发问题,为了避免并发问题,绝不能让多个线程读取或写入相同的变量,因此python中 ...

  5. Ansible运维工具

    1.Ansible是一款极为灵活的开源工具套件,能够大大简化Unix管理员的自动化配置管理与流程控制方式.可以通过命令行或者GUI来使用Ansible,运行Ansible的服务器这里俗称“管理节点”: ...

  6. python的if条件判断

    python的条件判断书写格式: 基本格式 if 条件判断:    #条件判断通过,则执行下面的语句 执行语句 执行语句 ... else:    #条件判断不通过,则执行下面的语句 执行语句 执行语 ...

  7. mysql修改删除You can't specify target table for update in FROM clause的问题

    表中出现重复数据,需要删除重复数据,只保留一条 DELETE FROM crm_participant WHERE id IN ( SELECT c.id cid FROM crm_participa ...

  8. CentOS7使用ZFS文件系统

    默认情况下,CentOS7并没有含ZFS支持的文件和,需要进行更新和安装第三方库. Step 1:安装第三方库和更新系统 [root@localhost ~]# rpm -Uvh http://www ...

  9. python shell的交互模式和文本编辑模式

    之前学python的时候,是拿<笨办法学python>练习的. 书里面基本都是以.py文件去写代码,也就是文本编辑模式. 而交互模式(也就是powershell),唯有在input用户输入 ...

  10. MySQL 树节点递归遍历所以子节点

    DELIMITER $$ DROP FUNCTION IF EXISTS `getChildList`$$ CREATE FUNCTION `getChildList`(rootId INT) RET ...