其实这篇跟MyOrm的关系并不太大,只是分页时使用了MyOrm的查询,不过Winform中分页也是比较实用的,也作为一个例子吧。
不知道为什么MS并没有考虑在Winform中提供分页的功能,虽然Winform不像Webform需要考虑减少数据量的交互,但是在数据量很大时或者网络条件不好时还是需要分页的。
在Winform中实现分页的麻烦主要在于MS没有给分页定义事件或设置,所以想要做到Webform那样易用比较困难。实现分页的一个简单的方法是,写一个控制页码的用户控件,在控件触发的页码更改事件里绑定DataSource。这样的缺点在于,实际使用的时候还是需要手动写一些代码,包括获取总记录数、分页查询。另外,Winform里有多种需要绑定数据的控件(虽然最常用的就是DataGridView),还有第三方控件或者自定义的控件,通用性不够好。
为了使用的方便,自己定义一个数据源的类比较好,继承BindingSource是个不错的选择。BindingSource具备了基本的功能,我们只需要再增加分页功能。
定义PagedBindingSource:
- public class PagedBindingSource : BindingSource, INotifyPropertyChanged
- {
- protected const int DefaultPageSize = 20;
- private bool _autoRefresh = true;
- private int _totalCount;
- private int _startIndex;
- private int _pageSize = DefaultPageSize;
- //private PropertyDescriptor _sortProperty;
- //private ListSortDirection _sortDirection;
- //public override bool SupportsSorting
- //{
- // get { return true; }
- //}
- //public override void ApplySort(PropertyDescriptor property, ListSortDirection sort)
- //{
- // if (property == _sortProperty) sort = _sortDirection ^ ListSortDirection.Descending;
- // _sortProperty = property;
- // _sortDirection = sort;
- // RefreshCurrentPage();
- //}
- //public override void RemoveSort()
- //{
- // _sortProperty = null;
- // RefreshCurrentPage();
- //}
- //public override bool IsSorted
- //{
- // get { return _sortProperty != null; }
- //}
- //public override PropertyDescriptor SortProperty
- //{
- // get { return _sortProperty; }
- //}
- //public override ListSortDirection SortDirection
- //{
- // get { return _sortDirection; }
- //}
- protected virtual object GetDataSource(int startIndex, int pageSize, PropertyDescriptor orderby, ListSortDirection direction)
- {
- if (PageChanged != null)
- {
- PageChangedEventArgs arg = new PageChangedEventArgs(startIndex, pageSize, orderby, direction);
- PageChanged(this, arg);
- return arg.ReturnSource;
- }
- return null;
- }
- protected virtual int GetTotalCount()
- {
- if (CountNeeded != null)
- {
- CountEventArgs arg = new CountEventArgs();
- CountNeeded(this, arg);
- return arg.TotalCount;
- }
- return 0;
- }
- [DefaultValue(true)]
- public bool AutoRefresh
- {
- get { return _autoRefresh; }
- set { _autoRefresh = value; }
- }
- [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- public int TotalCount
- {
- get { return _totalCount; }
- }
- [DefaultValue(DefaultPageSize)]
- public int PageSize
- {
- get { return _pageSize; }
- set
- {
- if (_pageSize != value)
- {
- _pageSize = value;
- OnPropertyChanged("PageSize");
- if (AutoRefresh) RefreshCurrentPage();
- }
- }
- }
- [DefaultValue(0)]
- public int StartIndex
- {
- get { return _startIndex; }
- set
- {
- if (_startIndex != value)
- {
- _startIndex = value;
- OnPropertyChanged("StartIndex");
- if (AutoRefresh) RefreshCurrentPage();
- }
- }
- }
- public override void Clear()
- {
- _totalCount = 0;
- _startIndex = 0;
- OnPropertyChanged(null);
- DataSource = null;
- }
- public virtual void RefreshSource()
- {
- _totalCount = GetTotalCount();
- _startIndex = 0;
- OnPropertyChanged(null);
- RefreshCurrentPage();
- }
- public virtual void RefreshCurrentPage()
- {
- DataSource = GetDataSource(StartIndex, PageSize, SortProperty, SortDirection);
- }
- protected void OnPropertyChanged(string propertyName)
- {
- if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
- }
- #region INotifyPropertyChanged Members
- public event PropertyChangedEventHandler PropertyChanged;
- public event PageChangedEventHandler PageChanged;
- public event GetCountEventHandler CountNeeded;
- #endregion
- }
- public delegate void PageChangedEventHandler(object sender, PageChangedEventArgs e);
- public delegate void GetCountEventHandler(object sender, CountEventArgs e);
- public class CountEventArgs : EventArgs
- {
- private int totalCount;
- public int TotalCount
- {
- get { return totalCount; }
- set { totalCount = value; }
- }
- }
- public class PageChangedEventArgs : EventArgs
- {
- internal PageChangedEventArgs(int startIndex, int pageSize, PropertyDescriptor orderby, ListSortDirection direction)
- {
- this.startIndex = startIndex;
- this.pageSize = pageSize;
- this.orderby = orderby;
- this.direction = direction;
- }
- private int startIndex;
- public int StartIndex
- {
- get { return startIndex; }
- }
- private int pageSize;
- public int PageSize
- {
- get { return pageSize; }
- }
- private PropertyDescriptor orderby;
- public PropertyDescriptor Orderby
- {
- get { return orderby; }
- }
- private ListSortDirection direction;
- public ListSortDirection Direction
- {
- get { return direction; }
- }
- private object returnSource;
- public object ReturnSource
- {
- get { return returnSource; }
- set { returnSource = value; }
- }
- }
注意到其中有2个事件PageChanged和CountNeeded,一个是分页的查询,一个是获取记录总数。底层的实现不同,这2个事件的实现也不同。比如在MyOrm里提供了分页查询的SearchSection方法,对ProductsView的分页查询可能像这样:
- private void pagedBindingSource1_CountNeeded(object sender, CountEventArgs e)
- {
- e.TotalCount = new ProductsViewDAO().Count(SearchCondition);
- }
- private void pagedBindingSource1_PageChanged(object sender, PageChangedEventArgs e)
- {
- e.ReturnSource = new ProductsViewDAO().SearchSection(SearchCondition, e.StartIndex, e.PageSize, e.Orderby == null ? null : e.Orderby.Name, e.Direction);
- }
另外还可以继承PagedBindingSource,自己实现GetTotalCount方法和GetDataSource方法,可以不需要在事件中添加代码。例如可以为MyOrm定制一个ConditionPagedSource:
- public class ConditionPagedSource : PagedBindingSource
- {
- private IObjectViewDAO _objectViewDAO;
- private Condition _condition;
- private Type _objectType;
- protected override int GetTotalCount()
- {
- return ObjectViewDAO.Count(Condition);
- }
- protected override object GetDataSource(int startIndex, int pageSize, PropertyDescriptor orderby, ListSortDirection direction)
- {
- return ObjectViewDAO.SearchSection(Condition, startIndex, pageSize, orderby == null ? null : orderby.Name, direction);
- }
- [DefaultValue(null), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- public Condition Condition
- {
- get
- {
- return _condition;
- }
- set
- {
- _condition = value;
- RefreshSource();
- }
- }
- [DefaultValue(null), TypeConverter(typeof(TypeTypeConverter)), RefreshProperties(RefreshProperties.Repaint)]
- public Type ObjectType
- {
- get { return _objectType; }
- set
- {
- if (_objectType != value)
- {
- _objectType = value;
- DataSource = value;
- }
- }
- }
- [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- public IObjectViewDAO ObjectViewDAO
- {
- get { return _objectViewDAO == null ? DAOFactoryUtil.GetObjectViewDAO(NorthwindFactory.DAOFactory, ObjectType) : _objectViewDAO; }
- set { _objectViewDAO = value; }
- }
- }
这样就完成了分页查询的数据源。ObjectType使用了TypeTypeConverter来实现设计时支持,其实就是可以把输入的类型名称转化为Type。再在ObjectType的内部DataSource = value也是为了在设计时能自动添加数据列,Winform中添加Object类型的DataSource其实就是BindingSource绑定到对象的Type,这里也照样。
TypeTypeConverter定义:
- public class TypeTypeConverter : TypeConverter
- {
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
- }
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
- }
- public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
- {
- if (value is string) return Type.GetType((string)value);
- return base.ConvertFrom(context, culture, value);
- }
- public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
- {
- if (destinationType == typeof(string)) return ((Type)value).FullName;
- return base.ConvertTo(context, culture, value, destinationType);
- }
- }
分页数据源有了,但是在界面上还需要提供一个控件来控制翻页。也许有现成的控件可以用,我这里仿造BindingNavigator提供了一个PageNavigator:
- public class PageNavigator : BindingNavigator
- {
- private ToolStripItem _currentPageItem;
- private ToolStripItem _moveFirstPageItem;
- private ToolStripItem _moveLastPageItem;
- private ToolStripItem _moveNextPageItem;
- private ToolStripItem _movePreviousPageItem;
- private string _pageCountFormat;
- private ToolStripItem _pageCountItem;
- private PagedBindingSource _pageSource;
- public override void AddStandardItems()
- {
- MoveFirstPageItem = new ToolStripButton();
- MovePreviousPageItem = new ToolStripButton();
- MoveNextPageItem = new ToolStripButton();
- MoveLastPageItem = new ToolStripButton();
- CurrentPageItem = new ToolStripTextBox();
- PageCountItem = new ToolStripLabel();
- ToolStripSeparator separator = new ToolStripSeparator();
- ToolStripSeparator separator2 = new ToolStripSeparator();
- char ch = string.IsNullOrEmpty(base.Name) || char.IsLower(base.Name[0]) ? 'p' : 'P';
- MoveFirstPageItem.Name = ch + "ageNavigatorMoveFirstPageItem";
- MovePreviousPageItem.Name = ch + "ageNavigatorMovePreviousPageItem";
- MoveNextPageItem.Name = ch + "ageNavigatorMoveNextPageItem";
- MoveLastPageItem.Name = ch + "ageNavigatorMoveLastPageItem";
- CurrentPageItem.Name = ch + "ageNavigatorCurrentPageItem";
- PageCountItem.Name = ch + "ageNavigatorPageCountItem";
- separator.Name = ch + "ageNavigatorSeparator";
- separator2.Name = ch + "ageNavigatorSeparator";
- MoveFirstPageItem.Text = "PageNavigatorMoveFirstPageItemText";
- MovePreviousPageItem.Text = "PageNavigatorMovePreviousPageItemText";
- MoveNextPageItem.Text = "PageNavigatorMoveNextPageItemText";
- MoveLastPageItem.Text = "PageNavigatorMoveLastPageItemText";
- PageCountItem.ToolTipText = "PageNavigatorPageCountItemTip";
- CurrentPageItem.ToolTipText = "PageNavigatorCurrentPageItemTip";
- PageCountItem.AutoToolTip = false;
- PageCountItem.Text = "/0";
- CurrentPageItem.AutoToolTip = false;
- CurrentPageItem.Text = "0";
- CurrentPageItem.AccessibleName = "PageNavigatorPositionAccessibleName";
- PageCountFormat = "/{0}";
- Bitmap bitmap1 = new Bitmap(typeof(BindingNavigator), "BindingNavigator.MoveFirst.bmp");
- Bitmap bitmap2 = new Bitmap(typeof(BindingNavigator), "BindingNavigator.MovePrevious.bmp");
- Bitmap bitmap3 = new Bitmap(typeof(BindingNavigator), "BindingNavigator.MoveNext.bmp");
- Bitmap bitmap4 = new Bitmap(typeof(BindingNavigator), "BindingNavigator.MoveLast.bmp");
- bitmap1.MakeTransparent(Color.Magenta);
- bitmap2.MakeTransparent(Color.Magenta);
- bitmap3.MakeTransparent(Color.Magenta);
- bitmap4.MakeTransparent(Color.Magenta);
- MoveFirstPageItem.Image = bitmap1;
- MovePreviousPageItem.Image = bitmap2;
- MoveNextPageItem.Image = bitmap3;
- MoveLastPageItem.Image = bitmap4;
- MoveFirstPageItem.RightToLeftAutoMirrorImage = true;
- MovePreviousPageItem.RightToLeftAutoMirrorImage = true;
- MoveNextPageItem.RightToLeftAutoMirrorImage = true;
- MoveLastPageItem.RightToLeftAutoMirrorImage = true;
- MoveFirstPageItem.DisplayStyle = ToolStripItemDisplayStyle.Image;
- MovePreviousPageItem.DisplayStyle = ToolStripItemDisplayStyle.Image;
- MoveNextPageItem.DisplayStyle = ToolStripItemDisplayStyle.Image;
- MoveLastPageItem.DisplayStyle = ToolStripItemDisplayStyle.Image;
- CurrentPageItem.AutoSize = false;
- CurrentPageItem.Width = 50;
- Items.AddRange(new ToolStripItem[] { MoveFirstPageItem, MovePreviousPageItem, separator, CurrentPageItem, PageCountItem, separator2, MoveNextPageItem, MoveLastPageItem });
- }
- private void AcceptNewPage()
- {
- if (CurrentPageItem != null && _pageSource != null)
- {
- int currentPage;
- int.TryParse(CurrentPageItem.Text, out currentPage);
- currentPage--;
- if (currentPage >= 0)
- {
- int startIndex = currentPage * _pageSource.PageSize;
- if (((((startIndex != _pageSource.StartIndex) && (startIndex >= 0)) && (startIndex < _pageSource.TotalCount)) ? 1 : 0) != 0)
- {
- _pageSource.StartIndex = startIndex;
- if (!_pageSource.AutoRefresh) _pageSource.RefreshCurrentPage();
- return;
- }
- }
- CancelNewPage();
- }
- }
- private void CancelNewPage()
- {
- RefreshPageProperty();
- }
- private void OnCurrentPageKey(object sender, KeyEventArgs e)
- {
- Keys keyCode = e.KeyCode;
- if (keyCode != Keys.Return)
- {
- if (keyCode != Keys.Escape)
- {
- return;
- }
- }
- else
- {
- AcceptNewPage();
- return;
- }
- CancelNewPage();
- }
- private void OnCurrentPageLostFocus(object sender, EventArgs e)
- {
- AcceptNewPage();
- }
- private void OnMoveFirstPage(object sender, EventArgs e)
- {
- if (Validate() && _pageSource != null)
- {
- _pageSource.StartIndex = 0;
- if (!_pageSource.AutoRefresh) _pageSource.RefreshCurrentPage();
- }
- }
- private void OnMoveLastPage(object sender, EventArgs e)
- {
- if (Validate() && _pageSource != null)
- {
- _pageSource.StartIndex = (_pageSource.TotalCount / _pageSource.PageSize) * _pageSource.PageSize;
- if (!_pageSource.AutoRefresh) _pageSource.RefreshCurrentPage();
- }
- }
- private void OnMoveNextPage(object sender, EventArgs e)
- {
- if (Validate() && _pageSource != null)
- {
- _pageSource.StartIndex += _pageSource.PageSize;
- if (!_pageSource.AutoRefresh) _pageSource.RefreshCurrentPage();
- }
- }
- private void OnMovePreviousPage(object sender, EventArgs e)
- {
- if (Validate() && _pageSource != null)
- {
- _pageSource.StartIndex -= _pageSource.PageSize;
- if (!_pageSource.AutoRefresh) _pageSource.RefreshCurrentPage();
- }
- }
- public void RefreshPageProperty()
- {
- if (_pageSource != null)
- {
- int pageSize = _pageSource.PageSize;
- if (pageSize > 0)
- {
- int currentPage = _pageSource.StartIndex / pageSize;
- int totalPage = ((_pageSource.TotalCount + pageSize) - 1) / pageSize;
- PageCountItem.Text = string.Format(PageCountFormat, totalPage);
- if (totalPage == 0)
- {
- CurrentPageItem.Text = "0";
- }
- else
- {
- CurrentPageItem.Text = Convert.ToString(currentPage + 1);
- }
- if (currentPage <= 0)
- {
- MoveFirstPageItem.Enabled = false;
- MovePreviousPageItem.Enabled = false;
- }
- else
- {
- MoveFirstPageItem.Enabled = true;
- MovePreviousPageItem.Enabled = true;
- }
- if (currentPage >= (totalPage - 1))
- {
- MoveLastPageItem.Enabled = false;
- MoveNextPageItem.Enabled = false;
- }
- else
- {
- MoveLastPageItem.Enabled = true;
- MoveNextPageItem.Enabled = true;
- }
- }
- }
- }
- private void WireUpButton(ref ToolStripItem oldButton, ToolStripItem newButton, EventHandler clickHandler)
- {
- if (oldButton != newButton)
- {
- if (oldButton != null)
- {
- oldButton.Click -= clickHandler;
- }
- if (newButton != null)
- {
- newButton.Click += clickHandler;
- }
- oldButton = newButton;
- }
- }
- private void WireUpTextBox(ref ToolStripItem oldTextBox, ToolStripItem newTextBox, KeyEventHandler keyUpHandler, EventHandler lostFocusHandler)
- {
- if (oldTextBox != newTextBox)
- {
- ToolStripControlHost host = oldTextBox as ToolStripControlHost;
- ToolStripControlHost host2 = newTextBox as ToolStripControlHost;
- if (host != null)
- {
- host.KeyUp -= keyUpHandler;
- host.LostFocus -= lostFocusHandler;
- }
- if (host2 != null)
- {
- host2.KeyUp += keyUpHandler;
- host2.LostFocus += lostFocusHandler;
- }
- oldTextBox = newTextBox;
- }
- }
- public ToolStripItem CurrentPageItem
- {
- get
- {
- return _currentPageItem;
- }
- set
- {
- WireUpTextBox(ref _currentPageItem, value, new KeyEventHandler(OnCurrentPageKey), new EventHandler(OnCurrentPageLostFocus));
- }
- }
- public ToolStripItem MoveFirstPageItem
- {
- get
- {
- return _moveFirstPageItem;
- }
- set
- {
- WireUpButton(ref _moveFirstPageItem, value, new EventHandler(OnMoveFirstPage));
- }
- }
- public ToolStripItem MoveLastPageItem
- {
- get
- {
- return _moveLastPageItem;
- }
- set
- {
- WireUpButton(ref _moveLastPageItem, value, new EventHandler(OnMoveLastPage));
- }
- }
- public ToolStripItem MoveNextPageItem
- {
- get
- {
- return _moveNextPageItem;
- }
- set
- {
- WireUpButton(ref _moveNextPageItem, value, new EventHandler(OnMoveNextPage));
- }
- }
- public ToolStripItem MovePreviousPageItem
- {
- get
- {
- return _movePreviousPageItem;
- }
- set
- {
- WireUpButton(ref _movePreviousPageItem, value, new EventHandler(OnMovePreviousPage));
- }
- }
- public string PageCountFormat
- {
- get
- {
- return _pageCountFormat;
- }
- set
- {
- if (!(_pageCountFormat == value))
- {
- _pageCountFormat = value;
- }
- }
- }
- public ToolStripItem PageCountItem
- {
- get
- {
- return _pageCountItem;
- }
- set
- {
- _pageCountItem = value;
- }
- }
- public PagedBindingSource PageSource
- {
- get
- {
- return _pageSource;
- }
- set
- {
- if (value != _pageSource)
- {
- if (_pageSource != null)
- {
- _pageSource.PropertyChanged -= new PropertyChangedEventHandler(PageSource_PropertyChanged);
- }
- _pageSource = value;
- RefreshPageProperty();
- if (_pageSource != null)
- {
- _pageSource.PropertyChanged += new PropertyChangedEventHandler(PageSource_PropertyChanged);
- }
- }
- }
- }
- void PageSource_PropertyChanged(object sender, PropertyChangedEventArgs e)
- {
- RefreshPageProperty();
- }
- }
定义比较繁琐,继承BindingNavigator是为了获得良好的设计时支持,自己写个设计控件实在不是个小工程,所以虽然别扭就凑合一下吧。
这是PagedBindingSource、DataGridView、PageNavigator之间的关系图:
因为DataGridView不提供分页的功能,只能通过PageNavigator控制PagedBindingSource的翻页。
在设计界面中设定PageNavigator的PageSource和DataGridView的DataSource为创建好的ConditionPagedSource后,分页查询就完成了,很简单。(DataGridView在设置DataSource后为自动把AutoGenerateColumns设为false,有可能造成没有列显示出来,需要把InitializeComponent方法中的dataGridView1.AutoGenerateColumns = false;删掉再编辑界面就可以,一个比较烦人的问题)
最后的效果:
示例代码可以到CodePlex/MyOrm下载。