其他分享
首页 > 其他分享> > 使用动态数据进行数据分页

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

作者:互联网

目录

介绍

动态数据

数据分页

填充数据源

页面切换

结论


 

介绍

如果您有一个大的数据集合,那么用集合中的所有数据填充项目控件就变得不切实际,更不用说用户不友好了。最好的方法是对数据进行分段,因此项目控件仅显示数据的子集,并允许用户在数据段之间循环。可以使用动态数据库在.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()方法将数据添加到可观察的缓存中。该方法有两个重载:一个重载单个对象,另一个重载对象集合。使用Bogus将1500个employee对象添加到可观察的集合中,该集合会生成20到60岁之间的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具有大量的集合运算符,因此请看一下它们还可以做什么。您也可以从文章顶部的链接下载本文的示例项目。

标签:分页,RelayCommand,PAGE,private,集合,new,动态数据,数据,public
来源: https://blog.csdn.net/mzl87/article/details/110439361