Prism使ってやってみましょう。UWPアプリを作ってPrism.Unityを追加してAppクラスを書き換えます。
XAML側
<Prism:PrismUnityApplication x:Class="App14.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App14" xmlns:Prism="using:Prism.Unity.Windows" RequestedTheme="Light"> </Prism:PrismUnityApplication>
C#側
using App14.ViewModels; using App14.Views; using Microsoft.Practices.Unity; using Prism.Unity.Windows; using System.Diagnostics; using System.Threading.Tasks; using Windows.ApplicationModel.Activation; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace App14 { sealed partial class App : PrismUnityApplication { public App() { Microsoft.ApplicationInsights.WindowsAppInitializer.InitializeAsync( Microsoft.ApplicationInsights.WindowsCollectors.Metadata | Microsoft.ApplicationInsights.WindowsCollectors.Session); this.InitializeComponent(); } protected override Task OnLaunchApplicationAsync(LaunchActivatedEventArgs args) { this.NavigationService.Navigate("Main", null); return Task.CompletedTask; } } }
画面遷移テスト用に3つくらい画面を作ります。ViewsフォルダにMainPage.xaml、NextPage.xaml、AboutPage.xamlくらい作ります。
Shellっていう名前でSplitViewを持ったページを作ります。こいつは、SplitViewを持ったページです。
<Page x:Class="App14.Views.Shell" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App14.Views" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Mvvm="using:Prism.Windows.Mvvm" Mvvm:ViewModelLocator.AutoWireViewModel="True" mc:Ignorable="d"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <SplitView x:Name="FrameHost" x:FieldModifier="public" IsPaneOpen="True" DisplayMode="CompactInline"> <SplitView.Pane> <ListView > </ListView> </SplitView.Pane> </SplitView> </Grid> </Page>
ListViewにページ名を出して、右側にページを表示するようにします。PrismでSplitViewとかを持ったクラスを作るには、AppクラスのCreateShellメソッドをオーバーライドして渡されたFrameを使ってページを構築します。今回の場合SplitViewにFrameHostという名前を付けてるので、こいつのContentにFrameを突っ込みます。
protected override UIElement CreateShell(Frame rootFrame) { var shell = this.Container.Resolve<Shell>(); shell.FrameHost.Content = rootFrame; return shell; }
なんとなく下地ができたので、ページ遷移を管理するクラスを作ります。Prismで画面遷移するためのページ名を管理するクラスです。ページクラスからページ名への変換ロジックも持たせてあります。後で使います。
using Prism.Mvvm; using System; using System.Collections.ObjectModel; namespace App14.ViewModels { public class NavigationStateManager : BindableBase { public ObservableCollection<string> PageTokens { get; } = new ObservableCollection<string>(); private string currentPageToken; public string CurrentPageToken { get { return this.currentPageToken; } set { this.SetProperty(ref this.currentPageToken, value); } } public void SetCurrentPageTokenFromPageType(Type pageType) { var typeName = pageType.Name; this.CurrentPageToken = typeName.Substring(0, typeName.Length - 4); } } }
こいつをUnityのコンテナにシングルトンで登録します。そして、FrameのNavigatedイベントで現在のページをセットするようにします。CurrentPageTokenが変わったら、そのページに画面遷移もするようにしておきましょう。ということでAppクラスが以下のように化けます。
using App14.ViewModels; using App14.Views; using Microsoft.Practices.Unity; using Prism.Unity.Windows; using System.Diagnostics; using System.Threading.Tasks; using Windows.ApplicationModel.Activation; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace App14 { sealed partial class App : PrismUnityApplication { public App() { Microsoft.ApplicationInsights.WindowsAppInitializer.InitializeAsync( Microsoft.ApplicationInsights.WindowsCollectors.Metadata | Microsoft.ApplicationInsights.WindowsCollectors.Session); this.InitializeComponent(); } protected override void ConfigureContainer() { base.ConfigureContainer(); // NavigationStateManagerをシングルトンで管理してもらう this.Container.RegisterType<NavigationStateManager>(new ContainerControlledLifetimeManager()); } protected override Frame OnCreateRootFrame() { // Frameをカスタマイズ var frame = new Frame(); frame.Navigated += (_, e) => { this.Container.Resolve<NavigationStateManager>().SetCurrentPageTokenFromPageType(e.SourcePageType); }; return frame; } protected override Task OnInitializeAsync(IActivatedEventArgs args) { // 初期化 var navigationStateManager = this.Container.Resolve<NavigationStateManager>(); navigationStateManager.PageTokens.Add("Main"); navigationStateManager.PageTokens.Add("Next"); navigationStateManager.PageTokens.Add("About"); navigationStateManager.CurrentPageToken = "Main"; // CurrentPageTokenが変わったら画面遷移する this.Container.Resolve<NavigationStateManager>() .PropertyChanged += (sender, e) => { if (e.PropertyName == nameof(NavigationStateManager.CurrentPageToken)) { this.NavigationService.Navigate(navigationStateManager.CurrentPageToken, null); } }; return Task.CompletedTask; } protected override UIElement CreateShell(Frame rootFrame) { var shell = this.Container.Resolve<Shell>(); shell.FrameHost.Content = rootFrame; return shell; } protected override Task OnLaunchApplicationAsync(LaunchActivatedEventArgs args) { this.NavigationService.Navigate("Main", null); return Task.CompletedTask; } } }
これで、NavigationStateManagerの状態に同期して画面遷移するようになりました。あとは、NavigationStateManagerをSplitViewの左側のListViewにバインドするだけです。ShellViewModelクラスを作って、以下のようにNavigationStateManagerを受け取って外部に公開しましょう。
using Prism.Windows.Mvvm; namespace App14.ViewModels { public class ShellViewModel : ViewModelBase { public NavigationStateManager NavigationStateManager { get; } public ShellViewModel(NavigationStateManager navigationStateManager) { this.NavigationStateManager = navigationStateManager; } } }
そして、Shell.xamlでListViewにバインドします。バインドするのは、PageTokensプロパティとCurrentPageTokenプロパティになります。
<Page x:Class="App14.Views.Shell" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App14.Views" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Mvvm="using:Prism.Windows.Mvvm" Mvvm:ViewModelLocator.AutoWireViewModel="True" mc:Ignorable="d"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <SplitView x:Name="FrameHost" x:FieldModifier="public" IsPaneOpen="True" DisplayMode="CompactInline"> <SplitView.Pane> <ListView ItemsSource="{x:Bind ViewModel.NavigationStateManager.PageTokens}" SelectedItem="{x:Bind ViewModel.NavigationStateManager.CurrentPageToken, Mode=TwoWay}"> </ListView> </SplitView.Pane> </SplitView> </Grid> </Page>
実行すると以下のようになります。初期状態ではMainPageが表示されてます。左側のListViewの選択もMainになってるのが確認できます。
ListViewを操作すると、画面遷移していくことが確認できます。下図は、Aboutをクリックしたときの様子です。
戻るボタンを押すときちんとページが戻ってListViewの選択も同期してることが確認できます。
まとめ
ということで、SplitViewの左側のページのリストと実際に右側に表示されてるページの同期をとってみました。管理クラスを作って、ページの状態をそれと同期させるというアプローチです。もうちょっと複雑な要件になってくると、もうちょっと賢く作らないといけないかもしれないですね。(ページのパラメータ渡すとかetc...)