しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

続・Windows ストアアプリで画面遷移前のスクロール位置を記憶するための Behavior を作る

以前の記事でスクロール位置を記憶するための Behavior を作りました

Windows ストアアプリで画面遷移前のスクロール位置を記憶するための Behavior を作る - しっぽを追いかけて

f:id:matatabi_ux:20140217075449j:plain

この実装の場合、スクロール位置のオフセットを記憶するのですが、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>