---
# System prepended metadata

title: UWP 換頁儲存那些小事
tags: [c#, UWP]

---

# 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，和畫面無關