使用动态数据进行数据分页
作者:互联网
目录
介绍
如果您有一个大的数据集合,那么用集合中的所有数据填充项目控件就变得不切实际,更不用说用户不友好了。最好的方法是对数据进行分段,因此项目控件仅显示数据的子集,并允许用户在数据段之间循环。可以使用动态数据库在.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