背景
最近要求项目组成员开发一个通用的分页组件,要求是这个组件简单易用,通用性,兼容现有框架MVVM模式,可是最后给我提交的成果勉强能够用,却欠少灵活性和框架兼容性。
设计的基本思想
传入数据源,总页数,当前页码,每页记录数,达到分页显示数据的功能。
优化
我把原本不支持MVVM的源码改善了一下,可能还可以再优化得好些,支持MVVM模式,较果如下图:
添加一解决方案:TLAgent.Pager
- 设计DataPager类,继承UserControl, INotifyPropertyChanged ,参考如下代码:
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.ComponentModel; namespace TLAgent.Pager { /// <summary> /// DataPager.xaml 的交互逻辑 /// </summary> public partial class DataPager : UserControl, INotifyPropertyChanged { public DataPager() { InitializeComponent(); } #region 依赖属性和事件 public int PageSize { get { return (int)GetValue(PageSizeProperty); } set { SetValue(PageSizeProperty, value); } } // Using a DependencyProperty as the backing store for PageSize. This enables animation, styling, binding, etc... public static readonly DependencyProperty PageSizeProperty = DependencyProperty.Register("PageSize", typeof(int), typeof(DataPager), new UIPropertyMetadata(10)); public int Total { get { return (int)GetValue(TotalProperty); } set { SetValue(TotalProperty, value); } } // Using a DependencyProperty as the backing store for Total. This enables animation, styling, binding, etc... public static readonly DependencyProperty TotalProperty = DependencyProperty.Register("Total", typeof(int), typeof(DataPager), new UIPropertyMetadata(0)); public int PageIndex { get { return (int)GetValue(PageIndexProperty); } set { SetValue(PageIndexProperty, value); } } // Using a DependencyProperty as the backing store for PageIndex. This enables animation, styling, binding, etc... public static readonly DependencyProperty PageIndexProperty = DependencyProperty.Register("PageIndex", typeof(int), typeof(DataPager), new UIPropertyMetadata(1)); public string PageSizeList { get { return (string)GetValue(PageSizeListProperty); } set { SetValue(PageSizeListProperty, value); } } // Using a DependencyProperty as the backing store for PageSizeList. This enables animation, styling, binding, etc... public static readonly DependencyProperty PageSizeListProperty = DependencyProperty.Register("PageSizeList", typeof(string), typeof(DataPager), new UIPropertyMetadata("5,10,20", (s, e) => { DataPager dp = s as DataPager; if (dp.PageSizeItems == null) dp.PageSizeItems = new List<int>(); else dp.PageSizeItems.Clear(); dp.RaisePropertyChanged("PageSizeItems"); })); public IEnumerable<object> ItemsSource { get { return (IEnumerable<object>)GetValue(ItemsSourceProperty); } set { SetValue(ItemsSourceProperty, value); } } /// <summary> /// ItemsSource数据源 /// </summary> public static DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable<object>), typeof(DataPager), new UIPropertyMetadata(null)); public static readonly RoutedEvent PageChangedEvent = EventManager.RegisterRoutedEvent("PageChanged", RoutingStrategy.Bubble, typeof(PageChangedEventHandler), typeof(DataPager)); /// <summary> /// 分页更改事件 /// </summary> public event PageChangedEventHandler PageChanged { add { AddHandler(PageChangedEvent, value); } remove { RemoveHandler(PageChangedEvent, value); } } #endregion public ICommand PageChangedCommand { get; set; } #region 通知属性 private List<int> _pageSizeItems; /// <summary> /// 显示每页记录数集合 /// </summary> public List<int> PageSizeItems { get { if (_pageSizeItems == null) { _pageSizeItems = new List<int>(); } if (PageSizeList != null) { List<string> strs = PageSizeList.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); _pageSizeItems.Clear(); strs.ForEach(c => { _pageSizeItems.Add(Convert.ToInt32(c)); }); } return _pageSizeItems; } set { if (_pageSizeItems != value) { _pageSizeItems = value; RaisePropertyChanged("PageSizeItems"); } } } private int _pageCount; /// <summary> /// 总页数 /// </summary> public int PageCount { get { return _pageCount; } set { if (_pageCount != value) { _pageCount = value; RaisePropertyChanged("PageCount"); } } } private int _start; /// <summary> /// 开始记录数 /// </summary> public int Start { get { return _start; } set { if (_start != value) { _start = value; RaisePropertyChanged("Start"); } } } private int _end; /// <summary> /// 结束记录数 /// </summary> public int End { get { return _end; } set { if (_end != value) { _end = value; RaisePropertyChanged("End"); } } } public event PropertyChangedEventHandler PropertyChanged; public void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } #endregion #region 字段、属性、委托 public delegate void PageChangedEventHandler(object sender, PageChangedEventArgs args); private PageChangedEventArgs pageChangedEventArgs; #endregion #region 引发分页更改事件 /// <summary> /// 引发分页更改事件 /// </summary> private void RaisePageChanged() { if (pageChangedEventArgs == null) { pageChangedEventArgs = new PageChangedEventArgs(PageChangedEvent, PageSize, PageIndex); } else { pageChangedEventArgs.PageSize = this.PageSize; pageChangedEventArgs.PageIndex = this.PageIndex; } RaiseEvent(pageChangedEventArgs); //calc start、end if (ItemsSource != null) { int curCount = ItemsSource.Count(); Start = (PageIndex - 1) * PageSize + 1; End = Start + curCount - 1; if (Total % PageSize != 0) { PageCount = Total / PageSize + 1; } else { PageCount = Total / PageSize; } } else { Start = End = PageCount = Total = 0; } //调整图片的显示 btnFirst.IsEnabled = btnPrev.IsEnabled = (PageIndex != 1); btnNext.IsEnabled = btnLast.IsEnabled = (PageIndex != PageCount); } #endregion #region 分页操作事件 void DataPager_Loaded(object sender, RoutedEventArgs e) { RaisePageChanged(); } private void cbpPageSize_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (this.IsLoaded) { PageSize = (int)cboPageSize.SelectedItem; RaisePageChanged(); } } private void btnFirst_Click(object sender, RoutedEventArgs e) { PageIndex = 1; RaisePageChanged(); } private void btnPrev_Click(object sender, RoutedEventArgs e) { if (PageIndex > 1) { --PageIndex; } RaisePageChanged(); } private void btnNext_Click(object sender, RoutedEventArgs e) { if (Total % PageSize != 0) { PageCount = Total / PageSize + 1; } else { PageCount = Total / PageSize; } if (PageIndex < PageCount) { ++PageIndex; } RaisePageChanged(); } private void btnLast_Click(object sender, RoutedEventArgs e) { if (Total % PageSize != 0) { PageCount = Total / PageSize + 1; } else { PageCount = Total / PageSize; } PageIndex = PageCount; RaisePageChanged(); } private void btnRefresh_Click(object sender, RoutedEventArgs e) { RaisePageChanged(); } private void tbPageIndex_PreviewKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) { tbPageIndex_LostFocus(sender, null); } } private void tbPageIndex_LostFocus(object sender, RoutedEventArgs e) { int pIndex = 0; try { pIndex = Convert.ToInt32(tbPageIndex.Text); } catch { pIndex = 1; } if (pIndex < 1) PageIndex = 1; else if (pIndex > PageCount) PageIndex = PageCount; else PageIndex = pIndex; RaisePageChanged(); } #endregion } /// <summary> /// 分页更改参数 /// </summary> public class PageChangedEventArgs : RoutedEventArgs { public int PageSize { get; set; } public int PageIndex { get; set; } public PageChangedEventArgs(RoutedEvent routeEvent, int pageSize, int pageIndex) : base(routeEvent) { this.PageSize = pageSize; this.PageIndex = pageIndex; } } }
- 添加一支持泛型类,可以传入任何类型对象
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; namespace TLAgent.Pager { public class DataResult<T> : INotifyPropertyChanged { public int _total; public int Total { get { return _total; } set { if (_total != value) { _total = value; RaisePropertyChanged("Total"); } } } private List<T> _dataSource; public List<T> DataSource { get { return _dataSource; } set { if (_dataSource != value) { _dataSource = value; RaisePropertyChanged("DataSource"); } } } public DataResult() { DataSource = new List<T>(); } public void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public event PropertyChangedEventHandler PropertyChanged; } }
测试
添加一个测试项目:TLAgent.Pager.Test
- 自定义一个命令InteractiveCommand,支持传入参数,代码参考:
// ----------------------------------------------------------------------- // <copyright file="InteractiveCommand.cs" company="M&M"> // TODO: Update copyright text. // </copyright> // ----------------------------------------------------------------------- namespace TLAgent.Pager.Test { using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Input; using System.Windows.Interactivity; using System.Reflection; /// <summary> /// TODO: Update summary. /// </summary> public class InteractiveCommand : TriggerAction<DependencyObject> { protected override void Invoke(object parameter) { if (base.AssociatedObject != null) { ICommand command = this.ResolveCommand(); object[] tempObj = { parameter, CommandParameter }; if ((command != null) && command.CanExecute(tempObj)) { command.Execute(tempObj); } } } public object CommandParameter { get { return GetValue(CommandParameterProperty); } set { SetValue(CommandParameterProperty, value); } } public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register ("CommandParameter", typeof(object), typeof(InteractiveCommand), new PropertyMetadata(null, OnCommandParameterChanged)); private static void OnCommandParameterChanged (DependencyObject sender, DependencyPropertyChangedEventArgs e) { InteractiveCommand ic = sender as InteractiveCommand; if (ic != null) { ic.SynchronizeElementState(); } } private void SynchronizeElementState() { ICommand command = Command; if (command != null) { FrameworkElement associatedObject = AssociatedObject as FrameworkElement; if (associatedObject != null) { associatedObject.IsEnabled = command.CanExecute(CommandParameter); } } } private ICommand ResolveCommand() { ICommand command = null; if (this.Command != null) { return this.Command; } if (base.AssociatedObject != null) { foreach (PropertyInfo info in base.AssociatedObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (typeof(ICommand).IsAssignableFrom(info.PropertyType) && string.Equals(info.Name, this.CommandName, StringComparison.Ordinal)) { command = (ICommand)info.GetValue(base.AssociatedObject, null); } } } return command; } private string commandName; public string CommandName { get { base.ReadPreamble(); return this.commandName; } set { if (this.CommandName != value) { base.WritePreamble(); this.commandName = value; base.WritePostscript(); } } } #region Command public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } } public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(InteractiveCommand), new UIPropertyMetadata(null)); #endregion } }
在MainWindow.xaml中,添一DataGrid和一个DataPager,参考如下代码:
<Window x:Class="TLAgent.Pager.Test.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:lib="clr-namespace:TLAgent.Pager;assembly=TLAgent.Pager" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:cmd="clr-namespace:TLAgent.Pager.Test" xmlns:viewModels="clr-namespace:TLAgent.Pager.Test.ViewModels" Name="self" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <viewModels:MainWindowViewModel/> </Window.DataContext> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <DataGrid ItemsSource="{Binding Path=ItemsSource,ElementName=dataPager}"> <!--<DataGrid.Columns> <DataGridTextColumn Header="Name" Binding="{Binding Name}" /> <DataGridTextColumn Header="Age" Binding="{Binding Age}" /> <DataGridTextColumn Header="Gender" Binding="{Binding Gender}" /> </DataGrid.Columns>--> </DataGrid> <lib:DataPager Grid.Row="1" Name="dataPager" PageSizeList="10,20,30,50" ItemsSource="{Binding Result.DataSource,Mode=TwoWay}" Total="{Binding Result.Total,Mode=TwoWay}" > <i:Interaction.Triggers> <i:EventTrigger EventName="PageChanged"> <cmd:InteractiveCommand Command="{Binding PageChangedCommand}" CommandName="PageChangedCommand"/> </i:EventTrigger> </i:Interaction.Triggers> </lib:DataPager> </Grid> </Window>添加一个ViewModel:MainWindowViewModel,继承NotificationObject:
using System.Collections.Generic; using System.Linq; using System.Windows.Input; using Microsoft.Practices.Prism.Commands; using Microsoft.Practices.Prism.ViewModel; namespace TLAgent.Pager.Test.ViewModels { public class MainWindowViewModel : NotificationObject { private DelegateCommand<object[]> _commandWithEventArgs; public List<Student> _dataSource;//声明数据 public MainWindowViewModel() { Result = new DataResult<Student>();//此处Student可以传入任何数据类型 _dataSource = Controller.GetData();//获取得数据 Result.DataSource = _dataSource;//给数据源赋值 Query(10, 1); } private DataResult<Student> _result; public DataResult<Student> Result//公开给View作数据源绑定 { get { return _result; } set { _result = value; RaisePropertyChanged("Result"); } } public void Query(int size, int pageIndex) { Result.Total = _dataSource.Count;//给页总数赋值 Result.DataSource = _dataSource.Skip((pageIndex - 1) * size).Take(size).ToList();//改变数据源赋值 } /// <summary> /// /// </summary> public ICommand PageChangedCommand { get { return _commandWithEventArgs ?? (_commandWithEventArgs = new DelegateCommand<object[]>(ShowData)); } } private void ShowData(object[] objParam) { PageChangedEventArgs args = (PageChangedEventArgs)objParam[0];//View把PageChangedEventArgs事件传过来,此事件带来页码和页序号 Query(args.PageSize, args.PageIndex); } } }
添加一个Controller类,从此类中获取测试数据来源:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TLAgent.Pager.Test { public class Controller { public static List<Student> GetData() { List<Student> Students; Students = new List<Student>(); Random random = new Random(); int count = random.Next(20, 200); for (int i = 1; i <= count; i++) { Student stu = new Student { Name = "Name" + i, Age = random.Next(20, 50), Gender = (i % 3 != 0), Desc = "Desc " + i, }; Students.Add(stu); } return Students; } } }
添加一个Student类,只是作为测试用,项目中可以按实际需要,DataResult<T>是支持多种类型的数据。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Practices.Prism.ViewModel; namespace TLAgent.Pager.Test { public class Student : NotificationObject { public string Name { get; set; } public int Age { get; set; } public bool Gender { get; set; } public string Desc { get; set; } } }
到此一个分页控件设计就完成了,在此做一些总结。