65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF - 在 DataGrid / ListBox 中分页

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (14投票s)

2012 年 3 月 20 日

CPOL

3分钟阅读

viewsIcon

94031

downloadIcon

6028

为 DataGrid / ListBox 控件提供分页功能。

问题

WPF 中 DataGrid/ListBox 不支持分页。

引言

我从 3 年前就开始从事 WPF 开发。最初,WPF 中没有 DataGrid 控件。微软在 .NET Framework- 4 中引入了它。因此,我们过去常常通过应用控件模板来将 ListBox 充当 DataGrid。在我的项目中,我们有大量的数据。我们必须以块(页面)的形式检索数据。为此,我们必须在每个页面上实现 UI 逻辑 + 数据获取逻辑,这非常繁琐。所以,我决定制作一个可以处理分页的通用控件。

概述

与其向 DataGrid 添加分页功能,我提出了另一个想法,将分页作为单独的控件。分页控件将负责页面检索任务。开发人员只需要将 DataGridListBox 绑定到分页控件提供的 ItemsSource 即可。

因此,它有点像即插即用系统。您无需编码就可以将任何能够显示 ICollection<T> 的控件插入到 PagingControl 中。您只需要实现 IpageContract 接口。此接口仅包含两个方法,一个用于获取计数,另一个用于获取数据。

在第一篇文章中,我只介绍了以块(页面)的形式获取数据,没有任何筛选或搜索条件。我将在后续文章中介绍这些内容。

实现细节

[TemplatePart(Name = "PART_FirstPageButton", Type = typeof(Button)),
    TemplatePart(Name = "PART_PreviousPageButton", Type = typeof(Button)),
    TemplatePart(Name = "PART_PageTextBox", Type = typeof(TextBox)),
    TemplatePart(Name = "PART_NextPageButton", Type = typeof(Button)),
    TemplatePart(Name = "PART_LastPageButton", Type = typeof(Button)),
    TemplatePart(Name = "PART_PageSizesCombobox", Type = typeof(ComboBox))]

public class PaggingControl : Control
{
    ………
}

我在分页控件中使用了 TemplatePart。它是一个继承 Control 类的单个 .CS 文件。在这里,我使用了四个用于导航的按钮,一个文本框用于显示当前页面或手动设置页面,以及一个组合框用于设置页面大小。我使用 TemplatePart 让其他开发人员可以完全更改此控件的 UI,并使其易于使用。

我已经创建了以下依赖属性和用于绑定的相关简单属性。

public static readonly DependencyProperty ItemsSourceProperty;
public static readonly DependencyProperty PageProperty;
public static readonly DependencyProperty TotalPagesProperty;
public static readonly DependencyProperty PageSizesProperty;
public static readonly DependencyProperty PageContractProperty;
public static readonly DependencyProperty FilterTagProperty;

public ObservableCollection<object> ItemsSource
public uint Page
public uint TotalPages
public ObservableCollection<uint> PageSizes
public IPageControlContract PageContract
public object FilterTag

我为页面更改事件创建了两个 RoutedEvent,一个在更改页面之前触发,另一个在更改页面之后触发。

public delegate void PageChangedEventHandler(object sender, PageChangedEventArgs args);
public static readonly RoutedEvent PreviewPageChangeEvent;
public static readonly RoutedEvent PageChangedEvent;

public event PageChangedEventHandler PreviewPageChange
public event PageChangedEventHandler PageChanged

我们已经重写了 OnApplyTemplate 方法。通过这样做,我们将获取所有子控件的引用到局部变量中,以便我们可以在整个控件中引用它们。我们还确保它们都没有丢失。如果其中任何一个丢失,我们将抛出异常。

public override void OnApplyTemplate()
{
    btnFirstPage = this.Template.FindName("PART_FirstPageButton", this) as Button;
    btnPreviousPage = this.Template.FindName("PART_PreviousPageButton", this) as Button;
    txtPage = this.Template.FindName("PART_PageTextBox", this) as TextBox;
    btnNextPage = this.Template.FindName("PART_NextPageButton", this) as Button;
    btnLastPage = this.Template.FindName("PART_LastPageButton", this) as Button;
    cmbPageSizes = this.Template.FindName("PART_PageSizesCombobox", this) as ComboBox;

    if (btnFirstPage == null ||
        btnPreviousPage == null ||
        txtPage == null ||
        btnNextPage == null ||
        btnLastPage == null ||
        cmbPageSizes == null)
    {
        throw new Exception("Invalid Control template.");
    }


    base.OnApplyTemplate();
}

