かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Windows 8.1のストアアプリでマルチビューをサポートする方法のメモ

Windows 8.1 RTM + Visual Studio 2013 RC時点の情報です。

また、試し始めたばっかりで試行錯誤中のメモです。

新しいViewの作成

CoreApplicationのCreateNewViewでいけるっぽい。

var v = CoreApplication.CreateNewView();

これで、CoreApplicationViewというクラスのインスタンスがとれるので、いろいろできる。この段階でApplicationクラスのWindowCreatedイベントが発生しているので、新しいWindowが作られてるっぽい。

新しいViewに画面を表示する

CoreApplicationViewからDispatcherがとれるのでRunAsyncでフレーム作って画面遷移させればいい。

var v = CoreApplication.CreateNewView();
var viewId = default(int);
await v.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => 
{
    var f = new Frame();
    f.Navigate(typeof(BlankPage1));

    Window.Current.Content = f;
    ApplicationView.GetForCurrentView().Title = "Windowのタイトル";
});

新しいViewを画面に表示する

1つ前の見出しと紛らわしいですが、上のコードでは、まだ画面には出てこない状態です。画面に表示させるには、ApplicationViewSwitcher.TryShowAsStandaloneAsync(int viewId)を呼び出す必要があります。viewIdは、CoreApplicationViewのDispatcherの処理の中でApplicationView.GetForCurrentView()で取得できるApplicationViewのIdプロパティで取得できるものです。なので、こんな感じのコードに…

var v = CoreApplication.CreateNewView();
var viewId = default(int);
await v.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => 
{
    var f = new Frame();
    f.Navigate(typeof(BlankPage1));

    Window.Current.Content = f;
    var applicationView = ApplicationView.GetForCurrentView();
    applicationView.Title = "Windowのタイトル";
    viewId = applicationView.Id;
});
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(viewId);

このコードをボタンクリックのイベントとかに書くと、BlankPage1が表示されたウィンドウが画面に表示されます。

複数ウィンドウに一気に処理をしたりする

マルチビューの環境で、他のWindowに対して処理をするには、CoreApplicationViewをとっておけばできそうです。なので、以下のようなフィールドをどこかに定義してCoreApplicationViewをとっておくようにします。

List<CoreApplicationView> views = new List<CoreApplicationView>();

そして、新しいViewを作成するコードの中でリストに保持するようにします。

var v = CoreApplication.CreateNewView();
var viewId = default(int);
await v.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
    var f = new Frame();
    f.Navigate(typeof(BlankPage1));

    Window.Current.Content = f;
    var applicationView = ApplicationView.GetForCurrentView();
    applicationView.Title = "AppView: " + views.Count;
    viewId = applicationView.Id;
});
// とっとく
views.Add(v);
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(viewId);

あとは、CoreApplicationViewのDispatcherからRunAsyncすると、その中でWindow.Currentプロパティを使って、そのビューのWindowを取得できます。Windowが取得できたら後はお好きなように。

例えば、全てのビューのBlankPage1に対してSetというメソッドを呼ぶ場合は以下のようになります。

foreach (var v in this.views)
{
    var data = DateTime.Now.ToString();
    await v.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => 
    {
        var page = ((Windows.UI.Xaml.Controls.Frame)Window.Current.Content).Content as BlankPage1;
        page.Set(data);
    });
}

感想

ちゃんと作らないと、サスペンドからの復帰とか色々考えると頭が痛くなりそう。こんな風にCoreApplicationViewをリストで保持して、それを依り代にして操作をするのではなくて、どんなデータを元にしたビューを表示中なのかということをきちんとアプリケーションのロジック内で管理して、それを永続化しておく。そして、サスペンドからの復帰時には必要に応じて新しいビューを生成するといった形にしないと辛そう。

これは適当に作ると詰む系だと思いました。

あと、MSのサンプルだとCreateNewViewの後にWindowCreatedの完了を待って、WindowCreatedイベントハンドラの中でCoreDispatcerを取得するとかいう複雑怪奇なサンプルになってるんだけど、これは必須なのだろうか… Multiple Views Sample