1. 程式人生 > >[UWP]一種利用Behavior 將StateTrigger集中管理的方案

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

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

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的控制模式對大家的開發有所啟發幫助。
另外完整的程式碼在這裡

相關推薦

[UWP]利用Behavior StateTrigger集中管理方案

不做開篇廢話,我們發現: AdaptiveTrigger 不夠好 我們知道,UWP可以在一個頁面適應不同尺寸比例的螢幕。一般來說這個功能是通過官方推薦的AdaptiveTrigger 進行的。 比如這樣: <VisualState x:Name="NarrowView"> <Visu

利用語音深度神經網路進行語音識別的新方案

A NOVEL SCHEME FOR SPEAKER RECOGNITION USING A PHONETICALLY-AWARE DEEP NEURAL NETWORK Yun Lei Nicolas Scheffer Luciana Ferrer Mitchell McLaren 美國加

利用Cmake,使得低版本Visual Studio IDE快速執行高版本VS專案的方法~

我們在實際程式設計中,經常會遇到如下情形: 電腦上安裝的是VS2008,需要執行的專案竟然是用VS2010或者VS2012編寫的,如何用VS2008,快速的執行高版本的專案,是一個很實際的操作。 首先,這種情況完全可以通過重新手動新建VS2008工程,手動新增檔案的方式解

Tomcat專案釋出的三方式;:直接專案(檔案)直接複製到tomcat/webapps下二:在tomcat/conf/server.xml配置tomcat的虛擬路徑;

Tomcat專案釋出的方式 一種:直接將專案(檔案)直接複製到tomcat/webapps下 部署專案的方式一 將專案(檔案)複製到tomcat/webapps中 啟動伺服器 訪問專案 二種:在t

利用百分比佈局適配所有android手機螢幕解析度的方法

場景1、團隊裡面,UI設計師往往只提供以一個標準解析度來設計的UI設計稿,用於Android、iOS、H5三端。2、Android手機解析度眾多,如何讓UI在不同解析度的手機上面能有相同的效果?解決方案在網上看到有一種解決方案是按照螢幕解析度根據基準解析度來等比縮放,經過實際

unity5打包機制下,資源打ab和資源管理方案

remove 自己 return game tor 基礎 resource mea easyn unity5打包機制下,一種資源打ab和資源管理的方案。1.打ab:   1.設置平臺   2.清楚所有資源的assetbundlename:     string[] abN

低侵入性的元件化方案 之 傳統元件化方案的問題

github開源地址 github.com/beyondxia/m… 傳統元件化方案介紹     元件化的核心問題為元件間的解耦,而解耦就不可避免的要面臨解決元件間的通訊問題,即通訊機制。按照通訊機制的維度來區分,可以大致概括為如下兩種方案:協議通訊、介面通訊。二者的基本實現原理如下。 1、協議通訊

新的自動化 UI 測試解決方案 Airtest Project

今天分享一個自動化UI測試工具airtest——一款網易出品的基於影象識別面向遊UI測試的工具,也支援原生Android App基於元素識別的UI自動化測試。主要包含了三部分:Airtest IDE、Airtest(用截圖寫指令碼)和 Poco(用介面UI元素來寫指令碼)。 來自google的評價:

MDC介紹 -- 多執行緒下日誌管理實踐方式

一:MDC介紹   MDC(Mapped Diagnostic Context,對映除錯上下文)是 log4j 和 logback 提供的一種方便在多執行緒條件下記錄日誌的功能。某些應用程式採用多執行緒的方式來處理多個使用者的請求。在一個使用者的使用過程中,可能有多個不

【問底】伍藝:基於Rsync演算法的資料庫備份方案設計

根據容災備份系統對備份類別的要求程度,資料庫備份系統可以分為資料級備份和應用級備份。資料備份是指建立一個異地的資料備份系統,該系統是對原本地系統關鍵應用資料實時複製。當出現故障時,可由異地資料系統迅速恢復本地資料從而保證業務的連續性。應用級備份比資料備份層次更高,即在異地建

