How come DataPager doesn't pick up my TotalItemCount?

2.6k views Asked by At

I'm trying to create an MVVM-friendly page that contains a datagrid and a data pager for Silverlight. On my viewmodel, I've implemented the IPagedCollectionView interface

public class DiscountViewModel : INotifyPropertyChanged, IPagedCollectionView

and I've implemented all the methods necessary to get ItemCount and TotalItemCount.

public bool CanChangePage {
        get { return TotalItemCount > PageIndex * PageSize; }
    }

    public bool IsPageChanging {
        get { return false; }
    }

    public int ItemCount {
        get { return itemCount; }
        set { itemCount = value; RaisePropertyChange("ItemCount"); }
    }

   public bool MoveToFirstPage() {
        PageChanging(this, new PageChangingEventArgs(PageIndex));
        PageIndex = 0;
        PageChanged(this, null);
        return true;
    }

    public bool MoveToLastPage() {
        throw new NotImplementedException();
    }

    public bool MoveToNextPage() {
        PageChanging(this, new PageChangingEventArgs(PageIndex));
        PageIndex++;
        PageChanged(this, null);
        return true;
    }

    public bool MoveToPage(int pageIndex) {
        PageChanging(this, new PageChangingEventArgs(PageIndex));
        PageIndex = pageIndex;
        PageChanged(this, null);
        return true;
    }

    public bool MoveToPreviousPage() {
        PageChanging(this, new PageChangingEventArgs(PageIndex));
        PageIndex--;
        PageChanged(this, null);
        return true;
    }

    public event EventHandler<EventArgs> PageChanged;

    public event EventHandler<PageChangingEventArgs> PageChanging;

    public int PageIndex {
        get { return pageIndex; }
        set { pageIndex = value; RaisePropertyChange("PageIndex"); }
    }

    public int PageSize {
        get { return pageSize; }
        set { pageSize = value; RaisePropertyChange("PageSize"); }
    }

    public int TotalItemCount {
        get { return totalItemCount; }
        set { totalItemCount = value; RaisePropertyChange("TotalItemCount"); }
    }

The XAML is working fine to bind to the items and the datagrid shows the first five items when initially loaded.

<data:DataGrid x:Name="discountsDataGrid"  ItemsSource="{Binding Discounts, Mode=TwoWay}"  MinHeight="200" AutoGenerateColumns="False" SelectedItem="{Binding SelectedDiscount, Mode=TwoWay}">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="RowEditEnded">
                            <i:InvokeCommandAction Command="{Binding SaveChangesCommand}" CommandParameter="{Binding SelectedDiscount}"  />
                        </i:EventTrigger>
                    </i:Interaction.Triggers>

                    <data:DataGrid.Columns>
                        <data:DataGridTextColumn Header="Name" Binding="{Binding Name}" />
                        <data:DataGridTextColumn Header="Discount Amount" Binding="{Binding Amount}" />
                    </data:DataGrid.Columns>
                </data:DataGrid>
                <sdk:DataPager  PageSize="{Binding PageSize}"  Source="{Binding Path=ItemsSource, ElementName=discountsDataGrid}" Margin="0,-1,0,0" />

The relavent code for getting the discounts and then setting the total count all execute and the numbers come out to what I'd expect (5 for the items returned and 9 for the total count of all discounts)

public void LoadDiscounts(Object parameter){
        EntityQuery<Discount> eq = ctx.GetPagedDiscountsQuery(PageIndex, PageSize, "");
        eq.IncludeTotalCount = true;
        ctx.Load<Discount>(eq, OnDiscountsLoaded, null);
    }

    private void OnDiscountsLoaded(LoadOperation<Discount> loadOperation) {
        if (loadOperation.Error != null) {

        } else {
            Discounts = loadOperation.Entities;   
            ItemCount = loadOperation.TotalEntityCount;
            ctx.GetDiscountCount(OnCountCompleted, null);
            RaisePropertyChange("Discounts");
        }
    }

    private void OnCountCompleted(InvokeOperation<int> op) {
        TotalItemCount = op.Value;
        RaisePropertyChange("Discounts");
        RaisePropertyChange("TotalItemCount");
    }

But the datapager doesn't seem to pick up that there are more than 5 discounts. I can see that the TotalItemCount is correctly set. The odd thing about this, is that the TotalItemCount property is never called by any other code except to set the value. Shouldn't the datapager be using that to determine whether the Prev / Next / First / Last buttons can be clicked?

Update So I looked at the DataPager while subscribing to the MouseEnter event and found something interesting. The ItemCount is 5 and no matter what I set it to on the ViewModel (for instance, setting it to 9 manually), the DataPager only looks at the items in its collection to determine how many items there actually are. It is not reading those values from the ViewModel class. I thought I read that if the surrounding context implements IPagedCollectionView, that the DataPager would use those methods to determine size / page /etc.

2

There are 2 answers

1
DaveB On BEST ANSWER

The DataPager controls Source property should be bound to the collection. Not the DataGrid control's ItemsSource property.

From DataPager.Source property:

The source can be any IEnumerable collection. When bound to an IEnumerable that does not implement IPagedCollectionView, the DataPager behaves as if all the data is on a single page. Setting DataPager properties will not have any effect on the control.

Typically, the source is a collection that implements IPagedCollectionView. The IPagedCollectionView provides paging functionality, and the DataPager control provides a user interface for interacting with the IPagedCollectionView. To provide paging functionality for an IEnumerable collection, you can wrap it in the PagedCollectionView class.

List<String> itemList = new List<String>();
// Generate some items to add to the list.
for (int i = 1; i <= 33; i++)
{
    System.Text.StringBuilder sb = new System.Text.StringBuilder("Item ");
    sb.Append(i.ToString());
    itemList.Add(sb.ToString());
}
// Wrap the itemList in a PagedCollectionView for paging functionality
PagedCollectionView itemListView = new PagedCollectionView(itemList);

// Set the DataPager and ListBox to the same data source.
dataPager1.Source = itemListView;
listBox1.ItemsSource = itemListView;
0
Rossen Hristov On

I have noticed that you are using WCF RIA Services. In simpler scenarios you can simply bind both your DataGrid and DataPager to a DomainDataSource and you will not have to implement anything. But I guess that you had a more complex requirement and that is why you are doing everything by hand.