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

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. 关于iphone、安卓手机VPN全面解析

    现在智能手机功能越来越强大,网络APP层出不穷,社交大佬facebook.twitter等纷纷推出了自己的社交APP应用,大部分手机已经内置了很多社交应用,包括facebook等:android.io ...

  2. 模拟Linux的shell

    在学习了Linux的进程控制之后,学习了fork函数和exec函数族,通过这些个函数可以简单的实现一份shell,就是实现一份命令行解释器,当然是简单版的,实现功能如下 能执行普通的命令如ls ,ps ...

  3. getline函数的用法

    函数声明 bool getline(istream &in, string &s) 功能说明: 从输入流读入一行到变量string s,及时是空格也可以读入. –直到出现以下情况为止: ...

  4. SQL中Where与Having的区别

    “Where” 是一个约束声明,使用Where来约束来之数据库的数据,Where是在结果返回之前起作用的,且Where中不能使用聚合函数. “Having”是一个过滤声明,是在查询返回结果集以后对查询 ...

  5. Programming Assignment 4: 8 Puzzle

    The Problem. 求解8数码问题.用最少的移动次数能使8数码还原. Best-first search.使用A*算法来解决,我们定义一个Seach Node,它是当前搜索局面的一种状态,记录了 ...

  6. C语言extern作用(全局变量)

    用C语言编写程序的时候,我们经常会遇到这样一种情况:希望在头文件中定义一个全局变量,然后包含到两个不同的c文件中,希望这个全局变量能在两个文件中共用. 举例说明:项目文件夹project下有main. ...

  7. Winpcap构建用户级网桥

    Winpcap构建网桥 根据winpcap sdk中的user-level-bridge用户级网桥 |机器1                 |                  |机器2   | | ...

  8. 人脸识别经典算法一:特征脸方法(Eigenface)

    这篇文章是撸主要介绍人脸识别经典方法的第一篇,后续会有其他方法更新.特征脸方法基本是将人脸识别推向真正可用的第一种方法,了解一下还是很有必要的.特征脸用到的理论基础PCA在另一篇博客里:特征脸(Eig ...

  9. Python操作memcached及redis

    Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度 ...

  10. 数据库知识整理<八>

    联接: 8.1理解简单的单联接: 基本上联接的结果是每个集合的笛卡尔积.例如:两个集合{a,b,c}和{a,b}的笛卡尔积是如下的成对集合:{(a,a),(a,b),(b,a),(b,b),(c,a) ...