1. 程式人生 > >[UWP]不那麼好用的ContentDialog

[UWP]不那麼好用的ContentDialog

原文: [UWP]不那麼好用的ContentDialog

ContentDialog是UWP開發中最常用的元件之一,一個體驗良好的UWP應用很難避免不去使用它。部落格園裡也有許多的文章介紹如何來利用ContentDialog實現各種自定義樣式的彈窗介面。不過實際上ContentDialog是一個令人又愛又恨的元件,今天我們就來說一下ContentDialog的缺點。

ContentDialog適合實現輕量級的UI需求,但在處理複雜UI需求時非常難用,例如說:

  • 多層級彈窗情況下的UI實現;
  • MVVM框架下的UI與業務邏輯的分離;
  • 需要彈窗關閉時返回使用者操作結果的情況。

上訴情況下,如果仍舊使用ContentDialog實現功能需求,會需要很多的程式碼來完成介面UI互動,這是多餘且沒有必要的。

多層級彈窗情況下的UI實現;

先說第一種情況,多層級彈窗情況下的UI實現。假設我們有一個這樣的需求:我們需要彈出一個視窗讓使用者修改應用設定,同是在使用者修改後點選“儲存設定”按鈕時,彈出一個自定義UI的確認對話方塊詢問使用者是否確定儲存。

怎麼實現呢?很自然的想到,我們可以寫兩個ContentDialog,一個是設定介面的彈窗,另外一個是自定義UI的確認對話方塊。先彈出設定彈窗,點選“儲存設定”是彈出確認對話方塊。聽起來很完美,邏輯上也沒有問題,編碼執行一下呢,應用崩潰了...

這是個悲劇,看下VS的崩潰資訊:

Only a single ContentDialog can be open at any time.

WTF!!! UWP應用同時只支援喚出一個ContentDialog 麼?這也太坑了吧!

不要驚訝,事實上確實如此,關於這點,微軟官方給出的解決方案是這樣的:

Only one ContentDialog can be shown at a time. To chain together more than one ContentDialog, handle the Closing event of the first ContentDialog. In the Closing event handler, call ShowAsync on the second dialog to show it.

也就是說想要同時顯示兩個彈窗是不可能的,只能在第一個彈窗關閉後再來開啟第二個。

那我們怎麼讓第二個彈窗出現時仍能保持第一個彈窗的工作狀態呢?在這種情況下,我能想到兩種解決方法,一是使用MessageDialog代替確認對話方塊(拋棄掉自定義UI),或者ContentDialog 內使用Frame做Page間導航,需要使用者確認時,導航到確認頁面。但是毫無疑問,這兩種方法都極為影響使用者體驗。

MVVM框架下的UI與業務邏輯的分離

上面已經說到了ContentDialog 本身的限制使其很難實現複雜UI需求,而這種困難涉及到MVVM框架時情況會更為複雜一些。

我們知道一個好的基於MVVM框架構建的專案一定是結構清晰,UI互動與後臺業務邏輯分離的完美狀態。ContentDialog本身是一個UI元件,如果只是輕量級的UI需求,比如說只是自定義一個確認對話方塊,在MVVM專案中使用倒還行。但是如果是一個較為複雜的多(層級)彈窗互動需求,或者彈窗內涉及到導航服務,這種情況下,將View層與ViewModel層間的程式碼整理清楚就有些困難了。

在之前的一個專案中,我有遇到這樣的情況,當時的選擇是使用中間人模式,搭建了一箇中介類。這個中介類對ViewModel層提供開啟或跳轉到指定彈窗頁面的介面,對View層則實現排程ContentDialog,控制ContentDialog中Frame的頁導航。

這樣看起來好像也還不錯,功能都實現了。但是缺點是仍舊是無法實現多層彈窗,同時要考慮ViewModel呼叫彈窗的多種情況,實現過程比較複雜,並不能算是一個優雅的解決方式。

需要彈窗關閉時返回使用者操作結果的情況

在很多情況下,我們使用彈窗的互動方式並不僅僅是互動需求,而是業務邏輯上的需要,我們想要使用者做出互動,並且返回互動結果給後臺程式碼做進一步的處理。

舉個例子說,我們做一個繪畫應用,我們提供給使用者一個調色盤來選取畫筆顏色,但是這個調色盤常駐在畫布有些過於侵佔使用者繪畫空間,我們的理想狀態是把它做成一個顏色選取彈窗。這個彈窗需要在使用者點選更換顏色時彈出來讓使用者選擇顏色,如果使用者取消選取顏色則關閉不做任何操作,如果確定選取某一顏色則關閉並返回選取的顏色。如果用ContentDialog來做會怎麼樣呢?ContentDialog關閉時會返回一個型別為ContentDialogResult的物件來標識使用者操作,其定義如下:

//
// 摘要:
//     指定用於指示 ContentDialog 的返回值的識別符號。
public enum ContentDialogResult
{
    //
    // 摘要:
    //     未點選按鈕。
    None = 0,
    //
    // 摘要:
    //     主按鈕由使用者點選。
    Primary = 1,
    //
    // 摘要:
    //     輔助按鈕由使用者點選。
    Secondary = 2
}

那麼要實現上面的需求我們需要在ContentDialog中先暫存使用者選取的顏色,在拿到返回結果後,如果值為ContentDialogResult.Primary則去取出暫存的顏色,否則不做任何處理。

聽起來這已經是個完美的方案了,但是還是有個大問題:我們選取顏色是在一個顏色盤上點選想要的顏色的位置取色,而ContentDialog的返回結果是依賴於點選預定義的幾個按鈕(PrimaryButton/SecondaryButton/CloseButton),這種情況下,對於UI互動的限制非常大,我們無法實現在顏色盤上取色後立即關閉彈窗,並且返回結果。

結尾

說了這麼多,那麼有沒有一個完美的解決方案呢?你問我有沒有,肯定是有的啊!請看下圖!

ContentDialog

ContentDialog的內部實現其實是依賴Popup,這就讓我有了一個大膽的想法,我們程式設計師最愛乾的事情是什麼?造輪子呀!ContentDialog不好用,造個好用的新輪子呀!

接下來幾篇博文來教大家如何造一個好用的,適用於MVVM框架的彈窗層元件。有興趣的可以先看一下我的開源專案HHChaosToolkit中的Picker部分(GitHub連結點這裡)。

好的,本篇博文到此結束,不知道大家有沒有收穫,謝謝大家!