読者です 読者をやめる 読者になる 読者になる

みかづきメモ

学習したことのメモとか、日記とか、備忘録。

Prism で NavigationService + SplitView

C# UWP

Prism を使っていて、画面遷移をする際に、ページ全体を遷移するんじゃなく、
SplitView の Content の部分だけ画面遷移させるにはどうするんだろう?ということで。

ちなみに、 Prism を使わず、コードビハインドに直接書いての方法は、サンプル集にあります。

github.com

毎度のごとく、もしかしたら間違ってる/もっといい方法があるかもしれないので、
何かあったら教えてください。



追記開始

github.com
普通にサンプルありました。
後日気がついたのですが、間違ってました。

SplitView.Content = rootFrame; // rootFrame は App.xaml.cs で生成したやつ。

をしておく必要があります。


追記終了



基本的には、 MainPage 及び MainPageViewModel でしか使わないだろうということで、
そういう感じに実装していきます。~~

まずはベースとなる SplitView を配置した MainPage.xaml
SplitView を使うのに、最低限と思われるものだけ配置しています。

<Page x:Class="PrismNavigation.Views.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:local="using:PrismNavigation.Views"
      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}">
        <Grid.RowDefinitions>
            <RowDefinition Height="48" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0"
                    Background="{StaticResource SystemControlBackgroundChromeMediumBrush}"
                    Orientation="Horizontal">
            <ToggleButton x:Name="HamburgerButton"
                          Width="48"
                          Height="48"
                          HorizontalAlignment="Center"
                          VerticalAlignment="Center"
                          Foreground="{StaticResource ApplicationForegroundThemeBrush}">
                <FontIcon Glyph="&#xE700;" />
            </ToggleButton>
            <TextBlock VerticalAlignment="Center"
                       Style="{StaticResource TitleTextBlockStyle}"
                       Text="PrismNavigation" />
        </StackPanel>
        <SplitView Grid.Row="1"
                   IsPaneOpen="{Binding ElementName=HamburgerButton,
                                        Path=IsChecked}">
            <SplitView.Pane>
                <RelativePanel>
                    <ListBox>
                        <ListBoxItem Tapped="{x:Bind ViewModel.NavigateToFirstPage}">
                            <TextBlock>First Page</TextBlock>
                        </ListBoxItem>
                        <ListBoxItem Tapped="{x:Bind ViewModel.NavigateToSecondPage}">
                            <TextBlock>Second Page</TextBlock>
                        </ListBoxItem>
                    </ListBox>
                </RelativePanel>
            </SplitView.Pane>
            <SplitView.Content>
                <Frame x:Name="Frame" />
            </SplitView.Content>
        </SplitView>
    </Grid>
</Page>

ポイントとしては、 SplitView.Content の子として、 Frame を追加しておくことです。
サンプルにもありますが、 Navigation で遷移させる場合には、 Frame が必要です。

そして、 ViewModel

namespace PrismNavigation.ViewModels
{
    public class MainPageViewModel : ViewModelBase
    {
        public INavigationService NavigationService { get; set; }

        public void NavigateToFirstPage()
        {
            this.NavigationService.Navigate("First", null);
        }

        public void NavigateToSecondPage()
        {
            this.NavigationService.Navigate("Second", null);
        }
    }
}

普通に、 Prism で INavigationService を使ったアプリと同じ感じです。
NavigationService プロパティは、 public かつ readonly では無いものにしておきます。

次に、 MainPage.xaml のコードビハインドをいじります。
ここで、 ViewModel の INavigationService に対して設定を行います。

namespace PrismNavigation.Views
{
    /// <summary>
    /// それ自体で使用できる空白ページまたはフレーム内に移動できる空白ページ。
    /// </summary>
    public sealed partial class MainPage : Page
    {
        // x:Bind 用
        public MainPageViewModel ViewModel => this.DataContext as MainPageViewModel;

        public MainPage()
        {
            this.InitializeComponent();
            this.DataContextChanged += (sender, args) =>
                this.ViewModel.NavigationService = new FrameNavigationService(new FrameFacadeAdapter(this.Frame), this.GetPageType, new SessionStateService());
        }

        private Type GetPageType(string pageToken)
        {
            var pageName = this.GetType().Namespace + $".{pageToken}Page";
            var view = Type.GetType(pageName);
            if (view == null)
                throw new ArgumentException($"Page {pageToken} is not found on {pageName}");
            return view;
        }
    }
}

Prism の AutoWire 機能で、 ViewModel が自動的に紐付けられるため、 DataContextChanged でそれを検知し、
ViewModel の NavigationService プロパティに、新しく作った Service を設定します。

ルートフレームを、 MainPage.xaml で設定している Frame にすることで、 SplitView.Content の内部だけ遷移することが可能となります。

実際のプロジェクトはこちら。

Various/PrismNavigation at master · fuyuno/Various · GitHub