[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
更多相關文章
  • 可空值類型,正如字面意義上的,是可以為NULL的值類型。 這個東西存在的意義可以解決比如資料庫的的Int可以為NUll的情況,使得處理資料庫數據更簡單。 實際上可空值類型就是Nullable<T>這個泛型值類型,而C#有一種更簡單的語法糖是int?這種用法: 可空值類型的更多玩法 在大多數時候用C# ...
  • Hello! 歡迎新老朋友來到這裡,這裡隨時恭候你的大駕。 接下來說說三層架構↓↓↓↓↓↓ 三層架構分為:表現層(UI(User Interface))、業務邏輯層(BLL(Business Logic Layer))、數據訪問層(DAL(Data Access Layer))再加上實體類庫(Mod ...
  • 1、解析簡單Json字元串 2、從Json字元串中解析Json數組 持續更新中,敬請期待... ...
  • 日常開發的絕大多數系統中,都涉及到管理用戶的登錄和授權問題。登錄功能(Authentication),針對於所有用戶都開放;而授權(Authorization),則對於某種用戶角色才開放。 在asp.net mvc中,微軟雖然已經幫助開發者構建了ASP.NET Identity這樣強大的驗證授權框架 ...
  • 一、開發環境 操作系統:Win10 編譯器:VS2013 .Net版本:.net framework4.5 二、涉及程式集 Spring.Core.dll:1.3.1 Common.Logging.dll 三、開發過程 1.項目結構 2.編寫Product.cs namespace SpringNe... ...
  • 作者:[美]Adam Freeman 來源:《精通ASP.NET MVC 4》 前面建立的都是簡單的MVC程式,現在到了吧所有事情綜合在一起,以建立一個簡單但真實的電子商務應用程式的時候了。 在此打算建立的應用程式 — SportsStore (體育用品商店),將遵循隨處可見的線上商店所採取的經典方 ...
  • 1、讀取網路中html網頁內容,獲取網頁中元素body內的html,處理所有img元素的src屬性後以字元串返回 2、通過HtmlAgilityPack Html操作類庫將html格式的字元串載入為html文檔對象,再對html dom進行操作 持續更新中,敬請期待... ...
  • SqlHelper類 作用:充當一個助人為樂的角色。這個類呢,任何類都可以調用。例如:數據的“增, 刪, 改 ,查”,資料庫的鏈接,等等。 這個類是靜態類,只要用類名.方法名(),就可以使用該方法的功能了。 作用:簡化代碼,提高性能,提高運行效率。 string sql = "select * fr ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...