以前の記事でスクロール位置を記憶するための Behavior を作りました
Windows ストアアプリで画面遷移前のスクロール位置を記憶するための Behavior を作る - しっぽを追いかけて
この実装の場合、スクロール位置のオフセットを記憶するのですが、ItemsPanel を WrapGrid や VirtualisingStackPanel などの仮想化描画を行うものを利用した場合はうまくいきません;
仮想化を有効にすると ScrollViewer のオフセットによるスクロールができなくなるからです
この場合、GridView や ListView の ScrollIntoView を利用したアイテム指定のスクロールで位置を復元するしかありません!
というわけで以前作った ScrollViewerBindableBehavior を少し改修・・・
#region OffsetItem 依存関係プロパティ /// <summary> /// スクロール位置アイテム 依存関係プロパティ /// </summary> private static readonly DependencyProperty OffsetItemProperty = DependencyProperty.Register("OffsetItem", typeof(ViewModelBase), typeof(ScrollViewerBindableBehavior), new PropertyMetadata(null)); /// <summary> /// スクロール位置アイテム /// </summary> public ViewModelBase OffsetItem { get { return (ViewModelBase)this.GetValue(OffsetItemProperty); } set { this.SetValue(OffsetItemProperty, value); } } #endregion //OffsetItem 依存関係プロパティ /// <summary> /// アタッチする /// </summary> /// <param name="associatedObject">アタッチ対象オブジェクト</param> public void Attach(DependencyObject associatedObject) { this.AssociatedObject = associatedObject; if (associatedObject is FrameworkElement) { ((FrameworkElement)associatedObject).SizeChanged += this.OnSizeChanged; } } /// <summary> /// 表示完了イベントハンドラ /// </summary> /// <param name="sender">イベント発行者</param> /// <param name="e">イベント引数</param> private void OnSizeChanged(object sender, SizeChangedEventArgs e) { if (this.OffsetItem != null && Window.Current.Content is Frame && ((Frame)Window.Current.Content).Content is Page) { // ScrollIntoView は Page Loaded 後でないと利用できないのでイベントハンドラを設定 ((Page)((Frame)Window.Current.Content).Content).Loaded += this.OnPageLoaded; return; } this.ScrollViewer = FindChild<ScrollViewer>(this.AssociatedObject); if (this.ScrollViewer != null) { this.ScrollViewer.ChangeView(this.HorizontalOffset, this.VerticalOffset, this.ZoomFactor, false); this.ScrollViewer.ViewChanged += this.OnViewChanged; } } /// <summary> /// 画面読み込み完了イベントハンドラ /// </summary> /// <param name="sender">イベント発行者</param> /// <param name="e">イベント引数</param> private void OnPageLoaded(object sender, RoutedEventArgs e) { ((Page)((Frame)Window.Current.Content).Content).Loaded -= this.OnPageLoaded; if (this.OffsetItem != null) { var listViewBase = this.AssociatedObject as ListViewBase; if (listViewBase != null) { listViewBase.ScrollIntoView(this.OffsetItem, ScrollIntoViewAlignment.Leading); } } } /// <summary> /// デタッチする /// </summary> public void Detach() { if (this.AssociatedObject is FrameworkElement) { ((FrameworkElement)this.AssociatedObject).SizeChanged -= this.OnSizeChanged; } if (Window.Current.Content is Frame && ((Frame)Window.Current.Content).Content is Page) { ((Page)((Frame)Window.Current.Content).Content).Loaded -= this.OnPageLoaded; } if (this.ScrollViewer != null) { this.ScrollViewer.ViewChanged -= this.OnViewChanged; } this.AssociatedObject = null; this.ScrollViewer = null; }
XAML の方は次のような感じでスクロール先の ViewModel を OffsetItem プロパティにバインドすれば仮想化してもスクロール位置が戻りました!
<!-- 水平スクロール グリッド --> <GridView x:Name="itemGridView" Margin="20,0,0,0" IsItemClickEnabled="True" IsSwipeEnabled="True" ItemClick="OnItemClick" ItemTemplateSelector="{StaticResource ItemSelector}" ItemsSource="{Binding Items}" Padding="120,175,120,60" SelectedItem="{Binding SelectItems}" SelectionChanged="OnItemSelectionChanged" SelectionMode="Multiple" TabIndex="1"> <i:Interaction.Behaviors> <b:ScrollViewerBindableBehavior OffsetItem="{Binding OffsetItem}" /> </i:Interaction.Behaviors> <GridView.ItemsPanel> <ItemsPanelTemplate> <WrapGrid Margin="0,0,120,0" Orientation="Vertical" /> </ItemsPanelTemplate> </GridView.ItemsPanel> <GridView.ItemContainerStyle> <Style TargetType="GridViewItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> <Setter Property="VerticalContentAlignment" Value="Stretch" /> </Style> </GridView.ItemContainerStyle> </GridView>