outcoldman
outcoldman Denis Gladkikh

Используем Silverlight DataPager без WCF RIA

Silverlight, XAML, DataGrid, DataPager, and Paging

Может я как-то не правильно подошел к 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>

Ну и само приложение:

Get Microsoft Silverlight

Это конечно же, уж очень примитивные наброски, но может кому-то поможет.

Весь исходный код можно забрать с моего репозитория на assembla.com.

Have feedback or questions? Looking for consultation?

My expertise: MongoDB, ElasticSearch, Splunk, and other databases. Docker, Kubernetes. Logging, Metrics. Performance, memory leaks.

Send me an email to public@denis.gladkikh.email.

The content on this site represents my own personal opinions and thoughts at the time of posting.

Content licensed under the Creative Commons CC BY 4.0.