ContentDialog是UWP开发中最常用的组件之一,一个体验良好的UWP应用很难避免不去使用它。博客园里也有许多的文章介绍如何来利用ContentDialog实现各种自定义样式的弹窗界面。不过实际上ContentDialog是一个令人又爱又恨的组件,今天我们就来说一下ContentDialog的缺点。
ContentDialog适合实现轻量级的UI需求,但在处理复杂UI需求时非常难用,例如说:
多层级弹窗情况下的UI实现;
MVVM框架下的UI与业务逻辑的分离;
需要弹窗关闭时返回用户操作结果的情况。
上诉情况下,如果仍旧使用ContentDialog实现功能需求,会需要很多的代码来完成界面UI交互,这是多余且没有必要的。
多层级弹窗情况下的UI实现;
先说第一种情况,多层级弹窗情况下的UI实现。假设我们有一个这样的需求:我们需要弹出一个窗口让用户修改应用设置,同是在用户修改后点击“保存设置”按钮时,弹出一个自定义UI的确认对话框询问用户是否确定保存。
怎么实现呢?很自然的想到,我们可以写两个ContentDialog,一个是设置界面的弹窗,另外一个是自定义UI的确认对话框。先弹出设置弹窗,点击“保存设置”是弹出确认对话框。听起来很完美,逻辑上也没有问题,编码运行一下呢,应用崩溃了...
这是个悲剧,看下VS的崩溃信息:
Only a single ContentDialog can be open at any time.
WTF!!! UWP应用同时只支持唤出一个ContentDialog 么?这也太坑了吧!
不要惊讶,事实上确实如此,关于这点,微软官方给出的解决方案是这样的:
Only one ContentDialog can be shown at a time. To chain together more than one ContentDialog, handle the Closing event of the first ContentDialog. In the Closing event handler, call ShowAsync on the second dialog to show it.
也就是说想要同时显示两个弹窗是不可能的,只能在第一个弹窗关闭后再来打开第二个。
那我们怎么让第二个弹窗出现时仍能保持第一个弹窗的工作状态呢?在这种情况下,我能想到两种解决方法,一是使用MessageDialog代替确认对话框(抛弃掉自定义UI),或者ContentDialog 内使用Frame做Page间导航,需要用户确认时,导航到确认页面。但是毫无疑问,这两种方法都极为影响用户体验。
MVVM框架下的UI与业务逻辑的分离
上面已经说到了ContentDialog 本身的限制使其很难实现复杂UI需求,而这种困难涉及到MVVM框架时情况会更为复杂一些。
我们知道一个好的基于MVVM框架构建的项目一定是结构清晰,UI交互与后台业务逻辑分离的完美状态。ContentDialog本身是一个UI组件,如果只是轻量级的UI需求,比如说只是自定义一个确认对话框,在MVVM项目中使用倒还行。但是如果是一个较为复杂的多(层级)弹窗交互需求,或者弹窗内涉及到导航服务,这种情况下,将View层与ViewModel层间的代码整理清楚就有些困难了。
在之前的一个项目中,我有遇到这样的情况,当时的选择是使用中间人模式,搭建了一个中介类。这个中介类对ViewModel层提供打开或跳转到指定弹窗页面的接口,对View层则实现调度ContentDialog,控制ContentDialog中Frame的页导航。
这样看起来好像也还不错,功能都实现了。但是缺点是仍旧是无法实现多层弹窗,同时要考虑ViewModel调用弹窗的多种情况,实现过程比较复杂,并不能算是一个优雅的解决方式。
需要弹窗关闭时返回用户操作结果的情况
在很多情况下,我们使用弹窗的交互方式并不仅仅是交互需求,而是业务逻辑上的需要,我们想要用户做出交互,并且返回交互结果给后台代码做进一步的处理。
举个例子说,我们做一个绘画应用,我们提供给用户一个调色板来选取画笔颜色,但是这个调色板常驻在画布有些过于侵占用户绘画空间,我们的理想状态是把它做成一个颜色选取弹窗。这个弹窗需要在用户点击更换颜色时弹出来让用户选择颜色,如果用户取消选取颜色则关闭不做任何操作,如果确定选取某一颜色则关闭并返回选取的颜色。如果用ContentDialog来做会怎么样呢?ContentDialog关闭时会返回一个类型为ContentDialogResult的对象来标识用户操作,其定义如下:
//
// 摘要:
// 指定用于指示 ContentDialog 的返回值的标识符。
public enum ContentDialogResult
{
//
// 摘要:
// 未点击按钮。
None = 0,
//
// 摘要:
// 主按钮由用户点击。
Primary = 1,
//
// 摘要:
// 辅助按钮由用户点击。
Secondary = 2
}
那么要实现上面的需求我们需要在ContentDialog中先暂存用户选取的颜色,在拿到返回结果后,如果值为ContentDialogResult.Primary则去取出暂存的颜色,否则不做任何处理。
听起来这已经是个完美的方案了,但是还是有个大问题:我们选取颜色是在一个颜色盘上点击想要的颜色的位置取色,而ContentDialog的返回结果是依赖于点击预定义的几个按钮(PrimaryButton/SecondaryButton/CloseButton),这种情况下,对于UI交互的限制非常大,我们无法实现在颜色盘上取色后立即关闭弹窗,并且返回结果。
结尾
说了这么多,那么有没有一个完美的解决方案呢?你问我有没有,肯定是有的啊!请看下图!
ContentDialog的内部实现其实是依赖Popup,这就让我有了一个大胆的想法,我们程序员最爱干的事情是什么?造轮子呀!ContentDialog不好用,造个好用的新轮子呀!
接下来几篇博文来教大家如何造一个好用的,适用于MVVM框架的弹窗层组件。有兴趣的可以先看一下我的开源项目HHChaosToolkit中的Picker部分(GitHub链接点这里)。
好的,本篇博文到此结束,不知道大家有没有收获,谢谢大家!