Files
ShrlAlgoToolkit/NeoUI/NeoUI/Controls/PaginationControl.xaml.cs

316 lines
16 KiB
C#
Raw Normal View History

2025-08-20 12:10:35 +08:00
// 命名空间: NeoUI.Controls
// 文件名: PaginationControl.cs
using System.Collections.ObjectModel;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace NeoUI.Controls
{
/// <summary>
/// 代表分页项的数据模型
/// </summary>
public class PageItem
{
/// <summary>
/// 表示分页项的值。这个属性通常用于标识页面编号是PageItem类的一个重要属性。
/// </summary>
/// <remarks>
/// 该属性主要用于存储和表示页面的具体数值在PaginationControl中通过点击对应的页面按钮来更新当前页码时会用到此属性。
/// </remarks>
public int Value { get; set; }
/// <summary>
/// 表示分页项的文本内容。此属性允许设置和获取一个字符串值,该值用于显示在用户界面上作为页面项的标签。
/// </summary>
/// <remarks>
/// 该属性主要用于展示分页控件中每个页面项的具体文本,例如数字或特殊字符(如“<”、“>”。通过设置不同的Text值可以自定义分页控件的外观和行为。
/// </remarks>
public string Text { get; set; }
/// <summary>
/// 表示页面项的工具提示文本。此属性允许为每个PageItem对象设置一个描述性的工具提示当用户将鼠标悬停在该页码按钮上时显示。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以为分页控件中的每一个页面项提供额外的信息说明,从而增强用户体验。此属性通常用于指示特定操作或状态(如“上一页”、“下一页”)。
/// </remarks>
public string ToolTip { get; set; }
/// <summary>
/// 表示控件是否可以响应用户交互。此属性控制控件的启用或禁用状态。
/// </summary>
/// <remarks>
/// 当设置为 true 时,控件能够接收并响应用户的输入;若设置为 false则控件将被禁用且不响应任何用户操作。
/// </remarks>
public bool IsEnabled { get; set; } = true;
}
/// <summary>
/// 一个功能完整、用户友好、可高度定制的 WPF 分页控件。
/// </summary>
[TemplatePart(Name = "PART_PageInputTextBox", Type = typeof(TextBox))]
public class PaginationControl : Control
{
private TextBox? pageInputTextBox;
static PaginationControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(PaginationControl), new FrameworkPropertyMetadata(typeof(PaginationControl)));
}
/// <summary>
/// 分页控件,用于在应用程序中实现分页功能。该控件允许用户通过指定每页显示的项目数量、当前页码以及总项目数来浏览大量数据。
/// </summary>
/// <remarks>
/// 本控件支持绑定属性,如<see cref="CurrentPage"/>(当前页)、<see cref="PageSize"/>(每页大小)和<see cref="TotalItems"/>总项目数以便与MVVM模式良好集成。
/// 另外,它还提供了一个<see cref="PageItems"/>集合属性,用于内部管理页面项,并且有一个<see cref="PageSizeOptions"/>属性,用来设定可选的每页显示项目数选项。
/// </remarks>
public PaginationControl()
{
PageItems = new ObservableCollection<PageItem>();
}
#region Dependency Properties
// PageItems (只读,供模板绑定)
private static readonly DependencyPropertyKey PageItemsPropertyKey =
DependencyProperty.RegisterReadOnly(nameof(PageItems), typeof(ObservableCollection<PageItem>), typeof(PaginationControl),
new PropertyMetadata(new ObservableCollection<PageItem>()));
/// <summary>
/// 用于表示分页控件中所有页面项的集合属性。此属性提供了一个只读的依赖属性允许在XAML模板中绑定到一个ObservableCollection类型的对象。
/// </summary>
/// <remarks>
/// 该属性主要用于内部管理和数据绑定在外部不应直接修改其值。它为PaginationControl提供了显示当前可用页面项的基础。
/// </remarks>
public static readonly DependencyProperty PageItemsProperty = PageItemsPropertyKey.DependencyProperty;
/// <summary>
/// 表示分页控件中所有页面项的集合。此属性提供了一个只读的依赖属性允许在XAML模板中绑定到一个ObservableCollection类型的对象。
/// </summary>
/// <remarks>
/// 该属性主要用于内部管理和数据绑定在外部不应直接修改其值。它为PaginationControl提供了显示当前可用页面项的基础。
/// </remarks>
public ObservableCollection<PageItem> PageItems
{
get => (ObservableCollection<PageItem>)GetValue(PageItemsProperty);
private set => SetValue(PageItemsPropertyKey, value);
}
// TotalItems
/// <summary>
/// 用于表示分页控件中总项数的属性。此依赖属性允许在XAML模板中绑定到一个long类型的值表示数据源中的总记录数。
/// </summary>
/// <remarks>
/// 该属性主要用于内部管理和数据绑定在外部可以设置其值以更新分页控件的状态。它为PaginationControl提供了计算页数和显示当前页面的基础。
/// </remarks>
public static readonly DependencyProperty TotalItemsProperty =
DependencyProperty.Register(nameof(TotalItems), typeof(long), typeof(PaginationControl),
new PropertyMetadata(0L, OnPagingPropertyChanged));
/// <summary>
/// 用于表示分页控件中总项目数的属性。此属性提供了一个依赖属性允许在XAML或代码中设置和获取当前分页控件所处理的项目总数。
/// </summary>
/// <remarks>
/// 该属性是PaginationControl的重要组成部分它决定了分页逻辑中的总页数以及如何显示页面项。当TotalItems发生变化时会触发相关事件以更新UI上的分页信息。请确保设置合理的数值以反映实际的数据量大小。
/// </remarks>
public long TotalItems
{
get => (long)GetValue(TotalItemsProperty); set => SetValue(TotalItemsProperty, value); }
/// <summary>
/// 用于表示分页控件中每页显示的项目数量的属性。此属性提供了一个双向绑定的依赖属性允许用户在XAML或代码中设置和获取每页显示的项目数。
/// </summary>
/// <remarks>
/// 该属性值必须大于0如果设置小于1的值则会自动调整为1。PageSizeProperty 是 PaginationControl 的核心属性之一,它直接影响到分页控件的行为和显示效果。
/// </remarks>
public static readonly DependencyProperty PageSizeProperty =
DependencyProperty.Register(nameof(PageSize), typeof(int), typeof(PaginationControl),
new FrameworkPropertyMetadata(10, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPagingPropertyChanged, CoercePageSize));
/// <summary>
/// 用于设置或获取每页显示的项目数量。此属性定义了一个双向绑定的依赖属性允许用户通过UI控件如ComboBox选择不同的页面大小。
/// </summary>
/// <remarks>
/// 修改此属性将影响分页控件中显示的总页数及当前页码的有效性。它是一个关键属性决定了PaginationControl如何分割和展示数据项。
/// </remarks>
public int PageSize
{
get => (int)GetValue(PageSizeProperty); set => SetValue(PageSizeProperty, value); }
private static object CoercePageSize(DependencyObject d, object baseValue) => (int)baseValue < 1 ? 1 : baseValue;
// CurrentPage
/// <summary>
/// 用于表示分页控件中当前选中的页面编号的属性。此依赖属性支持双向数据绑定允许在XAML模板或代码中设置和获取当前页码。
/// </summary>
/// <remarks>
/// 该属性主要用于控制分页控件显示的数据范围通过更改其值可以切换到不同的页面。它与PageSize和TotalItems属性共同作用以确定分页控件的行为。
/// </remarks>
public static readonly DependencyProperty CurrentPageProperty =
DependencyProperty.Register(nameof(CurrentPage), typeof(int), typeof(PaginationControl),
new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPagingPropertyChanged, CoerceCurrentPage));
/// <summary>
/// 用于表示分页控件中当前选中的页面编号。此属性是一个双向绑定的依赖属性默认值为1允许通过数据绑定或直接设置来更改当前显示的页面。
/// </summary>
/// <remarks>
/// 当用户点击分页按钮或输入新的页码时此属性会被更新并触发相关事件以刷新UI。请注意如果尝试设置超出有效范围即小于1或大于总页数的值该属性会自动调整到最近的有效值。
/// </remarks>
public int CurrentPage
{
get => (int)GetValue(CurrentPageProperty); set => SetValue(CurrentPageProperty, value); }
private static object CoerceCurrentPage(DependencyObject d, object baseValue)
{
var control = (PaginationControl)d;
var page = (int)baseValue;
if (page < 1) return 1;
var pageCount = (int)Math.Ceiling((double)control.TotalItems / control.PageSize);
if (pageCount > 0 && page > pageCount) return pageCount;
return page;
}
// PageSizeOptions
/// <summary>
/// 用于表示分页控件中可选的每页显示项数的集合属性。此属性提供了一个依赖属性允许在XAML模板中绑定到一个ObservableCollection类型的对象。
/// </summary>
/// <remarks>
/// 该属性主要用于设置和获取用户可以选择的不同每页显示项数选项。默认值包含一些常见的选项如10、20、50和100。可以通过修改此属性来提供自定义的每页显示项数选项。
/// </remarks>
public static readonly DependencyProperty PageSizeOptionsProperty =
DependencyProperty.Register(nameof(PageSizeOptions), typeof(ObservableCollection<int>), typeof(PaginationControl),
new PropertyMetadata(new ObservableCollection<int> { 10, 20, 50, 100 }));
/// <summary>
/// 表示分页控件中可选的每页显示项数选项集合。此属性是一个依赖属性允许用户通过绑定到一个ObservableCollection类型的对象来设置和获取可用的每页显示数量选项。
/// </summary>
/// <remarks>
/// 该属性主要用于提供给用户一个选择每页显示多少条记录的下拉列表选项。默认情况下它包含四个选项10, 20, 50 和 100。这些选项可通过XAML或代码进行自定义以适应不同的应用场景需求。
/// </remarks>
public ObservableCollection<int> PageSizeOptions { get => (ObservableCollection<int>)GetValue(PageSizeOptionsProperty); set => SetValue(PageSizeOptionsProperty, value); }
// ShowPageInput
/// <summary>
/// 用于控制是否显示页面输入框的属性。此属性允许用户通过设置布尔值来决定分页控件中是否显示一个可以手动输入页码的文本框。
/// </summary>
/// <remarks>
/// 当该属性为 true 时,分页控件会显示一个可以让用户直接输入页码的文本框;当为 false 时,则不会显示该文本框。这为用户提供了一种快速跳转到特定页面的方式。
/// </remarks>
public static readonly DependencyProperty ShowPageInputProperty =
DependencyProperty.Register(nameof(ShowPageInput), typeof(bool), typeof(PaginationControl), new PropertyMetadata(false));
/// <summary>
/// 用于控制是否显示页面输入框的属性。此依赖属性允许用户通过设置布尔值来决定是否在分页控件中展示一个输入框,以便直接输入目标页码。
/// </summary>
/// <remarks>
/// 当设置为true时分页控件将显示一个输入框用户可以直接输入想要跳转到的页码若设置为false则不显示该输入框。默认情况下此属性值为false。
/// </remarks>
public bool ShowPageInput
{
get => (bool)GetValue(ShowPageInputProperty); set => SetValue(ShowPageInputProperty, value); }
#endregion
#region Centralized Event Handling
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (pageInputTextBox != null) pageInputTextBox.KeyDown -= OnPageInputKeyDown;
pageInputTextBox = GetTemplateChild("PART_PageInputTextBox") as TextBox;
if (pageInputTextBox != null) pageInputTextBox.KeyDown += OnPageInputKeyDown;
this.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(OnPageButtonClick));
}
private void OnPageButtonClick(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is FrameworkElement button && button.DataContext is PageItem pageItem)
{
if (pageItem.IsEnabled && pageItem.Value > 0)
{
this.CurrentPage = pageItem.Value;
}
}
}
private void OnPageInputKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && sender is TextBox textBox)
{
if (int.TryParse(textBox.Text, out var page))
{
CurrentPage = page;
}
textBox.Text = CurrentPage.ToString(); // Reset to valid value
}
}
#endregion
#region Update Logic
private static void OnPagingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (PaginationControl)d;
control.UpdatePageItems();
}
private void UpdatePageItems()
{
PageItems.Clear();
if (PageSize <= 0 || TotalItems <= 0) return;
var pageCount = (int)Math.Ceiling((double)TotalItems / PageSize);
var currentPage = CurrentPage;
if (currentPage > pageCount) currentPage = pageCount;
if (currentPage < 1) currentPage = 1;
if (CurrentPage != currentPage)
{
CurrentPage = currentPage;
return; // 更改 CurrentPage 会自动再次触发此方法,直接返回即可
}
const int jumpSize = 5; // 定义快速跳转的步长
// 上一页按钮
PageItems.Add(new PageItem { Text = "<", Value = currentPage - 1, ToolTip = "上一页", IsEnabled = currentPage > 1 });
// 第 1 页
PageItems.Add(new PageItem { Text = "1", Value = 1 });
// 前一个 "..." 按钮
if (currentPage > 4)
{
PageItems.Add(new PageItem { Text = "...", Value = Math.Max(1, currentPage - jumpSize), ToolTip = $"向前 {jumpSize} 页" });
}
// 中间的页码
var start = Math.Max(2, currentPage - 2);
var end = Math.Min(pageCount - 1, currentPage + 2);
for (var i = start; i <= end; i++)
{
PageItems.Add(new PageItem { Text = i.ToString(), Value = i });
}
// 后一个 "..." 按钮
if (currentPage < pageCount - 3)
{
PageItems.Add(new PageItem { Text = "...", Value = Math.Min(pageCount, currentPage + jumpSize), ToolTip = $"向后 {jumpSize} 页" });
}
// 最后一页 (如果总页数大于1)
if (pageCount > 1)
{
PageItems.Add(new PageItem { Text = pageCount.ToString(), Value = pageCount });
}
// 下一页按钮
PageItems.Add(new PageItem { Text = ">", Value = currentPage + 1, ToolTip = "下一页", IsEnabled = currentPage < pageCount });
}
#endregion
}
}