更優雅的Flutter Dialog解決方案

# 前言 系統自帶的Dialog實際上就是Push了一個新頁面,這樣存在很多好處,但是也存在一些很難解決的問題 - **必須傳BuildContext** - loading彈窗一般都封裝在網路框架中,多傳個context引數就很頭疼;用fish_redux還好,effect層直接能拿到context

(轉)企業生產環境用戶權限集中管理方案案例

網管 pac 成本 local 6.2 whoami start blog 員工 https://wenku.baidu.com/view/0acd163d4a73f242336c1eb91a37f111f1850d94.html http://blog.51cto.com

利用Guzzle實現另PHP異步發送郵件(laravel5.4)

dot 博文 接下來 lar 時間 重點 5.4 targe 占用 前言:第二種實現方法 方法的思路: 此方法的實現需要借助Guzzle這個PHP的HTTP客戶端,用來輕而易舉地發送請求,並集成到我們的WEB服務上(laravel中如何引入guzzle不多說) 使用該方

傳小米推區塊鏈寵物“加密兔”,挖礦已成為營銷手段傳小米推區塊鏈寵物“加密兔”,挖礦已成為

小米加密兔3月10日,有網友在微信群分享了疑似小米區塊鏈產品“加密兔”的鏈接,小米或將推出自己的區塊鏈遊戲項目。 一、關於小米區塊鏈寵物“加密兔” 從曝光的2張微信群聊天截圖來看,小米應該正式進軍區塊鏈遊戲領域了。 從小米“加密兔”看區塊鏈遊戲,挖礦已成為一種營銷手段 歡迎使用加密兔區塊鏈寵物(以下簡稱:加密

Dual Path Networks(DPN)——一種結合了ResNet和DenseNet優勢的新型卷積網絡結構。深度殘差網絡通過殘差旁支通路再利用特征,但殘差通道不善於探索新特征。密集連接網絡通過密集連接通路探索新特征,但有高冗余度。

哪裏 esc 數學 itemid tip 視覺 bat tlist badge 如何評價Dual Path Networks(DPN)? 論文鏈接:https://arxiv.org/pdf/1707.01629v1.pdf在ImagNet-1k數據集上,淺DPN超過

次面向對象的體操:JSON字符串轉換為嵌套對象的方法

不同的 else num 安全 exceptio 1.9 下劃線 ppi cas 能不能把這個JSON串轉成相應的對象,更易於使用呢? 為了方便講解,這裏重復寫下JSON串。 { "item:s_id:18006666": "1024", "item:s_i

Python3基礎 str translate 指定字符轉換成另特定字符

default charm strong .py 實踐 lock right fault pycha ? python : 3.7.0 OS : Ubuntu 18.04.1 LT

Python爬蟲處理JS翻頁的方法,利用Ajax非同步請求

前端方面知識不是很好,只是想解決有關Python爬蟲翻頁的問題 =。=  如有不對,還望指正 瀏覽器:Google 利用區域性更新這種翻頁的方式,同樣需要進行一個url請求,因此我們的目的就是找到這個url 1.分析 如圖所示,頁面翻頁採用了JS的方法 &nb

區塊鏈引入全新的語言 開啟加密經濟新時代

    本質上,區塊鏈是一種永久儲存交易記錄的科技,而且交易記錄無法被刪除,只能序貫更新,從而建立了一條永無止境的歷史蹤跡。這個看上去簡單的功能性描述,卻有著意義深遠的含義。它引導我們對建立交易、儲存資料和移動資產的傳統方式進行重新思考,而這一切僅僅剛剛開始。   區塊鏈不能

c#:無法 NULL 轉換成“System.DateTime”,因為它是值型別

摘自:http://www.blogjava.net/parable-myth/archive/2010/09/30/333454.html 在C# 2.0裡面的資料型別中,分為值型別和引用型別,引用型別可以初始化為null,可是值型別是不可以的。 比如經常用到的System.Guid型別,要麼賦值為Gu