一旦控件加载完成,我们就开始工作。

void PaggingControl_Loaded(object sender, RoutedEventArgs e)
{
    if (Template == null)
    {
        throw new Exception("Control template not assigned.");
    }

    if (PageContract == null)
    {
        throw new Exception("IPageControlContract not assigned.");
    }

    RegisterEvents();
    SetDefaultValues();
    BindProperties();
}

在上面的代码中,我们首先检查控件模板是否已应用于 PagingControl。在检查模板后,我们转向 PageContract。我们检查是否已分配 PageContract。这个契约很重要,因为所有数据检索工作都由这个 PageContract 实例完成。

private void RegisterEvents()
{
    btnFirstPage.Click += new RoutedEventHandler(btnFirstPage_Click);
    btnPreviousPage.Click += new RoutedEventHandler(btnPreviousPage_Click);
    btnNextPage.Click += new RoutedEventHandler(btnNextPage_Click);
    btnLastPage.Click += new RoutedEventHandler(btnLastPage_Click);

    txtPage.LostFocus += new RoutedEventHandler(txtPage_LostFocus);

    cmbPageSizes.SelectionChanged += new SelectionChangedEventHandler(cmbPageSizes_SelectionChanged);
}

SetDefaultValues 方法会将局部变量属性设置为适当的默认值。

private void SetDefaultValues()
{
    ItemsSource = new ObservableCollection<object>();

    cmbPageSizes.IsEditable = false;
    cmbPageSizes.SelectedIndex = 0;
}

BindProperties 将进行属性绑定。在这里,我们将 Page 属性绑定到控件模板为 PageControl 提供的文本框。对于 PageSizes 属性也是如此 - ComboBox 控件。

private void BindProperties()
{
    Binding propBinding;
    propBinding = new Binding("Page");
    propBinding.RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent);
    propBinding.Mode = BindingMode.TwoWay;
    propBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
    txtPage.SetBinding(TextBox.TextProperty, propBinding);

    propBinding = new Binding("PageSizes");
    propBinding.RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent);
    propBinding.Mode = BindingMode.TwoWay;
    propBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
    cmbPageSizes.SetBinding(ComboBox.ItemsSourceProperty, propBinding);
}

现在,我们已经完成了设置控件。由于我们在组合框中保留了 SelectedIndex=0,在加载完成后,组合框的选择会发生变化。因此,项目更改事件将被触发。因此,控件将开始加载数据。

void cmbPageSizes_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    Navigate(PageChanges.Current);
}

上面的事件将调用一个带有导航类型的私有方法。它是一个枚举。它定义如下。

internal enum PageChanges
{
    First,        //FOR FIRST BUTTON
    Previous,     //FOR PREVIOUS BUTTON
    Current,      //FOR COMBOBOX ITEM CHANGE EVENT AND PAGE TEXT LOST FOCUS
    Next,         //FOR NEXT BUTTON
    Last          //FOR LAST BUTTON
}

此导航方法从所有 6 个注册事件中调用,带有适当的枚举值。此方法包含分页控件的核心逻辑。

