ほかの画面から元の画面に戻ってきたときに、GridViewコントロールの以前のスクロール位置を復元する方法として、ScrollViewerコントロールを使う方法を説明する。
powered by Insider.NET
GridViewコントロールを使っているアプリの中には、ある画面からほかの画面に遷移してから元の画面に戻ってきたときに、以前のスクロール位置を復元するものがある。これはどのようにしたら実現できるのだろうか? その方法を2回に分けて解説する。
本稿では、まず簡単な手法としてScrollViewerコントロールを使う方法を説明する。本稿のサンプルは「Windows Store app samples:MetroTips #29」からダウンロードできる。
●事前準備
Windows 8(以降、Win 8)向けのWindowsストア・アプリを開発するには、Win 8とVisual Studio 2012(以降、VS 2012)が必要である。これらを準備するには、第1回のTIPSを参考にしてほしい。本稿では64bit版Win 8 ProとVS 2012 Express for Windows 8を使用している。
●スクロール位置を復元するアプリの例
例えばWin 8に標準で付属する「ビデオ」アプリを見てみよう。アプリを起動した直後の画面(=トップ画面)は、グループ化したGridViewコントロール(Windows.UI.Xaml.Controls名前空間)を使っているようだ。そこで、トップ画面で適当な位置までスクロールしておいてから、[映画ストア]をクリックするなどしてほかの画面に移り、その後で[戻る]ボタンをクリックしてトップ画面に戻ると、以前と同じ位置にスクロールされている(次の図)。
さらに、アプリの中には中断→終了*1した後に再び起動しても、スクロール位置が復元されるものがある。画面遷移に伴うスクロール位置の保存と復元だけならページのNavigationCacheModeプロパティを設定すれば対応できる*2が、アプリのライフ・タイムを越えてのスクロール位置の保存と復元はどのようにしたら実現できるのだろうか? いろいろな方法が考えられるが、本稿ではScrollViewerコントロール(Windows.UI.Xaml.Controls名前空間)を使った簡易な方法を説明する。
*1 アプリのライフサイクルについては、「Metroスタイル・アプリの開発者が知るべき3つのこと 〜 その3: [設計の原則] 万全のリジューム機能を装備すべし!」を参照してほしい。
*2 ページのNavigationCacheModeプロパティを設定する方法は、「画面遷移する前の状態を保持するには?[Win 8]」を参照してほしい。
●サンプル・アプリを作る
今回は、VS 2012の「分割アプリケーション (XAML)」テンプレートから始めよう。プロジェクトを作成したら、動作確認時にトップ画面でスクロールを行えるようにサンプル・データの数を増やしておく。そのためには、「SampleDataSource.cs/.vb」ファイル内のSampleDataSourceクラスのコンストラクタの末尾に、次のようにしてダミー・データを追加するコードを記述する。
public SampleDataSource()
{
……
// ダミー・データを追加するコード
for (int i = 7; i <= 20; i++)
{
string n = i.ToString();
var g = new SampleDataGroup("Group-" + n,
"Group Title: " + n,
"Group Subtitle: " + n,
"Assets/DarkGray.png",
"Group Description: ");
this.AllGroups.Add(g);
}
}
Public NotInheritable Class SampleDataSource
……
Public Sub New()
……
' ダミー・データを追加するコード
For i As Integer = 7 To 20
Dim n As String = i.ToString()
Dim g = New SampleDataGroup("Group-" + n,
"Group Title: " + n,
"Group Subtitle: " + n,
"Assets/DarkGray.png",
"Group Description: ")
Me.AllGroups.Add(g)
Next
End Sub
End Class
これでデータのグループが20個となる。サンプル・アプリをビルドして実行してみると次の画像のように横スクロールが可能になっているはずだ。なお、後で比較するためにGridViewコントロールの周囲に赤色で枠線を付けた。
この状態でトップ画面(ItemsPage.xamlファイル)をスクロールした後で分割ページ(SplitPage.xamlファイル)に移動し、再度トップページに戻るなどして、スクロール位置が復元されないことを確かめておいてほしい。
●ScrollViewerコントロールを使ってスクロール位置を復元するには?
GridViewコントロールをScrollViewerコントロールの中に入れ、ScrollViewerコントロールのHorizontalOffsetプロパティとScrollToHorizontalOffsetメソッドを使って、スクロール位置の保存と復元を行う。
まず、ItemsPage.xamlファイル(=起動時に表示される画面)を開き、次のようにScrollViewerコントロールを追加するとともに、GridViewコントロールのプロパティも変更する(太字の部分)。
<ScrollViewer x:Name="scrollViewer1" Grid.RowSpan="2"
BorderBrush="Yellow" BorderThickness="3"
HorizontalScrollMode="Auto" HorizontalScrollBarVisibility="Auto"
VerticalScrollMode="Disabled" VerticalScrollBarVisibility="Disabled">
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemsGridView"
AutomationProperties.Name="Items"
TabIndex="1"
Grid.RowSpan="2"
Margin="116,136,116,46"
ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
ItemTemplate="{StaticResource Standard250x250ItemTemplate}"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick"
BorderBrush="Red" BorderThickness="3"
SizeChanged="itemGridView_SizeChanged"
/>
</ScrollViewer>
PaddingプロパティをMarginプロパティに変更しているのは、右端のデータ・アイテムの表示が欠けないようにするためである。変更しないとどうなるのかは、実際に試してみてほしい。
また、SizeChangedイベント・ハンドラを追加しているが、これについては後述する。取りあえず空のSizeChangedイベント・ハンドラを実装して実行してみると、次の画像のようになる。
実行してみて、以前とほぼ同じように動作することを確かめておいてほしい*3。
*3 GridViewコントロール上では、マウスのホイール操作でスクロールできなくなっていることに気付かれただろうか? 簡単に実装できる代わりに、この方法にはこのような欠点があるのだ。この欠点に対処するにはGridViewコントロール自体を改造するしかなさそうであり、その方法は次回に説明する。
次に、画面が切り替えられたときにスクロール位置を保存するコードを追加する。ItemsPage.xaml.cs/.vbファイルに、次のようにSaveStateメソッドを記述する。ScrollViewerコントロールのHorizontalOffsetプロパティの値を、そのままリポジトリに保存するだけである。
protected override void SaveState(Dictionary<string, object> pageState)
{
base.SaveState(pageState);
pageState["ScrollPosition"] = this.scrollViewer1.HorizontalOffset;
}
Protected Overrides Sub SaveState(pageState As Dictionary(Of String, Object))
MyBase.SaveState(pageState)
pageState("ScrollPosition") = Me.scrollViewer1.HorizontalOffset
End Sub
スクロール位置を復元するのは、ちょっと面倒だ。ページのLoadStateメソッドで先ほど保存しておいた値を取り出しておき、GridViewコントロールのSizeChangedイベントでスクロール位置を設定する。
まず、復元時に使用するスクロール位置を記憶しておくためのメンバ変数「_scrollPosition」を作り、LoadStateメソッドの末尾に次のコードを追加する。
private double? _scrollPosition;
protected override void LoadState(Object navigationParameter,
Dictionary<String, Object> pageState)
{
……
if (pageState != null && pageState.ContainsKey("ScrollPosition"))
_scrollPosition = pageState["ScrollPosition"] as double?;
}
Private _scrollPosition As Double?
Protected Overrides Sub LoadState(navigationParameter As Object, _
pageState As Dictionary(Of String, Object))
……
If (pageState IsNot Nothing) _
AndAlso pageState.ContainsKey("ScrollPosition") Then
_scrollPosition = pageState("ScrollPosition")
End If
End Sub
LoadStateメソッドが呼ばれた時点では、GridViewコントロールにデータは読み込まれておらず、従ってGridViewコントロールのサイズも小さいままなので、スクロール位置を設定できない(設定しても無視される)。そこで、データが読み込まれた後でGridViewコントロールのサイズが大きくなったときにSizeChangedイベントが発生することを利用する。次のようにして、GridViewコントロールのSizeChangedイベント・ハンドラでスクロール位置を設定するのだ。
private void itemGridView_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (_scrollPosition.HasValue)
{
if (scrollViewer1.ScrollableWidth >= _scrollPosition.Value)
{
scrollViewer1.ScrollToHorizontalOffset(_scrollPosition.Value);
_scrollPosition = null;
}
}
}
Private Sub itemGridView_SizeChanged(sender As Object, e As SizeChangedEventArgs)
If (_scrollPosition.HasValue) Then
If (scrollViewer1.ScrollableWidth >= _scrollPosition.Value) Then
scrollViewer1.ScrollToHorizontalOffset(_scrollPosition.Value)
_scrollPosition = Nothing
End If
End If
End Sub
これで完成だ。ほかの画面に遷移して戻ってきたときに、また、アプリを中断→終了させた後でまた起動したときに、スクロール位置が復元されることを確かめてほしい。
●まとめ
ScrollViewerコントロールの中にGridViewコントロールを入れることで、スクロール位置の保存と復元ができた。このScrollViewerコントロールの中に入れる手法は、ListViewコントロールやImageコントロールなどに広く応用できる。
Copyright© Digital Advantage Corp. All Rights Reserved.