不做开篇废话,我们发现:

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运行流程:

  1. 获取监视目标
  2. 获取可以操控的StateTrigger
  3. 订阅监视目标感兴趣值的变化
  4. 根据值判断哪个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的控制模式对大家的开发有所启发帮助。

另外完整的代码在这里

https://github.com/waynebaby/GreaterShareUWP/blob/master/GreaterShare/GreaterShare/Glue/StateTriggerActiveReadingBehavior.cs

[UWP]一种利用Behavior 将StateTrigger集中管理的方案的更多相关文章

  1. 【转】利用Behavior Designer制作敌人AI

    http://www.unity.5helpyou.com/3112.html 本篇unity3d教程,我们来学习下利用Behavior Designer行为树插件来制作敌人AI,下面开始! Beha ...

  2. silverlighter下MVVM模式中利用Behavior和TargetedTriggerAction实现文本框的一些特效

    在silverlight一般开发模式中,给文本框添加一些事件是轻而易举的,然而MVVM开发模式中,想要给文本框添加一些事件并非那么容易,因为MVVM模式中,只有ICommand接口,而且也只有Butt ...

  3. Linux企业生产环境用户权限集中管理项目方案案例

    企业生产环境用户权限集中管理项目方案案例: 1 问题现状 当前我们公司里服务器上百台,各个服务器上的管理人员很多(开发+运维+架构+DBA+产品+市场),在大家登录使用Linux服务器时,不同职能的员 ...

  4. unity5打包机制下,一种资源打ab和资源管理的方案

    unity5打包机制下,一种资源打ab和资源管理的方案.1.打ab: 1.设置平台 2.清楚所有资源的assetbundlename: string[] abNameArr = AssetDataba ...

  5. 两种利用GCD实现分步获取结果的方式和SDWebImage缓存机制的验证

    前段时间写界面,因为数据的请求分成了两部分,所以用到了多线程,实现数据的分步请求,然后自己写了一个Demo,用两种方式实现分步获取内容,其中也包含了验证SDWebImage这个库的缓存机制,在这里给大 ...

  6. 一种利用 Cumulative Penalty 训练 L1 正则 Log-linear 模型的随机梯度下降法

    Log-Linear 模型(也叫做最大熵模型)是 NLP 领域中使用最为广泛的模型之一,其训练常采用最大似然准则,且为防止过拟合,往往在目标函数中加入(可以产生稀疏性的) L1 正则.但对于这种带 L ...

  7. 一种利用异常机制基于MVC过滤器的防止重复提交的机制分享

    防止重复提交验证机制 某些时候因为系统反应稍慢,急性子用户可能不耐烦会进行重复的提交,这个操作不仅可能造成系统负担,也可能产生垃圾数据. 出现这两种状况都是我们不希望的. 为此,在公司项目系统设计了以 ...

  8. 一种利用ADO连接池操作MySQL的解决方案(VC++)

    VC++连接MySQL数据库 常用的方式有三种:ADO.mysql++,mysql API ; 本文只讲述ADO的连接方式. 为什么要使用连接池? 对于简单的数据库应用,完全可以先创建一个常连接(此连 ...

  9. iOS开发小技巧--UIButton的另一种布局方法(第一种在layoutSubViews方法中,这一种利用苹果提供的两个返回CGRect的方法)

随机推荐

  1. python 引用和对象理解

    今天浏览博客的时候看到这么一句话: python中变量名和对象是分离的:最开始的时候是看到这句话的时候没有反应过来.决定具体搞清楚一下python中变量与对象之间的细节.(其实我感觉应该说 引用和对象 ...

  2. 远程桌面Default.rdp 中各个参数的含义(转)

    存储在 Default.rdp 文件中的设置 默认情况下,将在“我的文档”文件夹中创建 Default.rdp 文件.以下 RDP 设置存储在 Desktop.rdp 文件中: desktopwidt ...

  3. string.Format , object[] args 使用

    string sql = "insert into TableA values('{0}','{1}','{2}',GetDate(),'{3}' "; sql = string. ...

  4. Model层数据验证

    问题1:View层如何向Controller的Action传递Model数据?在View中,可以使用Form表单进行模型数据的提交,同样的,我们需要关联提交数据的类型,则需要在View中使用@mode ...

  5. wav文件格式分析(三)

    (四)附表 1.头格式表: 2.PCM数据的存放方式 3.PCM波形样本的数据格式 WAVE文件的每个样本值包含在一个整数i中,i的长度为容纳指定样本长度所需的最小字节数. 首先存储低有效字节,表示样 ...

  6. 修改开机启动等待时间(for Ubuntu12.10)

    Ubuntu的开机启动等待时间默认是10s,等待时间比较长,每次启动都得按一下回车,于是就想修改一下等待时间.我们可以找到Grub的配置文件(/boot/grub/grub.cfg),在其中进行个性化 ...

  7. redis linux安装与简单集群配置

    由于项目原因最近在使用redis,把redis的安装以及配置记录下来方便查看. 1.下载 地址http://download.redis.io/releases/  需要哪个版本就使用那个版本 2.解 ...

  8. 比较任意两个JSON串是否相等(比较对象是否相等)JAVA版

    废话少说,直接入题. 在面向对象语言中,经常会比较两个对象是否相等,而比较的大多是实体类实例,也就是封装数据的那些类实例,或者是Map.List互相嵌套成的复杂数据结构. 比较对象是否相等,常见的思路 ...

  9. Gradle中使用idea插件的一些实践

    如果你的项目使用了Gradle作为构建工具,那么你一定要使用Gradle来自动生成IDE的项目文件,无需再手动的将源代码导入到你的IDE中去了. 如果你使用的是eclipse,可以在build.gra ...

  10. centos 关闭防火墙

    在centos上搭建了个服务器,本机可以访问,局域网无法访问 解决方案:关闭防火墙 sudo systemctl stop firewalld.service 令人恼火的是stop iptables之 ...