Используем Silverlight DataPager без WCF RIA
- modified:
- reading: 5 minutes
Может я как-то не правильно подошел к DataPager, но, как оказалось, заставить его нормально работать без DomainDataService не так уж и просто. Идея у меня была простая, думал найти свойство, вроде ItemCount (оно есть, но только для чтения), туда поставить то количество элементов, которое у меня есть, сделать байдинги на PageSize и PageIndex и все должно быть готово. Но пришлось делать все совершенно по-другому.
Вообще, мне кажется, что такая проблема, наверняка, была не только у меня, ну не всегда и не везде же используется WCF RIA, а использовать пейджинг в списках это уже правило хорошего тона, если знаешь, что список будет расти. Интересно, кто как борется с этой проблемой. А я пока расскажу про свой велосипед.
Решение (если его можно назвать таким) у меня всплыло сразу, подумал о подсовывании ему левой коллекции объектов, вроде
dataPager.Source = Enumerable.Range(1, itemsCount);
Тут конечно нужно понимать, что существует проблема, если элементов на сервере, действительно много, то есть itemsCount содержит, например, больше миллиона, то и память у нас будет заниматься достаточно шустро, потому мой способ лучше откинуть, и уже честно реализовывать либо свой DataPager, либо специальную IPagedCollectionView коллекцию. В моем случае, когда речь идет о тысячах, десятках тысяч, решил пока оставить так, хотя в ближайшем будущем переделаем на свою IPagedCollectionView.
В общем, в результате у меня получился такой вот контрол:
public class CustomDataPager : DataPager
{ public CustomDataPager()
{ // Problems with first binding if source is null or PageIndex is more than -1
SetEmptyCollection();
}
public static readonly DependencyProperty TotalItemsCountProperty =
DependencyProperty.Register("TotalItemsCount", typeof (int), typeof (CustomDataPager),
new PropertyMetadata(0, TotalItemsCountPropertyChanged));
public static readonly DependencyProperty PageSizeExProperty =
DependencyProperty.Register("PageSizeEx", typeof (int), typeof (CustomDataPager),
new PropertyMetadata(0, PageSizeExPropertyChanged));
public static void TotalItemsCountPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ if (d is CustomDataPager && e.NewValue is int)
{ var dataPager = d as CustomDataPager;
var newValue = int.Parse(e.NewValue.ToString());
if (dataPager.ItemCount != newValue)
dataPager.SetCollection(newValue);
}
}
public static void PageSizeExPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ if (d is CustomDataPager && e.NewValue is int)
{ var dataPager = d as CustomDataPager;
var newValue = int.Parse(e.NewValue.ToString());
dataPager.PageSize = newValue;
}
}
public int TotalItemsCount
{ get { return (int) GetValue(TotalItemsCountProperty); }
set { SetValue(TotalItemsCountProperty, value); }
}
public int PageSizeEx
{ get { return (int) GetValue(PageSizeExProperty); }
set { SetValue(PageSizeExProperty, value); }
}
private void SetEmptyCollection()
{
SetCollection(0);
}
private void SetCollection(int itemsCount)
{ if (itemsCount > 0)
Source = new PagedCollectionView(Enumerable.Range(1, itemsCount));
else
Source = new PagedCollectionView(new List<int>());
}
}
Ничего умного. Два DependencyProperty, одно из которых – это количество элементов на сервере, при изменении которого, я подставляю фейковую коллекцию. Второе PageSizeEx – это на самом деле обычный PageSize, мне пришлось сделать дополнительное свойство, потому что c PageSize мой байдинг не работал, не знаю почему. А это свойство имеет очень простое поведении, при изменении выставляет значение в PageSize.
Теперь пример использования. Создадим класс Foo, а так же класс FooService, который будет имитировать загрузку страницы с сервера (с небольшой задержкой и асинхронно):
public class Foo
{ public int ID { get; set; }
public string Name { get; set; }
}
public class FooService
{ public void GetFooListAsync(int pageIndex, int pageSize, Action<PageCollectionInfo<Foo>> postBack)
{ Thread thread = new Thread(() =>
{ List<Foo> list = new List<Foo>();
for (int i = pageIndex*pageSize; i < (pageIndex + 1)*pageSize; i++)
{ list.Add(new Foo {ID = i, Name = string.Format("Foo {0}", i)});
}
Thread.Sleep(1000);
postBack(new PageCollectionInfo<Foo> {ItemsCount = 1000, PageCollection = list});
});
thread.Start();
}
}
Реализуем BindingModel:
public class MainPageBindingModel : INotifyPropertyChanged
{ private readonly List<int> _pageSizes = new List<int> {10, 25, 50, 100};
private readonly FooService _service;
private int _pageIndex;
private int _pageSize;
private int _itemsCount;
private List<Foo> _items;
private bool _isBusy;
public MainPageBindingModel()
{ _service = new FooService();
_pageSize = _pageSizes.First();
LoadItems();
}
public List<int> PageSizes
{ get { return _pageSizes; }
}
public int PageIndex
{ get { return _pageIndex; }
set { _pageIndex = value; OnPropertyChanged("PageIndex"); LoadItems(); }
}
public int PageSize
{ get { return _pageSize; }
set { _pageSize = value; OnPropertyChanged("PageSize"); LoadItems(); }
}
public List<Foo> Items
{ get { return _items; }
set { _items = value; OnPropertyChanged("Items"); }
}
public int ItemsCount
{ get { return _itemsCount; }
set { _itemsCount = value; OnPropertyChanged("ItemsCount"); }
}
public bool IsBusy
{ get { return _isBusy; }
set { _isBusy = value; OnPropertyChanged("IsBusy"); }
}
private void LoadItems()
{ if (!IsBusy)
{ IsBusy = true;
var dispatcher = Application.Current.RootVisual.Dispatcher;
_service.GetFooListAsync(PageIndex, PageSize, (x) =>
{
dispatcher.BeginInvoke(() =>
{
Items = x.PageCollection;
ItemsCount = x.ItemsCount; IsBusy = false;
});
});
}
}
private void OnPropertyChanged(string propertyName)
{ PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
Тут все тоже очень просто, есть готовые свойства PageIndex, PageSize, которые говорят о том сколько элементов мы хотим видеть на странице, и какую страницу мы видим. PageSizes – это подготовленные размеры страниц, которые пользователь может выставлять. Items и ItemsCount – информация об объектах, первое свойство содержит элементы текущей страницы, а второе информацию о том, сколько всего у нас элементов на сервере. Дальше, XAML разметка:
<UserControl xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" x:Class="SilverlightDataPager.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:SilverlightDataPager="clr-namespace:SilverlightDataPager" Width="500" Height="300">
<Grid x:Name="LayoutRoot" Background="White" >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<sdk:DataGrid Grid.ColumnSpan="2" ItemsSource="{Binding Path=Items}" CanUserSortColumns="False">
</sdk:DataGrid>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock VerticalAlignment="Center">Items at page:</TextBlock>
<ComboBox VerticalAlignment="Center" ItemsSource="{Binding Path=PageSizes}" SelectedItem="{Binding Path=PageSize, Mode=TwoWay}"/>
</StackPanel>
<SilverlightDataPager:CustomDataPager Grid.Row="1" Grid.Column="1" DisplayMode="FirstLastPreviousNext" PageSizeEx="{Binding Path=PageSize}"
PageIndex="{Binding Path=PageIndex, Mode=TwoWay}" TotalItemsCount="{Binding Path=ItemsCount}" HorizontalAlignment="Right" />
<toolkit:BusyIndicator IsBusy="{Binding Path=IsBusy}" Grid.RowSpan="2" Grid.ColumnSpan="2" />
</Grid>
</UserControl>
Ну и само приложение:
Это конечно же, уж очень примитивные наброски, но может кому-то поможет.
Весь исходный код можно забрать с моего репозитория на assembla.com.