カジ 旅 サポートk8 カジノGridViewのスクロール位置を復元するには?(その2)[Win 8]仮想通貨カジノパチンコ麻雀 役 表 印刷
online slot websitesk8 カジノ
ミリオン 石神井 公園 スロット 館powered by Insider.NET
連載目次
GridViewコントロールのスクロール位置を復元するために、前回はScrollViewerコントロールの中にGridViewコントロールを格納し、ScrollViewerコントロールのHorizontalOffsetプロパティの値を利用した。しかし、この方法では、GridViewコントロールの「UIの仮想化」機能が無効になる。すると、データが多いときに表示やスクロールの処理が重くなる。また、GridViewコントロール上でマウスのホイールを使用してスクロールを行えない問題もあった。これらを解決するにはどうしたらよいだろうか?
本稿では、GridViewコントロールを継承するカスタム・コントロールを作成することで、「UIの仮想化」を有効にしたままスクロール位置を復元する方法を解説する。本稿のサンプルは「Windows Store app samples:MetroTips #30」からダウンロードできる。
●事前準備
Windows 8(以降、Win 8)向けのWindowsストア・アプリを開発するには、Win 8とVisual Studio 2012(以降、VS 2012)が必要である。これらを準備するには、第1回のTIPSを参考にしてほしい。本稿では64bit版Win 8 ProとVS 2012 Express for Windows 8を使用している。
●「UIの仮想化」とは?
Windowsストア・アプリは、「UIの仮想化」によってアイテム・コンテナのインスタンス数を抑えている。
Windows.UI.Xaml.Controls名前空間のGridViewコントロールやListView コントロールなどでは、個々のデータはアイテム・コンテナにバインドされて表示される。このとき、デフォルトでは「UIの仮想化」が有効になっており、アイテム・コンテナは画面に表示するために必要な個数+αしかインスタンスが作られないようになっている。
例えば、次の図のようにGridViewコントロールを左へスクロールしたとき、左側へはみ出したアイテム・コンテナのインスタンスは、データを詰め替えられて右側に再配置されるのだ。
UIの仮想化により、アイテム・コンテナが再利用されるイメージ
前回のサンプル・アプリでデータの数を増やして、UIの仮想化の効果を確かめてみよう。次のようにダミー・データを追加するコードを変更し、前回は20個だったものを2000個に増やしてみる*1。
*1 ScrollViewerコントロールを追加した状態(=前回の完成形)から始める場合には、XAMLデザイナでItemsPage.xamlファイルを開いていたら、コードを変更してビルドする前にXAMLデザイナを閉じておくこと。後述するようにアプリの動作が遅くなるのだが、XAMLデザイナでの表示も例外ではなく、表示し終わるまではVS 2012の動作も重くなってしまう。
public SampleDataSource(){ …… // ダミー・データを追加するコード for (int i = 7; i <= 2000; i++) // 20→2000 { 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 2000 ' 20→2000 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 SubEnd Class
前回のダミー・データを追加するコードを改修した(上:C#、下:VB)20個だったダミー・データの数を2000個に変更した。
ScrollViewerコントロールにGridViewコントロールを格納していない場合は、ダミー・データを2000個に増やしても起動時間の違いは気が付かないほどだし、スクロールもスムースである。これはUIの仮想化が効いているためだ。しかし、前回の記事に従ってScrollViewerコントロールを追加すると、アプリの起動に時間が掛かる(あるいは、メモリ不足で起動しなくなる)ようになり、スクロールの動きも重くなる(Visual Studio 2012の動作が重くなる場合もある)。これは、ScrollViewerコントロールの中にGridViewコントロールを入れるとUIの仮想化が無効にされてしまい、アイテム・コンテナのインスタンスがデータの数だけ(=2000個)生成されてしまうからだ。
●GridViewコントロールの中のScrollViewerコントロール
ところで、GridViewコントロールは単体で使ってもスクロールできる(デフォルト状態の分割アプリやグリッド・アプリを見れば分かるとおりだ)。これはどうなっているのだろうか?
実は、GridViewコントロールは既定でその内部にScrollViewerコントロールを持っているのだ。そして、そのScrollViewerコントロールの中にアイテム・コンテナが配置されるのである。
そこで、GridViewコントロールが既定で持っているこのScrollViewerコントロールを直接操作してスクロール位置を復元できれば、前回のようにGridViewコントロールをScrollViewerの中に入れる必要なく、問題は解決するだろう。
●GetTemplateChildメソッド
Windows.UI.Xaml.Controls名前空間にある全てのコントロールはGetTemplateChildメソッドを持っており、コントロールの内部にあるコントロールを取得できる*2。このメソッドを使えば、GridViewコントロールの中のScrollViewerコントロールを操作できるのだ。
しかし、GetTemplateChildメソッドのスコープはprotected(VBではProtected)であるため、継承関係にないクラスからこのメソッドは呼び出せない。例えば、ItemsPage.xaml.csファイルで定義されるItemsPageクラスで「itemGridView.GetTemplateChild("ScrollViewer")」のような記述はできない。利用するにはコントロールを継承したクラスを作らねばならない。
*2 正確には、ビジュアル・ツリーの親子関係にある子を取得できる。見た目でコントロールの内部にあっても、ビジュアル・ツリーの親子関係になっていなければ取得できない。なお、本稿では説明しないが、VisualTreeHelperクラス(Windows.UI.Xaml.Media名前空間)を利用すれば、ビジュアル・ツリーをたどってコントロールを列挙したり、その名前を調べたりできる。
●GridViewコントロールを継承してスクロール位置を復元するには?
GridViewコントロールを継承したクラスを作り、GetTemplateChildメソッドで内部のScrollViewerコントロールを取得し、そのHorizontalOffsetプロパティを公開する。
VS 2012のプロジェクトに新しいクラスを追加し、名前はMyGridViewクラスとする。次のコードのように、GridViewコントロールを継承させ、OnApplyTemplateメソッドをオーバーライドし、HorizontalOffsetプロパティとScrollToHorizontalOffsetメソッドを追加する。OnApplyTemplateメソッドの中では、GetTemplateChildメソッドを使って内部のScrollViewerコントロールを取得しメンバ変数に保持しておく。HorizontalOffsetプロパティとScrollToHorizontalOffsetメソッドは、メンバ変数に保持してあるScrollViewerコントロールの同名プロパティ/メソッドをそのまま公開する。また、ページ側でスクロール位置を復元するタイミングを取るために、ScrollViewerコントロールのSizeChangedイベントも公開しておく。
class MyGridView : GridView{ private ScrollViewer _sv; // テンプレートの適用直後にScrollViewerコントロールを取得し、 // メンバ変数に保持しておく // また、ScrollViewerコントロールのSizeChangedイベントに // _sv_SizeChangedメソッドを登録しておく protected override void OnApplyTemplate() { base.OnApplyTemplate(); _sv = (ScrollViewer)this.GetTemplateChild("ScrollViewer"); _sv.SizeChanged += _sv_SizeChanged; } // HorizontalOffsetプロパティを公開する public double HorizontalOffset { get { return (_sv == null) ? 0.0 : _sv.HorizontalOffset; } } // ScrollToHorizontalOffsetメソッドを公開する public void ScrollToHorizontalOffset(double value) { if (_sv != null) _sv.ScrollToHorizontalOffset(value); } // ScrollViewerコントロールのSizeChangedイベントを公開する public event EventHandler<SizeChangedEventArgs> ScrollViewerSizeChanged; void _sv_SizeChanged(object sender, SizeChangedEventArgs e) { if (ScrollViewerSizeChanged != null) ScrollViewerSizeChanged.Invoke(this, e); }}
Public Class MyGridView Inherits GridView Private _sv As ScrollViewer ' テンプレートの適用直後にScrollViewerコントロールを取得し、 ' メンバ変数に保持しておく ' また、ScrollViewerコントロールのSizeChangedイベントに ' _sv_SizeChangedメソッドを登録しておく Protected Overrides Sub OnApplyTemplate() MyBase.OnApplyTemplate() _sv = Me.GetTemplateChild("ScrollViewer") AddHandler _sv.SizeChanged, AddressOf _sv_SizeChanged End Sub ' HorizontalOffsetプロパティを公開する Public ReadOnly Property HorizontalOffset As Double Get If (_sv Is Nothing) Then Return 0.0 End If Return _sv.HorizontalOffset End Get End Property ' ScrollToHorizontalOffsetメソッドを公開する Public Sub ScrollToHorizontalOffset(value As Double) If (_sv IsNot Nothing) Then _sv.ScrollToHorizontalOffset(value) End If End Sub ' ScrollViewerコントロールのSizeChangedイベントを公開する Public Event ScrollViewerSizeChanged As EventHandler(Of SizeChangedEventArgs) Sub _sv_SizeChanged(sender As Object, e As SizeChangedEventArgs) RaiseEvent ScrollViewerSizeChanged(Me, e) End SubEnd Class
GridViewコントロールを継承して作ったクラス(上:C#、下:VB)
次に、ItemsPage.xamlファイル(=最初に表示される画面の定義)を開き、GridViewの定義部分を次のコードのように変更する(この前に一度、プロジェクトをビルドしておくとよい)。コントロールを「GridView」から「local:MyGridView」に変え*3、ScrollViewerSizeChangedイベント・ハンドラを追加するのだ。
*3 2012のプロジェクトのサブフォルダにMyGridViewクラスを作成した場合は、接頭辞「local:」を適宜改める。新規にサブフォルダを作ってそこに置いた場合には、ページの先頭に名前空間の宣言も必要だ。
<local:MyGridView x:Name="itemGridView" …… ScrollViewerSizeChanged="itemGridView_ScrollViewerSizeChanged" />
最初の画面で使っているGridViewを、作成したMyGridViewに置き換えた(XAML)
最後に、前回と同じようにしてスクロール位置の保存と復元を行う。ItemsPage.xaml.cs/.vbファイルを次のコードのように変更する。
public sealed partial class ItemsPage : MetroTips030CS.Common.LayoutAwarePage{ …… protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState) { …… // 追加したコード if (pageState != null && pageState.ContainsKey("ScrollPosition")) _scrollPosition = pageState["ScrollPosition"] as double?; } …… // 以下は追加したコード private double? _scrollPosition; protected override void SaveState(Dictionary<string, object> pageState) { base.SaveState(pageState); pageState["ScrollPosition"] = this.itemGridView.HorizontalOffset; } private void itemGridView_ScrollViewerSizeChanged(object sender, SizeChangedEventArgs e) { if (_scrollPosition.HasValue) { this.itemGridView.ScrollToHorizontalOffset(_scrollPosition.Value); _scrollPosition = null; } }}
Public NotInheritable Class ItemsPage Inherits Common.LayoutAwarePage …… 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 …… ' 以下は追加したコード Private _scrollPosition As Nullable(Of Double) Protected Overrides Sub SaveState(pageState As Dictionary(Of String, Object)) MyBase.SaveState(pageState) pageState("ScrollPosition") = Me.itemGridView.HorizontalOffset End Sub Private Sub itemGridView_ScrollViewerSizeChanged(sender As Object, e As SizeChangedEventArgs) If (_scrollPosition.HasValue) Then Me.itemGridView.ScrollToHorizontalOffset(_scrollPosition.Value) _scrollPosition = Nothing End If End SubEnd Class
画面のコードビハインドを修正した(上:C#、下:VB)
これで完成だ。ほかの画面に遷移して戻ってきたときに、また、アプリを中断→終了させた後でまた起動したときに、スクロール位置が復元されることを確かめてほしい*4。
完成した画面UIの仮想化を有効にすることで2000個のデータでも軽快に動作する。また、グリッド上のマウス・ホイール操作でもスクロールするようになった(前回の方法ではスクロールしなかった)*5。
*4 最も右端までスクロールした場合は、残念ながら完全には復元できない。右端まで少し残した状態になってしまうのだ(上の画像)。スクロール位置は正しく保存されるのだが、その値を設定しても若干小さな値にセットされてしまう。ScrollViewerコントロールのScrollableWidthプロパティが実際よりなぜか若干小さな値になっており、その値に制限されてしまうようだ。
*5 前回の方法でグリッド上のマウス・ホイール操作ができなくなったのは、GridViewコントロールがマウス・ホイールのイベントを受け取ってしまい、スクロールを担っている外側のScrollViewerコントロールにイベントが伝わらなかったからだ。今回は、スクロールを担っているのは通常どおりにGridViewコントロールの中のScrollViewerコントロールであるから、マウス・ホイール操作の問題もなくなった。
●まとめ
GridViewコントロールを改造することで、UIの仮想化を有効にしたままスクロール位置の保存と復元ができた。表示するデータの数が多くなると見込まれるときには、UIの仮想化を有効に保っておくことが大切だ。UIの仮想化については次のドキュメントを参照してほしい。
MSDN:リストまたはグリッドによる仮想化の使用
「WinRT/Metro TIPS」
仮想通貨カジノパチンコテレビ の 通販 サイト