使用动态数据进行数据分页

目录

介绍

动态数据

数据分页

填充数据源

页面切换

结论


 

介绍

如果您有一个大的数据集合,那么用集合中的所有数据填充项目控件就变得不切实际,更不用说用户不友好了。最好的方法是对数据进行分段,因此项目控件仅显示数据的子集,并允许用户在数据段之间循环。可以使用动态数据库.NET应用程序中实现这种分页功能,本文将介绍如何在WPF-MVVM应用程序中实现此功能。

动态数据

动态Data是一个可移植的类库,提供包含反应性扩展(Rx功能的集合。动态数据集合可以是SourceList<TObject>类型的可观察列表,也可以是SourceCache<TObject, TKey>类型的可观察缓存。这些集合使用可观察到的变更集进行管理,这些变更集是通过调用集合的Connect()运算符创建的,并且可以是类型IObservable<IChangeSet<TObject>>IObservable<IChangeSet<TObject, TKey>>。数据处理操作(如排序、分组、过滤、数据虚拟化和分页)是使用可链接在一起以执行复杂操作的运算符完成的。截至撰写本文时,该库有60个集合运算符。

要使用动态数据,您的项目必须引用动态数据NuGet包。

数据分页

如上一节所述,动态数据提供两种类型的反应性集合,它们充当数据源。要对数据进行分页,您需要利用SourceCache<TObject, TKey>集合。在示例项目中,此类集合在IEmployeesService实现中定义,并将包含Employee类型的对象。

using Bogus;
using DynamicData;
using PagedData.WPF.Models;
using System;

namespace PagedData.WPF.Services
{
    public class EmployeesService : IEmployeesService
    {
        private readonly ISourceCache<Employee, int> _employees;

        public EmployeesService() => _employees = new SourceCache<Employee, int>(e => e.ID);

        public IObservable<IChangeSet<Employee, int>>
               EmployeesConnection() => _employees.Connect();

        public void LoadData()
        {
            var employeesFaker = new Faker<Employee>()
                .RuleFor(e => e.ID, f => f.IndexFaker)
                .RuleFor(e => e.FirstName, f => f.Person.FirstName)
                .RuleFor(e => e.LastName, f => f.Person.LastName)
                .RuleFor(e => e.Age, f => f.Random.Int(20, 60))
                .RuleFor(e => e.Gender, f => f.Person.Gender.ToString());

            _employees.AddOrUpdate(employeesFaker.Generate(1500));
        }
    }
}

LoadData()中,通过调用集合的AddOrUpdate()方法将数据添加到可观察的缓存中。该方法有两个重载:一个重载单个对象,另一个重载对象集合。使用Bogus1500employee对象添加到可观察的集合中,该集合会生成2060岁之间的employee的伪数据。

集合的可观察更改集由EmployeesConnection()公开,调用集合的Connect()运算符。然后,可以将可观察到的变更集绑定到视图模型中的ReadOnlyObservableCollection,并且还可以调用其他运算符来执行数据管理操作。

public class MainWindowViewModel : ViewModelBase
{
    private const int PAGE_SIZE = 25;
    private const int FIRST_PAGE = 1;

    private readonly IEmployeesService _employeesService;
    private readonly ISubject<PageRequest> _pager;

    private readonly ReadOnlyObservableCollection<Employee> _employees;
    public ReadOnlyObservableCollection<Employee> Employees => _employees;

    public MainWindowViewModel(IEmployeesService employeesService)
    {
        _employeesService = employeesService;

        _pager = new BehaviorSubject<PageRequest>(new PageRequest(FIRST_PAGE, PAGE_SIZE));

        _employeesService.EmployeesConnection()
            .Sort(SortExpressionComparer<Employee>.Ascending(e => e.ID))
            .Page(_pager)
            .Do(change => PagingUpdate(change.Response))
            .ObserveOnDispatcher()
            .Bind(out _employees)
            .Subscribe();
    }

    ...
}

要对数据进行分页,首先必须对其进行排序。然后,您可以调用Page()运算符,该运算符采用ISubject<PageRequest>来指定第一页以及每页中的项目数。当集合发生变化时,Do()操作符提供更新,因此我使用它来使用IPagedChangeSet<TObject, TKey>响应更新几个视图模型属性。

private void PagingUpdate(IPageResponse response)
{
    TotalItems = response.TotalSize;
    CurrentPage = response.Page;
    TotalPages = response.Pages;
}

填充数据源

当应用程序加载时,数据将被添加到反应性集合中。这是通过视图模型中的LoadDataCommand完成的。

private RelayCommand _loadDataCommand;
public RelayCommand LoadDataCommand =>
    _loadDataCommand ??= new RelayCommand(_ => LoadEmployeeData());

private void LoadEmployeeData() => _employeesService.LoadData();

页面切换

数据页面之间的循环是使用先前定义的ISubject<PageRequest>来完成的,该对象具有一个传递了PageRequest对象的OnNext()运算符。

...

#region Previous page command
private RelayCommand _previousPageCommand;
public RelayCommand PreviousPageCommand => _previousPageCommand ??=
    new RelayCommand(_ => MoveToPreviousPage(), _ => CanMoveToPreviousPage());

private void MoveToPreviousPage() =>
    _pager.OnNext(new PageRequest(_currentPage - 1, PAGE_SIZE));

private bool CanMoveToPreviousPage() => CurrentPage > FIRST_PAGE;
#endregion

#region Next page command
private RelayCommand _nextPageCommand;
public RelayCommand NextPageCommand => _nextPageCommand ??=
    new RelayCommand(_ => MoveToNextPage(), _ => CanMoveToNextPage());

private void MoveToNextPage() =>
    _pager.OnNext(new PageRequest(_currentPage + 1, PAGE_SIZE));

private bool CanMoveToNextPage() => CurrentPage < TotalPages;
#endregion

#region First page command
private RelayCommand _firstPageCommand;
public RelayCommand FirstPageCommand => _firstPageCommand ??=
    new RelayCommand(_ => MoveToFirstPage(), _ => CanMoveToFirstPage());

private void MoveToFirstPage() =>
    _pager.OnNext(new PageRequest(FIRST_PAGE, PAGE_SIZE));

private bool CanMoveToFirstPage() => CurrentPage > FIRST_PAGE;
#endregion

#region Last page command
private RelayCommand _lastPageCommand;
public RelayCommand LastPageCommand => _lastPageCommand ??=
    new RelayCommand(_ => MoveToLastPage(), _ => CanMoveToLastPage());

private void MoveToLastPage() =>
    _pager.OnNext(new PageRequest(_totalPages, PAGE_SIZE));

private bool CanMoveToLastPage() => CurrentPage < TotalPages;
#endregion

这就是分页逻辑所需要的。然后可以将视图模型的属性和命令绑定到视图中的必要元素上。

<mah:MetroWindow x:Class="PagedData.WPF.MainWindow"
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
               xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
               xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
               xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
               xmlns:iconPack="http://metro.mahapps.com/winfx/xaml/iconpacks"
               xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors"
               DataContext="{Binding Source={StaticResource VmLocator}, Path=MainWindowVM}"
               WindowStartupLocation="CenterScreen"
               mc:Ignorable="d"
               Title="Paged Data"
               Height="420" Width="580">
  <behaviors:Interaction.Triggers>
      <behaviors:EventTrigger>
          <behaviors:InvokeCommandAction Command="{Binding LoadDataCommand}"/>
      </behaviors:EventTrigger>
  </behaviors:Interaction.Triggers>

  <Grid>
      <Grid.RowDefinitions>
          <RowDefinition Height="*"/>
          <RowDefinition Height="Auto"/>
      </Grid.RowDefinitions>

      <DataGrid AutoGenerateColumns="False"
                IsReadOnly="True"
                EnableColumnVirtualization="True"
                EnableRowVirtualization="True"
                ItemsSource="{Binding Employees}">
          <DataGrid.Columns>
              <DataGridTextColumn Header="ID"
                                  Binding="{Binding ID}"/>
              <DataGridTextColumn Header="First Name"
                                  Binding="{Binding FirstName}"/>
              <DataGridTextColumn Header="Last Name"
                                  Binding="{Binding LastName}"/>
              <DataGridTextColumn Header="Age"
                                  Binding="{Binding Age}"/>
              <DataGridTextColumn Header="Gender"
                                  Binding="{Binding Gender}"/>
          </DataGrid.Columns>
      </DataGrid>

      <StackPanel Grid.Row="1" Margin="0,10" Orientation="Horizontal"
                  HorizontalAlignment="Center">
          <Button Style="{StaticResource CustomButtonStyle}"
                  Command="{Binding FirstPageCommand}">
              <iconPack:PackIconMaterial Kind="SkipBackward"/>
          </Button>
          <RepeatButton Margin="12,0,0,0"
                        Style="{StaticResource CustomRepeatButtonStyle}"
                        Command="{Binding PreviousPageCommand}">
              <iconPack:PackIconMaterial Width="15" Height="15"
                                          Kind="SkipPrevious"/>
          </RepeatButton>
          <TextBlock Margin="8,0" VerticalAlignment="Center">
              <TextBlock.Text>
                  <MultiBinding StringFormat="Page {0} of {1}">
                      <Binding Path="CurrentPage" />
                      <Binding Path="TotalPages" />
                  </MultiBinding>
              </TextBlock.Text>
          </TextBlock>
          <RepeatButton Style="{StaticResource CustomRepeatButtonStyle}"
                        Command="{Binding NextPageCommand}">
              <iconPack:PackIconMaterial Width="15" Height="15"
                                          Kind="SkipNext"/>
          </RepeatButton>
          <Button Margin="12,0,0,0"
                  Style="{StaticResource CustomButtonStyle}"
                  Command="{Binding LastPageCommand}">
              <iconPack:PackIconMaterial Kind="SkipForward"/>
          </Button>
      </StackPanel>

      <TextBlock Grid.Row="1" Margin="0,0,15,0"
                  HorizontalAlignment="Right" VerticalAlignment="Center"
                  Text="{Binding TotalItems, StringFormat={}{0} items}"/>
  </Grid>
</mah:MetroWindow>

结论

希望您从本文中学到了有用的东西。如前所述,Dynamic Data具有大量的集合运算符,因此请看一下它们还可以做什么。您也可以从文章顶部的链接下载本文的示例项目。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值