自定义控件DataPager
在最近的一个项目中,涉及到一个自定义翻页控制的控件,下面就这一个控件做详细的说明,这个自定义控件的主要作用是对数据源进行翻页控制,比如说:“上一页、下一页、首页、末页”等相关操作,由于在一个项目中有多个界面要用到这一部分,所以我们将其封装成一个自定义控件,从而使软件整体结构更加清楚明了,首先我们来将控件的界面展示出来。
整个控件可以分成三个部分,最左边是一个Combobox控件,中间是常见的上一页、下一页、向前、向后操作,最右边是显示当前记录。下面我们重点来介绍中间部分的封装,在这里我们将每一个按钮封装成一个ImageButton控件,这里我们贴出相应的前台、后台代码,从而从整体上进行分析。
<Button x:Class="CustomControlLibrary.ImageButton" 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" mc:Ignorable="d" x:Name="loc" d:DesignHeight="44" d:DesignWidth="75"> <Button.Resources> <ResourceDictionary Source="Themes/Generic.xaml" /> </Button.Resources> <Image Name="innerImage" Source="{Binding ImageSource,ElementName=loc}" Stretch="None" /> </Button>
这里前台的代码比较简单,我们将Button的样式写在了一个资源字典中,同时在Button上面放置一个Image控件,从而组成我们的ImageButton控件,在后台的代码中我们主要定义了两个依赖项属性,ImageSource和GrayImageSource,ImageSource是当按钮正常使用的时候,其显示的背景,GrayImageSource是当按钮的IsEnable=false的时候,其显示的背景,这里先贴出其后台代码。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace CustomControlLibrary { /// <summary> /// ImageButton.xaml 的交互逻辑 /// </summary> public partial class ImageButton : Button { public ImageButton() { InitializeComponent(); this.Style = this.FindResource("ImageButtonStyle") as Style; this.IsEnabledChanged += new DependencyPropertyChangedEventHandler(ImageButton_IsEnabledChanged); } void ImageButton_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) { if (this.IsEnabled && ImageSource != null) { innerImage.Source = ImageSource; } else if (!this.IsEnabled && GrayImageSource != null) { innerImage.Source = GrayImageSource; } } public ImageSource ImageSource { get { return (ImageSource)GetValue(ImageSourceProperty); } set { SetValue(ImageSourceProperty, value); } } // Using a DependencyProperty as the backing store for ImageSource. This enables animation, styling, binding, etc... public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null)); public ImageSource GrayImageSource { get { return (ImageSource)GetValue(GrayImageSourceProperty); } set { SetValue(GrayImageSourceProperty, value); } } // Using a DependencyProperty as the backing store for GrayImageSource. This enables animation, styling, binding, etc... public static readonly DependencyProperty GrayImageSourceProperty = DependencyProperty.Register("GrayImageSource", typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null)); public override void OnApplyTemplate() { base.OnApplyTemplate(); if (this.IsEnabled && ImageSource != null) { innerImage.Source = ImageSource; } else if (!this.IsEnabled && GrayImageSource != null) { innerImage.Source = GrayImageSource; } } } }
这里首先分析一下WPF的依赖项属性和普通的.net属性有什么不同,依赖属性是WPF平台中一个新增的概念,出现的目的是用来实现WPF中的样式、自动绑定及实现动画等特性。依赖属性的出现是WPF这种特殊的呈现原理派生出来的,与.NET普通属性不同的是,依赖属性的值是依靠多个提供程序来判断的,并且其具有内建的传递变更通知的能力。
依赖属性的定义与普通的.NET属性的定义有区别,普通的.NET属性定义只需要定义其set和get区块的赋值与设置即可,而依赖属性需要向.NET的属性系统进行注册。这里我们定义了ImageSource和GrayImageSource两个依赖项属性。另外这里还重写了基类的OnApplyTemplate方法,这个方法是FrameworkElement中定义的方法,在派生类中重写后,每当应用程序代码或内部进程调用 ApplyTemplate,都将调用此方法。FrameworkElement 派生类可以使用 OnApplyTemplate 类处理程序获知显式调用或由布局系统调用此方法的情况。OnApplyTemplate 在完全生成模板并将其附加到逻辑树之后调用。
然后就是具体的DataPager控件的实现了,这里也分别贴出相应的前台和后台的代码。
<UserControl x:Class="CustomControlLibrary.DataPager" 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:loc="clr-namespace:CustomControlLibrary" x:Name="dp" Margin="3" mc:Ignorable="d" d:DesignHeight="46" d:DesignWidth="590" Loaded="DataPager_Loaded"> <Grid> <!--<Grid.Resources> <Style TargetType="{x:Type Image}"> <Setter Property="Margin" Value="3,0,3,0" /> <Setter Property="Cursor" Value="Hand" /> </Style> </Grid.Resources>--> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <ComboBox Grid.Column="0" VerticalAlignment="Center" Name="cboPageSize" MinWidth="40" Margin="5,0,0,0" ItemsSource="{Binding Path=PageSizeItems,ElementName=dp}" SelectedItem="{Binding PageSize,Mode=TwoWay,ElementName=dp}" SelectionChanged="cbpPageSize_SelectionChanged" Visibility="Visible"/> <StackPanel Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal" Margin="5,0,0,0"> <loc:ImageButton Click="btnFirst_Click" x:Name="btnFirst" ImageSource="/CustomControlLibrary;component/Images/pagination_first.gif" GrayImageSource="/CustomControlLibrary;component/Images/pagination_first_gray.gif" /> <loc:ImageButton Click="btnPrev_Click" x:Name="btnPrev" ImageSource="/CustomControlLibrary;component/Images/pagination_prev.gif" GrayImageSource="/CustomControlLibrary;component/Images/pagination_prev_gray.gif" /> <TextBlock Text="第页 " VerticalAlignment="Center"/> <TextBox Width="30" Text="{Binding Path=PageIndex,ElementName=dp}" Name="tbPageIndex" PreviewKeyDown="tbPageIndex_PreviewKeyDown" LostFocus="tbPageIndex_LostFocus" /> <TextBlock Text=" 共 " VerticalAlignment="Center"/> <TextBlock Text="{Binding Path=PageCount, ElementName=dp}" VerticalAlignment="Center"/> <loc:ImageButton Click="btnNext_Click" x:Name="btnNext" ImageSource="/CustomControlLibrary;component/Images/pagination_next.gif" GrayImageSource="/CustomControlLibrary;component/Images/pagination_next_gray.gif" /> <loc:ImageButton Click="btnLast_Click" x:Name="btnLast" ImageSource="/CustomControlLibrary;component/Images/pagination_last.gif" GrayImageSource="/CustomControlLibrary;component/Images/pagination_last_gray.gif" /> <!--<loc:ImageButton Click="btnRefresh_Click" ImageSource="/CustomControlLibrary;component/Images/pagination_load.png" Visibility="Hidden"/>--> </StackPanel> <TextBlock Grid.Column="2" VerticalAlignment="Center" Margin="5,0,5,0" >显示记录 <TextBlock Text="{Binding Path=Start,ElementName=dp}" /> 到 <TextBlock Text="{Binding Path=End,ElementName=dp}"/> 共 <TextBlock Text="{Binding Path=Total,ElementName=dp}"/> 条记录 </TextBlock> </Grid> </UserControl>
这里重点介绍后台的逻辑代码。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.ComponentModel; namespace CustomControlLibrary { /// <summary> /// DataPager.xaml 的交互逻辑 /// </summary> public partial class DataPager : UserControl, INotifyPropertyChanged { public DataPager() { InitializeComponent(); } #region 依赖属性和事件 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 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(16)); 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 IEnumerable<object> ItemsSource { get { return (IEnumerable<object>)GetValue(ItemsSourceProperty); } set { SetValue(ItemsSourceProperty, value); } } /// <summary> /// ItemsSource数据源 /// </summary> public static readonly 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 #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; } } }
这里定义了一些该控件要使用的一些属性,PageSize属性用于定义每一页中的项的数量,Total属性定义总共有多少项,PageIndex定义当前的页码,ItemsSource用于定义数据源的集合,类型是IEnumerable<object>类型,这个里面定义的最重要的函数是RaisePageChanged,该函数将引发分页更改事件,在这个函数中我们将当前定义的路由事件PageChangedEvent,以及每一页项目的大小,以及当前页码传递到PageChangedEventArgs的一个对象中,然后调用RaiseEvent来引发特定的路由事件,这里需要注意的是必须将当前定义的路由事件传递到基类RoutedEventArgs中,否则不能触发该路由事件。