[UWP]一種利用Behavior 將StateTrigger的集中管理的方案

来源:http://www.cnblogs.com/waynebaby/archive/2016/03/30/UWP-USE-Behavior-Instead-Of-Adaptive-Trigger.html
-Advertisement-
Play Games

不做開篇廢話,我們發現: AdaptiveTrigger 不夠好 我們知道,UWP可以在一個頁面適應不同尺寸比例的屏幕。一般來說這個功能是通過官方推薦的AdaptiveTrigger 進行的。 比如這樣: 我們可以看到這樣的的Trigger制定了最小值,隱含了條件“當滿足長寬都大於於這個條件時,這個 ...


不做開篇廢話,我們發現:

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一般來說,分層主要分三層即:UI(User Interface) 界面顯示層,BLL(Business Logic Layer)業務邏輯層,以及DAL(Data Access Layer)數據訪問層。 首先來看下三層之間的引用關係吧 簡單說下使用三層的原因:區分層次的目的即為了“高內聚低耦合”的思想 ...
  • (1)為什麼要使用Enum? (4)如果enum中的部分成員顯式定義了值,而部分沒有;那麼沒有定義值的成員還是會按照上一個成員的值來遞增賦值,例如: (5)enum枚舉成員可以用來作為位標誌,同時支持位的操作(位與,位或等等),例如:??? 十六進位數的一個作用就是用來進行位運算和操作,很方便。 ( ...
  • 【提取內容圖片地址】 【去掉字元串中的數字】 ...
  • 看完覺得不錯,適合作為學習資料,就轉載過來了 原文鏈接:http://www.cnblogs.com/Wayou/archive/2012/09/20/EF_CodeFirst.html 這是上周就寫好的文章,是在公司浩哥的建議下寫的,本來是部門裡面分享求創新用的,這裡貼出來分享給大家。 最近在對M ...
  • 1.文件目錄操作命令 (1) ls 顯示文件和目錄列表 a ls -l 顯示文件的詳細信息 b ls -a 列出當前目錄的所有文件,包含隱藏文件。 c stat '目錄/文件' 顯示指定目錄/文件的相關信息,比ls命令顯示的內容更多 (2) mkdir '目錄' 創建目錄 (3) touch '文件 ...
  • 1:使用epplus合併多個excel文件到同一excel的不同sheet頁中 2:設置excel文件sheet頁的 頁邊距(使用epplus) ...
  • 來源:http://www.cnblogs.com/Wayou/archive/2012/09/20/EF_CodeFirst.html Entity Framework的全稱是ADO.NET Entity Framework,是微軟開發的基於ADO.NET的ORM(Object/Relationa ...
  • C#中的委托與游戲中的運用 1.什麼是委托 在C/C++中,有函數指針的概念,即指向函數的指針。當我們調用該指針時,就相當於調用了該指針所指向的函數,這就是函數指針的一個作用,而他另一個作用就是將自己作為其他函數的參數。 但是指針是直接訪問記憶體單元的,程式員對指針的不恰當使用常常會引發錯誤。因此作為 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...