# UWP 換頁儲存那些小事 ###### tags: `UWP`, `c#` ## 問題描述 當使用者在一個畫面做編輯後,未按下儲存就跳轉別的頁面,此時要跳出==是否儲存變更==的警示彈窗 > 不同的換頁方式有不一樣的應對方式,以下會一一說明 > [name=wendee] [time=Thu, Sep 4, 2019] [color=#f2e948] ## 解法 ### 1. Event Handler #### 使用情境 任何可以互相傳值的頁面轉換皆適用,但層級很深的會寫到崩潰,多層情況建議混用別的解法(ex. UnLoaded...) ex. pivot, tab 之間的==不同FrameName== 切換 (ex. `BasicInformationPivot`, `OutpatientTimePivot`) `主頁.xaml.cs` ```csharp= switch ((SettingPivot)index) { case SettingPivot.BasicInformation: NavigationService.NavigateWithoutAnimation(BasicInformationPivot, typeof(BasicInformationView), null); break; case SettingPivot.OutpatientTime: NavigationService.NavigateWithoutAnimation(OutpatientTimePivot, typeof(OutpatientTimeView), null); break; } ``` #### 作法 1. 在主頁建一個 `EventHandler` class `主頁.xaml.cs` ```csharp= public class UnsavedChangeEventHandler { public void OnExistUnsavedChange(bool changes) { OnUnsavedChange(new UnsavedChangeEventArgs { Changes = changes }); } protected virtual void OnUnsavedChange(UnsavedChangeEventArgs e) { EventHandler handler = UnsavedChange; handler?.Invoke(this, e); } public event EventHandler UnsavedChange; } public class UnsavedChangeEventArgs : EventArgs { public bool Changes { get; set; } } ``` 2. 在主頁建立 `EventHandler` 的property,並註冊事件 `主頁.xaml.cs` ```csharp private readonly UnsavedChangeEventHandler _eventHandler = new UnsavedChangeEventHandler(); _eventHandler.UnsavedChange += EventHandlerUnsavedChange; private void EventHandlerUnsavedChange(object sender, EventArgs e) { if (e is UnsavedChangeEventArgs args) { ViewModel.ExistUnsavedChange = args.Changes; } } ``` 3. 把主頁的 `EventHandler` 當成參數傳進分頁們 `主頁.xaml.cs` ```csharp= switch ((SettingPivot)index) { case SettingPivot.BasicInformation: NavigationService.NavigateWithoutAnimation(BasicInformationPivot, typeof(BasicInformationView), new ClinicSettingParam { EventHandler = _eventHandler }); break; case SettingPivot.OutpatientTime: NavigationService.NavigateWithoutAnimation(OutpatientTimePivot, typeof(OutpatientTimeView), new ClinicSettingParam { EventHandler = _eventHandler }); } ``` 4. 分頁接收param,並在有改動時trigger剛剛註冊的事件 `分頁.xaml.cs` ```csharp= protected override void OnNavigatedTo(NavigationEventArgs e) { if (e.Parameter is ClinicSettingParam param) { ViewModel.EventHandler = param.EventHandler; } } ``` 5. 在分頁有改動時trigger剛剛註冊的事件 `分頁ViewModel.cs` ```csharp= public UnsavedChangeEventHandler EventHandler; // 確定 isLoaded = true 之後 EventHandler?.OnExistUnsavedChange(isChanged); ``` :::warning 這樣把就可以把彈窗放在 `主頁.xaml`,並從 `ViewModel.ExistUnsavedChange` 判斷分頁是否有改動來決定要不要跳彈窗~ * 彈窗只有一個 ::: ### 2. OnNavigatedFrom / OnNavigatingFrom #### 使用情境 pivot, tab 之間的==相同FrameName== 切換 (ex.`SettingNavFrame`) ```csharp= switch((Setting)index) case (Setting.CLINIC): NavigationService.NavigateWithoutAnimation(SettingNavFrame, typeof(ClinicSettingPage)); break; case (Setting.OWNEXPENSE): NavigationService.NavigateWithoutAnimation(SettingNavFrame, typeof(OwnExpenseView)); break; ``` #### 作法 1. 在分頁的 `OnNavigatedFrom / OnNavigatingFrom` 裡做判斷 `分頁.xaml.cs` ```csharp= protected override void OnNavigatedFrom(NavigationEventArgs e) { // check if unsaved changes exist } protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) { // check if unsaved changes exist // 這邊的好處是決定不想換頁可以這樣寫 e.Cancel = true; } ``` :::warning 這樣每個分頁都會有各自的提醒彈窗在各自的 `分頁.xaml` 裡,離開時各自在 `OnNavigatedFrom/OnNavigatingFrom` 決定要不要跳彈窗 * 彈窗有多個 ::: ### 3. UnLoaded #### 使用情境 當這一頁會從 `VisualTree` 上消失時 ex1. pivot, tab 之間的 ==相同FrameName== 切換 ex2. 在 ==相同FrameName==內的 pivot, tab 切換至以 ==相同FrameName== 做切換的父級 pivot, tab (像是在診所設定做編輯後,跳至掛號頁面) ![](https://i.imgur.com/mRtpmnf.png) #### 作法 1. 以上面情況為例,我們在分頁的 `UnLoaded` 註冊邏輯判斷 `分頁.xaml.cs` ```csharp= public ClinicSettingPage() { Unloaded += async delegate { await CheckChanges(); }; } private async Task CheckChanges() { // check if unsaved change exists } ``` :::danger **注意** UnLoaded 一定要用 `async/await/Task` 不然跳彈窗會出錯喔! 畢竟 UnLoaded 發生就表示這一頁就快要不見了,我們不用 await 等一下讓彈窗出現,彈窗就會從visualTree上被拔除然後程式就找不到彈窗就Error了 >< ::: :::warning 這樣每個分頁都會有各自的提醒彈窗在各自的 `分頁.xaml` 裡,離開時各自在 `UnLoaded` 決定要不要跳彈窗 * 彈窗有多個 ::: ### Q & A #### Q1. 為什麼 ==不同FrameName== 切換不會觸發 `OnNavigatedFrom`? OnNavigatedFrom 就是要同媽媽換小孩才會被觸發喔! 媽媽不同NavigatedFrom的基礎就不一樣所以不會被trigger (就只是顯示不一樣的媽媽而已,跟pivot的概念一樣) #### Q2. 為什麼 ==不同FrameName== 切換不會觸發 `UnLoaded`? `UnLoaded` 是這一頁會從 `VisualTree` 上消失時被觸發,不同FrameName的切換只是顯示上的不一樣,其實每個Frame都還掛在 `VisualTree` 上喔~ > 不過有個很神祕的現象我還沒弄懂,下面是不同FrameName做切換 > ![](https://i.imgur.com/zC1JuBY.png) > 我從**基本資訊**跳到**費用**不會觸發 `UnLoaded`(正常),可是我從**費用**跳回**基本資訊**它就先叫了**基本資訊**的 `Loaded` 後又叫了**基本資訊**的`UnLoaded`! ...今古奇觀qq > [color=orange] > >我猜 `Loaded` 和 `Unloaded` 是不一樣的 Page(`Unloaded` 舊的基本資訊 & `Loaded` 新的基本資訊頁) [name=sushi] ### 其他失敗的嘗試 #### Windows.Current.VisibilityChanged 這是整個app被最小化和被打開做切換時trigger,和畫面無關