ストアアプリのライフサイクルイベントの扱いは難しい。本稿ではページ遷移の際にイベントハンドラーを外す適切なタイミングについて解説する。
powered by Insider.NET
イベントハンドラーは不要になったら外せとは、よくいわれることだ。そうしないと実際に不具合が出ることもある。ところが、イベントハンドラーを外すようにすると、今度は予想外のタイミングで外れてしまうことがあるのだ。どのようにして外すとよいのだろうか? 本稿では、イベントハンドラーを外す際の問題点と、その解決法を説明する。なお、本稿のサンプルは「Windows Store app samples:MetroTips #95」からダウンロードできる。
ユニバーサルプロジェクトを使ってユニバーサルWindowsアプリを開発するには、以下の開発環境が必要である。本稿では、無償のVisual Studio Express 2013 for Windowsを使っている。
*1 SLAT対応ハードウェアは、Windows Phone 8.1エミュレーターの実行に必要だ。ただし未対応でも、ソースコードのビルドと実機でのデバッグは可能だ。SLAT対応のチェック方法はMSDNブログの「Windows Phone SDK 8.0 ダウンロードポイント と Second Level Address Translation (SLAT) 対応PCかどうかを判定する方法」を参照。なお、SLAT対応ハードウェアであっても、VM上ではエミュレーターが動作しないことがあるのでご注意願いたい。
*2 事前には「Windows 8.1 Update 1」と呼ばれていたアップデート。スタート画面の右上に検索ボタンが(環境によっては電源ボタンも)表示されるようになるので、適用済みかどうかは簡単に見分けられる。ちなみに公式呼称は「the Windows RT 8.1, Windows 8.1, and Windows Server 2012 R2 update that is dated April, 2014」というようである。
*3 Windows Phone 8.1エミュレーターを使用しないのであれば、32bit版のWindows 8.1でもよい。
*4 マイクロソフトのダウンロードページから誰でも入手できる(このURLはUpdate 4のもの)。
*5 本稿に掲載したコードを試すだけなら、無償のExpressエディションやCommunityエディションで構わない。Visual Studio Express 2013 with Update 4 for Windows(製品版)はマイクロソフトのページから無償で入手できる。Expressエディションはターゲットプラットフォームごとに製品が分かれていて紛らわしいが、Windowsストアアプリの開発には「for Windows」を使う(「for Windows Desktop」はデスクトップで動作するアプリ用)。また、この11月12日(米国時間)に新しくリリースされたVisual Studio Community 2013 with Update 4(製品版)もマイクロソフトのページから無償で入手できる。Communityエディションは本稿執筆時点では英語版だけなので、同じ場所にあるVisual Studio 2013 Language Packの日本語版を追加インストールする必要がある。
本稿では、紛らわしくない限り次の略称を用いる。
Visual Studio 2013 Update 2(Update 3/4も)では、残念なことにVB用のユニバーサルプロジェクトのテンプレートは含まれていない*6。そのため、本稿で紹介するVBのコードはユニバーサルプロジェクトではなく、PCL(ポータブルクラスライブラリ)を使ったプロジェクトのものである*7。
*6 VB用のユニバーサルプロジェクトは、来年にリリースされるといわれているVisual Studio 2015(開発コード「Visual Studio 14」)からの提供となるようだ。「Visual Studio UserVoice」(英語)のリクエストに対する、6月18日付けの「Visual Studio team (Product Team, Microsoft)」からの回答による。
*7 Visual Studio 2013 Update 2以降のVBでユニバーサルWindowsアプリを作る場合のお勧めは、「The Visual Basic Team」のブログ記事(英語)によれば、PCLを使う方法のようである。PCLに置いたものは、コードだけでなくXAML(画面)やリソースディクショナリなども共通に利用できる。そこで別途公開のサンプルコードでも、VBはWindows用/Phone用/共通コード(PCL)の3プロジェクト構成とした。ユニバーサルプロジェクトで作らなくてもユニバーサルWindowsアプリはリリースできるのである(「WinRT/Metro TIPS:ユニバーサルプロジェクトで開発するには?」参照)。
次のようなアプリを考えてみよう。起動時に表示されるメイン画面と、メイン画面から遷移する[Page-A」画面という、2つの画面を持っているアプリ。そして、[Page-A]画面では、エンドユーザーが選択した画像データを共有に送れるようにしたい(=[Page-A]画面に共有ソースのイベントハンドラーを実装する)。
そのような場合には、[Page-A」画面が表示されるときにイベントハンドラーをセットするだろう(次のコード)。
// 共有にデータを渡すためのイベントハンドラー
// 冒頭に「using Windows.ApplicationModel.DataTransfer;」が必要
private TypedEventHandler<DataTransferManager, DataRequestedEventArgs> _shareToEventHandler;
public PageA()
{
……省略……
// 共有にデータを渡すためのイベントハンドラーをメンバー変数に保持しておく
_shareToEventHandler
= new TypedEventHandler<DataTransferManager,
DataRequestedEventArgs>(this.MyUserControl1.ShareToHandler);
}
private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
// このページが最初に要求されたときに、イベントハンドラーを結び付ける
DataTransferManager.GetForCurrentView().DataRequested += _shareToEventHandler;
}
' 共有にデータを渡すためのイベントハンドラー
' 冒頭に「Imports Windows.ApplicationModel.DataTransfer」が必要
Private _shareToEventHandler As TypedEventHandler(Of DataTransferManager, DataRequestedEventArgs)
Public Sub New()
……省略……
' 共有にデータを渡すためのイベントハンドラーをメンバー変数に保持しておく
_shareToEventHandler _
= New TypedEventHandler(Of DataTransferManager, DataRequestedEventArgs)(
AddressOf Me.MyUserControl1.ShareToHandler)
End Sub
Private Sub NavigationHelper_LoadState(sender As Object, e As Common.LoadStateEventArgs)
' このページが最初に要求されたときに、イベントハンドラーを結び付ける
AddHandler DataTransferManager.GetForCurrentView().DataRequested, _shareToEventHandler
End Sub
これで実行してみると、[Page-A]画面で共有ソースのイベントハンドラーが動作するのはよいのだが、その後でメイン画面に戻っても共有ソースが機能してしまう(次の画像)。
これは一般的には望ましくない挙動であろう。画面表示時に結び付けたイベントハンドラーは、他の画面に遷移するときに外さねばならない。
他の画面へ遷移するときに行いたい処理は、通常はOnNavigatedFromメソッド(画面作成時に[基本ページ]テンプレートなどを使った場合はnavigationHelper_SaveStateメソッド)に記述するだろう。イベントハンドラーもここで外してみよう(次のコード)。
private void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
{
// 他の画面へ遷移するときに、イベントハンドラーを外すようにしてみる(よくない例)
DataTransferManager.GetForCurrentView().DataRequested -= _shareToEventHandler;
}
Private Sub NavigationHelper_SaveState(sender As Object, e As Common.SaveStateEventArgs)
' 他の画面へ遷移するときに、イベントハンドラーを外すようにしてみる(よくない例)
RemoveHandler DataTransferManager.GetForCurrentView().DataRequested, _shareToEventHandler
End Sub
これで実行してみると、[Page-A]画面からメイン画面に戻ったときには、共有の機能がきちんと停止する。
ところが、中断時に画面遷移履歴などのデータを保存するようにしていると、問題が発生するのだ。[ハブ アプリケーション]テンプレートなどを使ってプロジェクトを作成すると、自動的にそのような実装がなされている*8。
*8 ご興味のある方は、[ハブ アプリケーション]テンプレートを使ってプロジェクトを作成し、確認してみてほしい。Appクラスの中でSuspensionManagerクラスを使用している箇所がそれである。AppクラスのOnLaunchedメソッドの中に「SuspensionManager.RegisterFrame」(作成したフレームをSuspensionManagerクラスに登録)/「SuspensionManager.RestoreAsync」(画面遷移履歴を復元)の2箇所、OnSuspendingメソッドの中に「SuspensionManager.SaveAsync」(画面遷移履歴や画面ごとの中断時のデータを保存)の1カ所である。別途公開のサンプルコードにも実装してある。
そのような中断時のデータ保存機能が実装されている場合は、[Page-A]画面を表示しているときに中断してすぐに再開するとイベントハンドラーが外れてしまい、共有機能が働かなくなってしまうのだ(次の画像)。
これはよろしくない。他のアプリに切り替えてまた戻ってきたときに機能が効かなくなってしまっては、まずいだろう。
中断からの再開時には、イベントハンドラーが外れてほしくない。それには、OnNavigatingFromメソッドで外せばよい(次のコード)。
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
// ここで外すようにすれば、中断時には外れない!
DataTransferManager.GetForCurrentView().DataRequested -= _shareToEventHandler;
}
Protected Overrides Sub OnNavigatingFrom(e As NavigatingCancelEventArgs)
MyBase.OnNavigatingFrom(e)
' ここで外すようにすれば、中断時には外れない!
RemoveHandler DataTransferManager.GetForCurrentView().DataRequested, _shareToEventHandler
End Sub
OnNavigatingFromメソッドは、「SuspensionManager」クラスからは呼び出されない。実際に画面遷移が起きる直前だけに呼び出されるのだ(「SuspensionManager」クラス以外のフレームワークを利用している場合は違うかもしれない)。
これで、他の画面に遷移したときにはイベントハンドラーが外れるとともに、この画面で中断から再開したときにはイベントハンドラーは外れなくなる。
画面を表示するときにセットしたイベントハンドラーは、その画面のOnNavigatingFromメソッドで外す(OnNavigatedFromメソッドではない)。画面遷移や中断時の処理によってはそれ以外の場所で外しても構わない場合もあるのだが、「イベントハンドラーはOnNavigatingFromで外す」と覚えておけば問題ないだろう。
Copyright© Digital Advantage Corp. All Rights Reserved.