[UWP]一种利用Behavior 将StateTrigger集中管理的方案
不做开篇废话,我们发现:
AdaptiveTrigger 不够好
我们知道,UWP可以在一个页面适应不同尺寸比例的屏幕。一般来说这个功能是通过官方推荐的AdaptiveTrigger 进行的。
比如这样:
<VisualState x:Name="NarrowView">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="800" MinWindowHeight="600"/>
</VisualState.StateTriggers>
</VisualState>
我们可以看到这样的的Trigger制定了最小值,隐含了条件“当满足长宽都大于于这个条件时,这个状态会被触发;但如果有更严格的条件被触发,那么优先触发更严格的那个状态"
这听上去是个高大上,暗含模式匹配概念的好主意。但是如果你对其中的条件哪怕有一点拿不住,这样的trigger往往会造成开发中的混乱。
AdaptiveTrigger模糊的命中规则
比如下面的例子
例:
A:
<AdaptiveTrigger MinWindowWidth="800" MinWindowHeight="600"/>
B:
<AdaptiveTrigger MinWindowWidth="600" MinWindowHeight="800"/>
这两个货究竟谁会优先被触发?你得手动实验。
而且由于你不知道到Windows Runtime内部是怎么管理这些小玩意(本地代码),有时候你只需要简单的通过长宽比较判断的横竖屏切换竟然要烧脑一番。再加上VisualState元素里往往含有巨多的动画和属性设置,我们很难将所有的Trigger拉到一起进行有效的管理,这对页面的构建可能会产生很大的阻碍。
AdaptiveTrigger只订阅窗口大小切换VisualState是不够的
当窗口的大小不敏感,对窗体内部的一些元素大小敏感的时候,只针对窗口的大小监视显然也是不够的,我们需要更多的逻辑扩展。
| Page | ||
| Content(Size变化无法被AdaptiveTrigger订阅) | ||
比如我有一个页面“Page”,里面有一个从服务器端获取内容的控件“Content”。这时候我设置了两个VisualStateGroup: PageLayoutGroup 和 ContentLayoutGroup,分别应付外层Page的长宽变化,和内部Content的长宽变化(内容要访问服务器Render到界面以后才会知道Size). 这时候想用AdaptiveTrigger来控制ContentLayoutGroup 的State切换就玩不转了。
我们需要针对任意控件的属性监控来切换State
于是你可能想实现一个自己的StateTrigger。这时候,更大的坑出现了,我们来分析。
自定义StateTrigger的限制与风险
我们来看一个我Appconsult同事跟我们一起讨论用的例子代码:
public class SizeTrigger : StateTriggerBase
{
public SizeTrigger()
{
Window.Current.SizeChanged += Current_SizeChanged;
}
private void Current_SizeChanged(object sender, WindowSizeChangedEventArgs e)
{
Debug.WriteLine($"CurrentSize_Changed: {DateTime.Now}");
SizeObject _size = new SizeObject();
_size.width = e.Size.Width;
_size.height = e.Size.Height;
dynamic result = SetTrigger(Orientation, _size);
SetActive(result);
}
}
限制1:无法精确控制生存期
这位同事说第一次他首先想到这样做。当然他知道这里可以用WeakEventListener,但是测试嘛,先跑通看看。
但是他发现"OMG 为什么我都navigate到 Page2了这个事件还会触发到这个实例来"。
就算实现了WeakEventListener,无法控制生存期,能免得了MemoryLeak 免不了Exception啊。难道要加大号的TryCatch?那也是个消耗内!
后来他实测了WeakEventListener,果然开始一段时间事件还是丢到了未注销的订阅里面。然后他发现了新bug:当NavigationCacheMode 打开的时候 ,当离开这个页面到page2 一段时间再回来这个页面,弱引用已经被释放掉了,订阅size的功能被取消了,没有重新新订阅的机会。
所以, StateTriggerBase 没有提供明确的Onload/OnUnload 生存期注入点,成为这一类扩展的巨大限制。
于是我们顺理成章的建议,我们可以绑控件,不绑Window.Current嘛,
"给你的SizeTrigger加一个Panel属性 绑定到你得RootGrid上面,你订阅这货怎么样?"
结果遇到了第二个限制:
限制2:在VisualGroup属性内产生的Trigger,绑定其他元素经常失效或造成Xaml设计器崩溃
这点老司机们往往会有体会,当你声明对象的父节点有一层或者基层不是DP/FrameworkElements的时候,运行时可能无法得到正确的绑定上下文(在某几个版本的Windows Runtime出现过 我没有Check 最新版本) 当SizeTrigger拿不到绑定值的时候,SizeTrigger是无法订阅目标变化的。
同时我也提出提出第三个不爽的地方:
限制3:分散的逻辑仍然难以整体控制
相关的非此即彼的几个Trigger,把他们写成若干个逻辑分散的Trigger实现,还要他们分别埋在不同的State里面,生产力提高了吗?
这时候我就拿 Greater Share的代码出来给他们看我的behavior方案了
用Behavior解决问题
不是我藏私,是我写Greater Share代码的时候觉得分散管理生产力低下,一周前就写了一个自用,谁想到扩展StaeTriggerBase会有那么多坑啊(逃
Behavior设计思路
实现我们的Behavior首先要利用下面两个类型的特性
- Behavior
- 绑定友好,能够拿到各种绑定上下文
- 具有完整的 OnAttach/OnDetatch 生存期支持
- 能够附加在任何DepenedencyObject上 获取其状态和事件。
- StateTrigger
- 不含逻辑,简单根据属性的True/False进行判断是否命中
我原本就是为了生产力来设计这个Behavior。
思路是:
如果分散的逻辑很麻烦,我干啥不设计一个超然的管理器来管理多个StateTrigger呢?
集中控制,我让谁上谁就上。
这样一想就会发现,AdaptiveTrigger也一定有一个傀儡师在操控吧?
Behavior运行流程:
- 获取监视目标
- 获取可以操控的StateTrigger
- 订阅监视目标感兴趣值的变化
- 根据值判断哪个State更合适,用代码激活它
代码
大概是这个样子
public class StateTriggerActiveReadingBehavior : Behavior<Panel>
{
long NarrowTriggerPropertyReg;
long WideTriggerPropertyReg;
protected override void OnAttached() //订阅
{
AssociatedObject.SizeChanged += AssociatedObject_SizeChanged;
NarrowTriggerPropertyReg = RegisterPropertyChangedCallback(NarrowTriggerProperty, (o, a) => RefreshState());
WideTriggerPropertyReg = RegisterPropertyChangedCallback(WideTriggerProperty, (o, a) => RefreshState());
base.OnAttached();
}
private void AssociatedObject_SizeChanged(object sender, SizeChangedEventArgs e)
{
RefreshState();
}
private void RefreshState() //判断条件,选一个Trigger状态来激活。
{
//if (true)
//{
// WideTrigger.IsActive = false;
// NarrowTrigger.IsActive = true;
//}
}
protected override void OnDetaching() //注销
{
base.OnDetaching();
AssociatedObject.SizeChanged -= AssociatedObject_SizeChanged;
UnregisterPropertyChangedCallback(NarrowTriggerProperty, NarrowTriggerPropertyReg);
UnregisterPropertyChangedCallback(WideTriggerProperty, WideTriggerPropertyReg);
}
public StateTrigger NarrowTrigger //窄状态
{
get { return (StateTrigger)GetValue(NarrowTriggerProperty); }
set { SetValue(NarrowTriggerProperty, value); }
}
public static readonly DependencyProperty NarrowTriggerProperty =
DependencyProperty.Register(nameof(NarrowTrigger), typeof(StateTrigger), typeof(StateTriggerActiveReadingBehavior), new PropertyMetadata(null));
public StateTrigger WideTrigger //宽状态
{
get { return (StateTrigger)GetValue(WideTriggerProperty); }
set { SetValue(WideTriggerProperty, value); }
}
public static readonly DependencyProperty WideTriggerProperty =
DependencyProperty.Register(nameof(WideTrigger), typeof(StateTrigger), typeof(StateTriggerActiveReadingBehavior), new PropertyMetadata(null));
}
调用的时候只需绑定两个属性就可以了
<Interactivity:Interaction.Behaviors>
<Glue:StateTriggerActiveReadingBehavior
x:Name="StateTriggerActiveReadingBehavior"
WideTrigger="{Binding ElementName=wideTrigger}"
NarrowTrigger="{Binding ElementName=narrowTrigger}"/>
</Interactivity:Interaction.Behaviors>
可以规避那么多坑是我始料未及的,我们来Review一下我们刚才提到的各种问题
- AdaptiveTrigger模糊命中不确定 (完美规避)
- AdaptiveTrigger不能订阅任意来源的状态变化 (完美规避)
- CustomeTrigger生存期不能控制,容易造成未捕获异常和内存泄漏(完美规避)
- CustomeTrigger绑定不便或容易造成异常(完美规避)
- CustomeTrigger逻辑分散不集中造成生产力低下(完美规避)
此外利用了绑定技术还降低了另一种“GotToStateActionBehavior”方案对于Magic String名称的依赖,似乎还不错?
希望这种VisualState的控制模式对大家的开发有所启发帮助。
另外完整的代码在这里
[UWP]一种利用Behavior 将StateTrigger集中管理的方案的更多相关文章
- 【转】利用Behavior Designer制作敌人AI
http://www.unity.5helpyou.com/3112.html 本篇unity3d教程,我们来学习下利用Behavior Designer行为树插件来制作敌人AI,下面开始! Beha ...
- silverlighter下MVVM模式中利用Behavior和TargetedTriggerAction实现文本框的一些特效
在silverlight一般开发模式中,给文本框添加一些事件是轻而易举的,然而MVVM开发模式中,想要给文本框添加一些事件并非那么容易,因为MVVM模式中,只有ICommand接口,而且也只有Butt ...
- Linux企业生产环境用户权限集中管理项目方案案例
企业生产环境用户权限集中管理项目方案案例: 1 问题现状 当前我们公司里服务器上百台,各个服务器上的管理人员很多(开发+运维+架构+DBA+产品+市场),在大家登录使用Linux服务器时,不同职能的员 ...
- unity5打包机制下,一种资源打ab和资源管理的方案
unity5打包机制下,一种资源打ab和资源管理的方案.1.打ab: 1.设置平台 2.清楚所有资源的assetbundlename: string[] abNameArr = AssetDataba ...
- 两种利用GCD实现分步获取结果的方式和SDWebImage缓存机制的验证
前段时间写界面,因为数据的请求分成了两部分,所以用到了多线程,实现数据的分步请求,然后自己写了一个Demo,用两种方式实现分步获取内容,其中也包含了验证SDWebImage这个库的缓存机制,在这里给大 ...
- 一种利用 Cumulative Penalty 训练 L1 正则 Log-linear 模型的随机梯度下降法
Log-Linear 模型(也叫做最大熵模型)是 NLP 领域中使用最为广泛的模型之一,其训练常采用最大似然准则,且为防止过拟合,往往在目标函数中加入(可以产生稀疏性的) L1 正则.但对于这种带 L ...
- 一种利用异常机制基于MVC过滤器的防止重复提交的机制分享
防止重复提交验证机制 某些时候因为系统反应稍慢,急性子用户可能不耐烦会进行重复的提交,这个操作不仅可能造成系统负担,也可能产生垃圾数据. 出现这两种状况都是我们不希望的. 为此,在公司项目系统设计了以 ...
- 一种利用ADO连接池操作MySQL的解决方案(VC++)
VC++连接MySQL数据库 常用的方式有三种:ADO.mysql++,mysql API ; 本文只讲述ADO的连接方式. 为什么要使用连接池? 对于简单的数据库应用,完全可以先创建一个常连接(此连 ...
- iOS开发小技巧--UIButton的另一种布局方法(第一种在layoutSubViews方法中,这一种利用苹果提供的两个返回CGRect的方法)
随机推荐
- Windows server 2003 WINS的配置和使用详解
NetBios名称概述 网络中的一台计算机可以使用NETBIOS和DNS两种命名方式为其命名,在NETBIOS标准中,使用长度不超 过16个字符的名称来惟一标识每个网络资源,用于标识资源或服务类型.在 ...
- linux 下 oracle 11g r2 的卸载
1.停止oracle服务 [oracle@OracleTest oracle]$ sqlplus /nolog SQL> connect / as sysdba SQL> shutdown ...
- Linux下安装软件的一般步骤
目录 一.解析Linux应用软件安装包 二.了解包里的内容 三.搞定使用tar打包的应用软件 四.搞定使用rpm打包的应用软件 五.搞定使用deb打包的应用程序 一.解析Linux应用软件安装包(回目 ...
- 计算机图形学——OpenGL开发库开发库
vc++6.0 有 OpenGL 的东西.有头文件 GL.H, GLAUX.H, GLU.H 但没有 GLUT 软件包/工具包 如果想使用glut.h的话必须自己添加. 首先下载 OpenGL开发库, ...
- innobackupex使用实践
先介绍一下环境: MySQL:5.6.19 安装路径:/u01/mysql 数据文件:/u01/mysql/data 备份源:/u02/backup 我是异机恢复,和本机操作一样. 一. 全量备份 步 ...
- 斯坦福第十九课:总结(Conclusion)
19.1 总结和致谢 欢迎来到<机器学习>课的最后一段视频.我们已经一起学习很长一段时间了.在最后视频中,我想快速地回顾一下这门课的主要内容,然后简单说几句想说的话. 作为这门课的结束时 ...
- [php-composer] how to install composer in windows
Composer Setup 1. Choose the command-line PHP you want to use.选择使用可以命令行的PHP程序 2. proxy Settings - ch ...
- Windows2003 SQL2005解决系统Administrator密码不知道的问题
Windows2003 SQL2005解决系统Administrator密码不知道的问题 今天上班的时候,有个同事说不知道谁设置了开机密码,那台电脑一直没有开机密码的他现在进不了桌面 那台电脑没有光驱 ...
- solr与.net系列课程(八)solr中重跑索引的注意事项
solr与.net系列课程(八)solr中重跑索引的注意事项 我们如果在项目中使用solr,那肯定就是把数据库中的数据跑进solr服务器中,solr有两种操作一种是新建索引,一种是增量索引,这里我们来 ...
- js 调整排序
<html> <head> <script type='text/javascript' src='jquery-1.8.2.min.js'></script ...