背景

最近要求项目组成员开发一个通用的分页组件,要求是这个组件简单易用,通用性,兼容现有框架MVVM模式,可是最后给我提交的成果勉强能够用,却欠少灵活性和框架兼容性。

设计的基本思想

传入数据源,总页数,当前页码,每页记录数,达到分页显示数据的功能。

优化

我把原本不支持MVVM的源码改善了一下,可能还可以再优化得好些,支持MVVM模式,较果如下图:

image

添加一解决方案:TLAgent.Pager

image

  • 设计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

image

  • 自定义一个命令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; }
    }
}

       到此一个分页控件设计就完成了,在此做一些总结。

      源码

posted on 2013-09-08 23:18  aganqin  阅读(7110)  评论(9编辑  收藏  举报