本文主要用于介绍WPF基于MVVM实现自定义分页控件的代码实现。
主要功能: 自定义页码,当前页/总页数, 上一页,下一页,返回首页,返回最后一页, 利用FontAwesome作为FontFamily实现icon的功能。
本代码中的ViewModelBase的实现请参照另一篇文章:WPF MVVM ViewModelBase(造轮子实现类似MVVMLight的功能)
1. 创建PaginationControl
1.1 Color Brush
xaml
<SolidColorBrush x:Key="HighlightBrush" Color="#E84315" />
<SolidColorBrush x:Key="DisabledForegroundBrush" Color="#888" />
1.2 PaginationControl.xaml.cs
不用更改任何代码
1.3 PaginationControl.xaml
xaml
<UserControl x:Class="MES.UI.Views.CustomControls.PaginationControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:MES.UI.Views.CustomControls"
mc:Ignorable="d" Height="45">
<UserControl.Resources>
<Style x:Key="paginationTB" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontFamily" Value="pack://application:,,,/Resources/#FontAwesome"/>
<Setter Property="Margin" Value="5,15,5,10"/>
<Setter Property="Foreground" Value="{StaticResource HighlightBrush}"/>
<Setter Property="Cursor" Value="Hand"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Foreground" Value="{StaticResource HighlightBrush}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="Page Size:" Margin="5,15,5,10"></TextBlock>
<ComboBox x:Name="PageSizeComboBox"
ItemsSource="{Binding PageSizes}"
SelectedItem="{Binding PageSize}"
Width="70"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
<TextBlock Style="{StaticResource paginationTB}" IsEnabled="{Binding EnabledPreviousButton}" Text="" x:Name="btnFirstPage" ToolTip="First Page">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding FirstPageCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<TextBlock Style="{StaticResource paginationTB}" IsEnabled="{Binding EnabledPreviousButton}" Text="" x:Name="btnPrevious" ToolTip="Previouse">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding PreviousPageCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<TextBlock Name="txtCurrentPageTextBlock" Text="{Binding CurrentPageText}" Margin="5,15,5,10" VerticalAlignment="Center" />
<TextBlock Style="{StaticResource paginationTB}" IsEnabled="{Binding EnabledNextButton}" Text="" x:Name="btnNext" ToolTip="Next" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding NextPageCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<TextBlock Style="{StaticResource paginationTB}" IsEnabled="{Binding EnabledNextButton}" Text="" x:Name="btnLastPage" ToolTip="Last Page" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding LastPageCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</StackPanel>
</UserControl>
2. 创建PaginationControlViewModel
csharp
/// <summary>
/// Pagination Control ViewModel
/// </summary>
public class PaginationControlViewModel : ViewModelBase
{
#region Pagination paramters
private int _currentPage;
private int _totalItems;
private int _pageSize = 1;
public int TotalItems
{
get => _totalItems;
set
{
_totalItems = value;
OnPropertyChanged(nameof(TotalItems));
OnPropertyChanged(nameof(TotalPages));
OnPropertyChanged(nameof(CurrentPageText));
SetButtonStatus();
}
}
public int PageSize
{
get => _pageSize;
set
{
_pageSize = value;
OnPropertyChanged(nameof(PageSize));
OnPageChanged();
}
}
public int CurrentPage
{
get => _currentPage;
set
{
if (_currentPage != value)
{
_currentPage = value;
OnPropertyChanged(nameof(CurrentPage));
OnPageChanged();
}
}
}
public string CurrentPageText
{
get
{
return $"Page {CurrentPage} of {TotalPages}";
}
}
public int TotalPages => (TotalItems + PageSize - 1) / PageSize;
private ObservableCollection<int> _pageSizes;
public ObservableCollection<int> PageSizes
{
get { return _pageSizes; }
set
{
_pageSizes = value;
OnPropertyChanged(nameof(PageSizes));
}
}
private bool _enabledPreviousButton;
public bool EnabledPreviousButton
{
get { return _enabledPreviousButton; }
set { _enabledPreviousButton = value; OnPropertyChanged(nameof(EnabledPreviousButton)); }
}
private bool _enabledNextButton;
public bool EnabledNextButton
{
get { return _enabledNextButton; }
set { _enabledNextButton = value; OnPropertyChanged(nameof(EnabledNextButton)); }
}
#endregion
public ICommand FirstPageCommand { get; }
public ICommand PreviousPageCommand { get; }
public ICommand NextPageCommand { get; }
public ICommand LastPageCommand { get; }
public event EventHandler PageChanged;
private bool isInitialized = false;// avoid call OnPageChanged in initial
public PaginationControlViewModel()
{
_pageSizes = new ObservableCollection<int> { 1, 20, 50, 100, 200, 300, 500 };
CurrentPage = 1; // Initialize to the first page
PageSize = AgencyCRMConstants.DefaultPageSize;
FirstPageCommand = new RelayCommand(_ => CurrentPage = 1);
PreviousPageCommand = new RelayCommand(_ => { if (CurrentPage > 1) CurrentPage--; });
NextPageCommand = new RelayCommand(_ => { if (CurrentPage < TotalPages) CurrentPage++; });
LastPageCommand = new RelayCommand(_ => CurrentPage = TotalPages);
isInitialized = true;
}
protected virtual void OnPageChanged()
{
if (isInitialized)
{
PageChanged?.Invoke(this, EventArgs.Empty);
}
}
private void SetButtonStatus()
{
if (isInitialized)
{
if (CurrentPage > 1)
{
EnabledPreviousButton = true;
}
else
{
EnabledPreviousButton = false;
}
if (CurrentPage >= 1 && CurrentPage < TotalPages)
{
EnabledNextButton = true;
}
else
{
EnabledNextButton = false;
}
}
else
{
EnabledNextButton = false;
EnabledPreviousButton = false;
}
}
}
3. 调用
3.1 在CompanyListViewModel中引用PaginationControlViewModel
csharp
public class CompanyListViewModel : ViewModelBase, IViewModelDispose
{
#region Agency.CRM API Urls
private const string GetByCompanyNameAsync = "api/Company/GetByCompanyName?CompanyName={0}&pageNumber={1}&pageSize={2}";
#endregion
#region Models
private readonly IMESHttpClientService _mesHttpClientService;
private BackgroundWorker _backgroundWorker;
public PaginationControlViewModel PaginationViewModel;
private string? _CompanyName;
public string? CompanyName
{
get { return _CompanyName; }
set
{
_CompanyName = value;
OnPropertyChanged(nameof(CompanyName));
}
}
private bool _isEnableExport;
public bool IsEnableExport
{
get { return _isEnableExport; }
set
{
_isEnableExport = value;
OnPropertyChanged(nameof(IsEnableExport));
}
}
private CompanyDto? _selectedCompany;
public CompanyDto? SelectedCompany
{
get { return _selectedCompany; }
set
{
_selectedCompany = value;
OnPropertyChanged(nameof(SelectedCompany));
}
}
private ObservableCollection<CompanyDto> _CompanyList;
public ObservableCollection<CompanyDto> CompanyList
{
get { return _CompanyList; }
set
{
_CompanyList = value;
OnPropertyChanged(nameof(CompanyList));
}
}
private int pageSize = MESConstants.DefaultPageSize;
private int currentPage = 1;
#endregion
#region ctor
public CompanyListViewModel()
{
_mesHttpClientService = NInjectBase.Kernel.Get<IMESHttpClientService>();
_CompanyList = new();
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.DoWork += BackgroundWorker_DoWork;
_backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
InitialParameters();
}
private void InitialParameters()
{
_CompanyName = string.Empty;
PaginationViewModel = new PaginationControlViewModel();
PaginationViewModel.PageSize = MESConstants.DefaultPageSize;
_selectedCompany = new();
PaginationInitial();
}
#endregion
#region ICommands
public ICommand? RefreshRecordsCommand
{
get
{
return new DelegateCommand(() =>
{
RefreshRecords();
});
}
}
public ICommand? ResetFilterCommand
{
get
{
return new DelegateCommand(() =>
{
ResetFilterFunction();
});
}
}
#endregion
#region Functions
private void ResetFilterFunction()
{
InitialParameters();
RefreshRecords();
}
public void RefreshRecords()
{
AppEvents.Instance.StartProgressBar(this, new ProgressBarDisplayEventArgs(AppConstants.ProgressBarMessages.Generic.LOADING));
_backgroundWorker.RunWorkerAsync(new List<string>() { CompanyName, currentPage.ToString(), pageSize.ToString() });
}
private void BackgroundWorker_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
{
if (e.Error == null)
{
var result = e.Result as PagedResultDto<CompanyDto>;
if (result != null)
{
int.TryParse(result.TotalCount.ToString(), out var totalCount);
PaginationViewModel.TotalItems = totalCount;
CompanyList = result.Items;
this.IsEnableExport = CompanyList?.Count > 0;
}
else
{
this.IsEnableExport = false;
}
}
AppEvents.Instance.StopProgressBar(this);
}
private void BackgroundWorker_DoWork(object? sender, DoWorkEventArgs e)
{
try
{
var parameters = e.Argument as List<string>;
if (parameters == null || parameters.Count != 3) return;
using var httpClient = _mesHttpClientService.GetHttpClient();
string urlWithParameters = string.Format(GetByCompanyNameAsync, parameters[0], parameters[1], parameters[2]);
var response = httpClient.GetAsync(urlWithParameters).Result;
if (response.IsSuccessStatusCode)
{
var json = response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<PagedResultDto<CompanyDto>>(json);
e.Result = result;
}
}
catch (Exception ex)
{
Log.Error("Error loading CRM users", ex);
}
}
public void DisposeRecordsAndEvents()
{
CompanyList = new();
_backgroundWorker.DoWork -= BackgroundWorker_DoWork;
_backgroundWorker.RunWorkerCompleted -= BackgroundWorker_RunWorkerCompleted;
_backgroundWorker.Dispose();
PaginationDispose();
}
#endregion
#region Pagination
private void PaginationInitial()
{
PaginationViewModel = new();
PaginationViewModel.PageChanged += PaginationControlViewModel_PageChanged;
}
private void PaginationDispose()
{
PaginationViewModel.PageChanged -= PaginationControlViewModel_PageChanged;
}
private void PaginationControlViewModel_PageChanged(object? sender, EventArgs e)
{
pageSize = PaginationViewModel.PageSize;
currentPage = PaginationViewModel.CurrentPage;
RefreshRecords();
}
#endregion
}
3.2 CompanyList.xaml中添加PaginationControl
xaml
<CustomControls:PaginationControl x:Name="paginationControl"></CustomControls:PaginationControl>
3.3 CompanyList.xaml.cs中为PaginationControl指定DataContext
csharp
private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
paginationControl.DataContext = ViewModel.PaginationViewModel;
ViewModel.RefreshRecords();
}
4. 最终实现效果
