Silverlightでカスタムページングをしたい時
また日記を放置していたので、月一くらいでは小ネタも書いておこうかと(・∀・;)
元ネタはこちらですが、ちょっと使ったのでSilverlightでのカスタムページングについて。
http://weblogs.asp.net/manishdalal/archive/2009/10/01/silverlight-3-custom-sorting-with-paging-support.aspx
SilverlightでDataGrid、DataPagerを使ってデータ管理アプリケーションを作るときの話ですが(・ω・)
Silverlightで作ったクライアントにデータを提供するサービス側も.NETで作って、WCF RIA ServicesでLinqToEntitiesDomainServiceなんかを使えば、ページング処理なんかも楽ちんぽんと作れて、ちゃんとSkip() & Take()した必要なデータだけの取得なんかも出来ちゃうわけですが。
でも、サービス側がJava製だったり既存のRESTなサービスを使いたいときとか、パラメータを自分で組み立ててデータ取得の通信を行いたいときにはどうするか(・ω・)?
もちろん、ページに必要なデータのみをオンデマンドで取得する方法で。*1
っで、そこで登場するのが、ICollectionView、IPagedCollectionViewですね(・∀・)
ICollectionViewの定義はこんなんですが。
public interface ICollectionView : IEnumerable, INotifyCollectionChanged { event EventHandler CurrentChanged; event CurrentChangingEventHandler CurrentChanging; bool Contains(object item); IDisposable DeferRefresh(); bool MoveCurrentTo(object item); bool MoveCurrentToFirst(); bool MoveCurrentToLast(); bool MoveCurrentToNext(); bool MoveCurrentToPosition(int position); bool MoveCurrentToPrevious(); void Refresh(); bool CanFilter { get; } bool CanGroup { get; } bool CanSort { get; } CultureInfo Culture { get; set; } object CurrentItem { get; } int CurrentPosition { get; } Predicate<object> Filter { get; set; } ObservableCollection<GroupDescription> GroupDescriptions { get; } ReadOnlyObservableCollection<object> Groups { get; } bool IsCurrentAfterLast { get; } bool IsCurrentBeforeFirst { get; } bool IsEmpty { get; } SortDescriptionCollection SortDescriptions { get; } IEnumerable SourceCollection { get; } }
カスタムページングに関連するのは、CanSort、SortDescriptions、Refresh()/DeferRefresh()のあたり(・ω・)
ICollectionViewの実装をDataGridにバインドしてソートをすると、DeferRefresh()が呼び出されます。
DeferRefresh()は更新遅延用のメカニズムなので、IDisposable.Dispose()内でRefresh()メソッドをコールバックするオブジェクトを返してあげればOK。
っで、Refresh()内ではイベントをあげて、ソート条件にあったデータの取得処理を呼び出し、コレクションを再構築してあげるっと。
ソートで使用する列や昇順降順はSortDescriptionsプロパティを参照、っと。
っで、ICollectionViewの実装としてはObservableCollectionを継承したクラスを作る方向で(・ω・)
CurrentItem、CurrentPositionに対応するための現在位置のプロパティを用意して、コレクションの操作時にはその情報も更新して。
CanFilter、CanGroupのあたりは、とりあえずソートに対応するだけであればfalseを返す実装でOK。
あとは、MoveXxx()を実装して、CurrentChanged、CurrentChanging及びOnPropertyChanged()を適切に呼び出してあげれば、オンデマンドでのデータ取得に対応したコレクションの完成っと。
更にページングに対応するためには、IPagedCollectionViewも実装(・ω・)
IPagedCollectionViewの定義はこんなんですが。
public interface IPagedCollectionView { event EventHandler<EventArgs> PageChanged; event EventHandler<PageChangingEventArgs> PageChanging; bool MoveToFirstPage(); bool MoveToLastPage(); bool MoveToNextPage(); bool MoveToPage(int pageIndex); bool MoveToPreviousPage(); bool CanChangePage { get; } bool IsPageChanging { get; } int ItemCount { get; } int PageIndex { get; } int PageSize { get; set; } int TotalItemCount { get; } }
IPagedCollectionViewの実装をDataPagerにバインドすると、ページの変更時にはMoveToXxx()が呼び出されるので(・ω・)
ICollectionViewの実装にIPagedCollectionViewも実装してあげて、MoveToXxx()の中でRefresh()を呼び出すようにすれば、ページングもオンデマンドで行えるようになるっと。
っで、リンク先からはその実装例がダウンロードできますね(・∀・)
ICollectionView及びIPagedCollectionView実装のPagedSortableCollectionViewの使い方はこんなんです。
まずViewModelをこんな感じで用意して。
public class HogeViewModel { public PagedSortableCollectionView<Hoge> DataList { get; private set; } public int PageSize { get { return DataList.PageSize; } } public HogeViewModel() { DataList = new PagedSortableCollectionView<Hoge>(); DataList.OnRefresh += new EventHandler<RefreshEventArgs>(DataList_OnRefresh); GetData(); // 初期データ取得 } // DataGrid、DataPagerでの操作からこのハンドラが呼び出される private void DataList_OnRefresh(object sender, RefreshEventArgs e) { GetData(); } private void GetData() { // 下記の条件を元に検索 int take = DataList.PageSize; int skip = DataList.PageIndex * DataList.PageSize; foreach(SortDescription sortDesc in DataList.SortDescriptions) { ... } // 本当はここで非同期通信開始 ... // 完了ハンドラでデータ更新 DataList.Clear(); foreach (Hoge hoge in list) { DataList.Add(hoge); } DataList.TotalItemCount = count; } }
DataGrid、DataPagerとはこんな風にバインディング。
<Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.DataContext> <vm:PeopleViewModel /> </Grid.DataContext> <data:DataGrid x:Name="dataGrid" AutoGenerateColumns="True" Grid.Row="0" ItemsSource="{Binding DataList}" /> <data:DataPager x:Name="dataPager" Grid.Row="1" PageSize="{Binding PageSize}" DisplayMode="FirstLastPreviousNext" IsTotalItemCountFixed="True" Source="{Binding DataList}" /> </Grid>
これで、カスタムソート/ページングしたデータの取得をオンデマンドで行う仕組みができました(・∀・)
将来のWCFはRESTfulということですが、このあたりのサポートも強化されると嬉しいかな〜(・ω・)
*1:サンプルレベルのものだと、画面初期表示に必要なデータを取得するようなものしかなかったりしてあれですが(´д`;)