「ストア」アプリなどで使われている Pivot 。
「ストア」アプリなどの挙動をよく見ると、 Pivot の Content の部分だけが遷移しています。
ということで、そこも Prism の INavigationService
で遷移させてみました。
深夜テンションで書いたのでちょっとあれですが、許してください。
まずは、 Pivot に対して Behavior を作ります。
using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; using Microsoft.Xaml.Interactivity; using Pyxis.Attach; namespace Pyxis.Behaviors { internal sealed class AttachNavigationToPivotBehavior : Behavior<Pivot> { public static readonly DependencyProperty RootFrameProperty = DependencyProperty.Register(nameof(RootFrame), typeof(Frame), typeof(AttachNavigationToPivotBehavior), new PropertyMetadata(null)); private readonly Stack<int> _pageStack; private bool _isAttached; private int _oldIndex; // 1つ前の Index public Frame RootFrame { get { return (Frame) GetValue(RootFrameProperty); } set { SetValue(RootFrameProperty, value); } } public AttachNavigationToPivotBehavior() { _pageStack = new Stack<int>(); _isAttached = false; _oldIndex = -1; } // https://github.com/PrismLibrary/Prism/blob/3dded2/Source/Windows10/Prism.Windows/PrismApplication.cs#L148-L171 private Type GetPageType(string pageToken) { var assemblyQualifiedAppType = GetType().AssemblyQualifiedName; var pageNameWithParameter = assemblyQualifiedAppType.Replace(GetType().FullName, typeof(App).Namespace + ".Views.{0}Page"); var viewFullName = string.Format(CultureInfo.InvariantCulture, pageNameWithParameter, pageToken); var viewType = Type.GetType(viewFullName); if (viewType == null) throw new ArgumentException(string.Format("{0}'{1}' is not found.", nameof(pageToken), pageToken)); return viewType; } private void OnSelectionChanged(object sender, SelectionChangedEventArgs args) { if (!_isAttached) { RootFrame.Navigating += RootFrameOnNavigating; _isAttached = true; } ReplaceRootFrame(); } private void ReplaceRootFrame() { var item = AssociatedObject.ItemsPanelRoot?.Children[AssociatedObject.SelectedIndex] as PivotItem; if (item == null) return; foreach (var pivot in AssociatedObject.ItemsPanelRoot?.Children.Select((w, i) => new {Item = w, Index = i})) { if (pivot.Index == AssociatedObject.SelectedIndex) continue; ((PivotItem) pivot.Item).Content = new Frame(); } var pageToken = NavigateTo.GetPageToken(item); if (!string.IsNullOrWhiteSpace(pageToken)) RootFrame.Navigate(GetPageType(pageToken)); item.Content = RootFrame; } private void RootFrameOnNavigating(object sender, NavigatingCancelEventArgs args) { if (args.NavigationMode == NavigationMode.Back) AssociatedObject.SelectedIndex = _pageStack.Pop(); else if (args.NavigationMode == NavigationMode.New) { if (_oldIndex >= 0) _pageStack.Push(_oldIndex); } _oldIndex = AssociatedObject.SelectedIndex; ReplaceRootFrame(); } #region Overrides of Behavior protected override void OnAttached() { base.OnAttached(); AssociatedObject.SelectionChanged += OnSelectionChanged; } protected override void OnDetaching() { RootFrame.Navigating -= RootFrameOnNavigating; AssociatedObject.SelectionChanged -= OnSelectionChanged; base.OnDetaching(); } #endregion } }
Prism の INavigationService
は、 PrismApplication
で作成された Frame
に対して
操作を行っているため、現在表示されている PivotItem
の Content
部分を、
PrismApplication
で作成された Frame
へと置き換えます。
また、「ストア」アプリでは、「戻る」ボタンを押すことで、 Pivot の選択項目も変わるため、
遷移が行われるたびに、その時選択状態にあった Pivot の Index
を Stack
へ入れています。
次に、添付プロパティ。
using Windows.UI.Xaml; namespace Pyxis.Attach { public static class NavigateTo { public static readonly DependencyProperty PageTokenProperty = DependencyProperty.RegisterAttached("PageToken", typeof(string), typeof(NavigateTo), new PropertyMetadata(string.Empty)); public static string GetPageToken(DependencyObject obj) => (string) obj.GetValue(PageTokenProperty); public static void SetPageToken(DependencyObject obj, string value) => obj.SetValue(PageTokenProperty, value); } }
Prism の INavigationService
で遷移する際、 ns.Navigate("Secondary", null)
という風に、
遷移先のページを指定する必要があるので、それを XAML から行うためのものです。
Behavior の ReplaceRootFrame
の最後でトークンを取得して、遷移を行っています。
最後に XAML とそのコードビハインド。
<Page x:Class="Pyxis.AppShell" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:attach="using:Pyxis.Attach" xmlns:behaviors="using:Pyxis.Behaviors" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:i="using:Microsoft.Xaml.Interactivity" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Pivot x:Name="Pivot"> <i:Interaction.Behaviors> <behaviors:AttachNavigationToPivotBehavior RootFrame="{x:Bind AppRootFrame}" /> </i:Interaction.Behaviors> <Pivot.Resources> <Style TargetType="TextBlock"> <Setter Property="FontSize" Value="18" /> </Style> </Pivot.Resources> <PivotItem attach:NavigateTo.PageToken="Page1"> <PivotItem.Header> <TextBlock Text="Page 1" /> </PivotItem.Header> </PivotItem> <PivotItem attach:NavigateTo.PageToken="Page2"> <PivotItem.Header> <TextBlock Text="Page 2" /> </PivotItem.Header> </PivotItem> <PivotItem attach:NavigateTo.PageToken="Page3"> <PivotItem.Header> <TextBlock Text="Page 3" /> </PivotItem.Header> </PivotItem> </Pivot> </Grid> </Page>
using System.ComponentModel; using System.Runtime.CompilerServices; using Windows.UI.Xaml.Controls; namespace Pyxis { /// <summary> /// それ自体で使用できる空白ページまたはフレーム内に移動できる空白ページ。 /// </summary> public sealed partial class AppShell : Page, INotifyPropertyChanged { public AppShell() { InitializeComponent(); } public event PropertyChangedEventHandler PropertyChanged; public void StoreContentFrame(Frame frame) => AppRootFrame = frame; private void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } #region AppRootFrame private Frame _appRootFrame; public Frame AppRootFrame { get { return _appRootFrame; } set { if (_appRootFrame == value) return; _appRootFrame = value; OnPropertyChanged(); } } #endregion } }
App.xaml.cs
の CreateShell
で、 StoreContentFrame
を呼び出せば OK。
こんな感じで、ちゃんと動きます。