主にプログラミング関連のメモ帳 ♪(✿╹ヮ╹)ノ
書いてあるコードは自己責任でご自由にどうぞ。記事本文の無断転載は禁止です。
2016/06/16
「ストア」アプリなどで使われている 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。
こんな感じで、ちゃんと動きます。