private void Navigate(PageChanges change)
{
    uint totalRecords;
    uint newPageSize;

    if (PageContract == null)     //IF NO CONTRACT THEN RETURN
    {
        return;
    }

    totalRecords = PageContract.GetTotalCount();
    //GETTING NEW TOTAL RECORDS COUNT
    newPageSize = (uint)cmbPageSizes.SelectedItem;
    //GETTING NEW PAGE SIZE

    if (totalRecords == 0)
    {
        //IF NO RECORD FOUND, THEN CLEAR ITEMSSOURCE
        ItemsSource.Clear();
        TotalPages = 1;
        Page = 1;
    }
    else
    {
        //CALCULATE TOTALPAGES
        TotalPages = (totalRecords / newPageSize) + (uint)((totalRecords % newPageSize == 0) ? 0 : 1);
    }

    uint newPage = 1;

	//SETTING NEW PAGE VARIABLE BASED ON CHANGE ENUM
	//FOLLOWING SWITCH CODE IS SELF-EXPLANATORY

    switch (change)
    {
        case PageChanges.First:
            if (Page == 1)
            {
                return;
            }
            break;

        case PageChanges.Previous:
            newPage = (Page - 1 > TotalPages) ? TotalPages : (Page - 1 < 1) ? 1 : Page - 1;
            break;
        case PageChanges.Current:
            newPage = (Page > TotalPages) ? TotalPages : (Page < 1) ? 1 : Page;
            break;
        case PageChanges.Next:
            newPage = (Page + 1 > TotalPages) ? TotalPages : Page + 1;
            break;
        case PageChanges.Last:
            if (Page == TotalPages)
            {
                return;
            }
            newPage = TotalPages;
            break;
        default:
            break;
    }

	//BASED ON NEW PAGE SIZE, WE’LL CALCULATE STARTING INDEX.
    uint StartingIndex = (newPage - 1) * newPageSize;
    uint oldPage = Page;

	//HERE, WE’RE RAISING PREVIEW PAGE CHANGE ROUTED EVENT
    RaisePreviewPageChange(Page, newPage);

    Page = newPage;
    ItemsSource.Clear();

    ICollection<object> fetchData = 
          PageContract.GetRecordsBy(StartingIndex, newPageSize, FilterTag);

 
	//FETCHING DATA FROM DATASOURCE USING PROVIDED CONTRACT
	//I’LL EXPLAIN FilterTag IN SUBSEQUENT ARTICLES
	//RIGHT NOW IT IS NOT USED

    foreach (object row in fetchData)
    {
        ItemsSource.Add(row);
    }
 

    RaisePageChanged(oldPage, Page);
    //RAISING PAGE CHANGED EVENT.

}

在 XAML 中使用控件

您必须将一个 DataGrid/ListBoxPagingControl 放在 Window 中。将其 ItemsSource 属性绑定到 PageControlItemsSource 属性。向 PageControl 提供 PaggingContract。是的,不要忘记将控件模板应用于 PageControl。完成这些操作后,PageControl 就准备好了。

<DataGrid 
        ItemsSource="{Binding ItemsSource, ElementName=pageControl, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" 
        AutoGenerateColumns="False"
        CanUserAddRows="False">

    <DataGrid.Columns>
        <DataGridTextColumn Header="First name" 
              Binding="{Binding FirstName}" IsReadOnly="True"/>
        <DataGridTextColumn Header="Middle name" 
              Binding="{Binding MiddleName}" IsReadOnly="True"/>
        <DataGridTextColumn Header="Last name" 
              Binding="{Binding LastName}" IsReadOnly="True"/>
        <DataGridTextColumn Header="Age" 
              Binding="{Binding Age}" IsReadOnly="True"/>
    </DataGrid.Columns>
</DataGrid>

<local:PaggingControl x:Name="pageControl" Grid.Row="1" Height="25"
                              PageContract="{StaticResource database}"
                              PreviewPageChange="pageControl_PreviewPageChange"
                              PageChanged="pageControl_PageChanged">

    <local:PaggingControl.PageSizes>
        <sys:UInt32>10</sys:UInt32>
        <sys:UInt32>20</sys:UInt32>
        <sys:UInt32>50</sys:UInt32>
        <sys:UInt32>100</sys:UInt32>
    </local:PaggingControl.PageSizes>
</local:PaggingControl>

我使用以下方式通过样式应用了控件模板。

<Style TargetType="{x:Type local:PaggingControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:PaggingControl}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>

                    <Button Name="PART_FirstPageButton" Content="<<" Grid.Column="0"/>
                    <Button Name="PART_PreviousPageButton" Content="<" Grid.Column="1"/>
                    <TextBox Name="PART_PageTextBox" Grid.Column="2"/>
                    <TextBlock Text="{Binding TotalPages, RelativeSource={RelativeSource TemplatedParent}}" Grid.Column="3"/>
                    <Button Name="PART_NextPageButton" Content=">" Grid.Column="4"/>
                    <Button Name="PART_LastPageButton" Content=">>" Grid.Column="5"/>
                    <ComboBox Name="PART_PageSizesCombobox" Grid.Column="6"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

很简单,不是吗!!!

我附上了项目文件。如果您有任何疑问或建议,请告诉我。我稍后会介绍筛选功能。 

© . All rights reserved.