惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
Help Net Security
Help Net Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
A
Arctic Wolf
Cyberwarzone
Cyberwarzone
The Hacker News
The Hacker News
P
Privacy & Cybersecurity Law Blog
C
Cybersecurity and Infrastructure Security Agency CISA
P
Privacy International News Feed
V
Vulnerabilities – Threatpost
Schneier on Security
Schneier on Security
Project Zero
Project Zero
Cisco Talos Blog
Cisco Talos Blog
L
LINUX DO - 最新话题
Hacker News: Ask HN
Hacker News: Ask HN
The Last Watchdog
The Last Watchdog
Forbes - Security
Forbes - Security
SecWiki News
SecWiki News
H
Heimdal Security Blog
Latest news
Latest news
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
H
Help Net Security
AWS News Blog
AWS News Blog
Cloudbric
Cloudbric
W
WeLiveSecurity
I
InfoQ
B
Blog RSS Feed
Webroot Blog
Webroot Blog
博客园 - 三生石上(FineUI控件)
T
Tor Project blog
Last Week in AI
Last Week in AI
Recent Announcements
Recent Announcements
GbyAI
GbyAI
S
Security Affairs
T
Tailwind CSS Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
MongoDB | Blog
MongoDB | Blog
T
Threatpost
罗磊的独立博客
L
LINUX DO - 热门话题
C
CXSECURITY Database RSS Feed - CXSecurity.com
aimingoo的专栏
aimingoo的专栏
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hacker News - Newest:
Hacker News - Newest: "LLM"
Google DeepMind News
Google DeepMind News
Application and Cybersecurity Blog
Application and Cybersecurity Blog

博客园 - MSFT:waywa 韦恩卑鄙

【干货】个人工作文档节选:XAML MVVM 框架易用性细节优化Tips MVVM Sidekick Win8 RT 轻量级MVVM框架开源 对资源加读写锁的容器 对不能用using的成对操作,快速扩展IDisposable的方法 intel I7平台Win7 x64 下wpf、silverlight 与aero特效动画缓慢故障排除一则 今天被Prism里面的一段代码雷到了 1080*1920 下看网站很爽 游戏大厅 从基础开始(8)--绕回来细说聊天室(下)垃圾列表 游戏大厅 从基础开始(7)--绕回来细说聊天室(中间偏下)之女仆编年史2 游戏大厅 从基础开始(6)--绕回来细说聊天室(中)之女仆编年史1 - MSFT:waywa 韦恩卑鄙 游戏大厅 从基础开始(5)--绕回来细说聊天室(上) 简单的树遍历枚举器v0.2-挑战一个程序员到底能多懒- 添加广度优先遍历 一时技痒 不用模拟第一印象的构造 通过三个观察得来的规律解决N^2个往返接力问题 游戏大厅 从基础开始(4)-通过L2X用配置文件反射组装程序(VB only) 关于silverlight depencyobject简单的clone silver light 2 客户端的伪DataTable for EF X360吉他英雄4鼓套装 &1 TB硬盘入手 游戏大厅 从基础开始(3.5)——最吸引眼球的部分 客户端与服务器的连接 的实现 游戏大厅 从基础开始(3)——最吸引眼球的部分 客户端与服务器的连接
[UWP]一种利用Behavior 将StateTrigger集中管理的方案
MSFT:waywa 韦恩卑鄙 · 2016-03-30 · via 博客园 - MSFT:waywa 韦恩卑鄙

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

AdaptiveTrigger 不够好

我们知道,UWP可以在一个页面适应不同尺寸比例的屏幕。一般来说这个功能是通过官方推荐的AdaptiveTrigger 进行的。
比如这样:


<VisualState.StateTriggers>

</VisualState.StateTriggers>

我们可以看到这样的的Trigger制定了最小值,隐含了条件“当满足长宽都大于于这个条件时,这个状态会被触发;但如果有更严格的条件被触发,那么优先触发更严格的那个状态"

这听上去是个高大上,暗含模式匹配概念的好主意。但是如果你对其中的条件哪怕有一点拿不住,这样的trigger往往会造成开发中的混乱。

AdaptiveTrigger模糊的命中规则

比如下面的例子

例:

A:

B:

这两个货究竟谁会优先被触发?你得手动实验。

而且由于你不知道到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
{
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