WPF自定义分页控件

来源:互联网 发布:淘宝最大股东是日本人 编辑:程序博客网 时间:2024/05/19 13:59


       在对大量数据进行可视化展示的时候,我们常常会用到分页的方法,这样一方面可以避免因大量数据在同一页上展示所产生的计算机性能问题,同时,每页上相对少量的数据也有利于浏览时候的快速定位。但是在WPF中并没有这样一个用于分页的预定义控件,因此需要用户自己去实现这样一个应用逻辑。为便于复用,可以将用于分页的内容,包括UI界面和控制逻辑,封装成一个WPF的自定义控件。


       一 设计思路

       经简单的分析可知,一个较为通用的分页控件应该接受以下几个核心的数据:1 总的数据量 2 每页可显示的最大数据 3 可选择的页码数量,同时需要向使用者提供两个核心的数据:1 每页可显示的最大数据 2 当前选择的页数。

       为了便于灵活使用,还应满足一些控制逻辑:1 每页显示的最大数据可以通过选择框来实时切换,2 要能快速跳转到首页和尾页, 3 在切换页码的时候,要能刷新可选择的页码。

       除此之外,还有一些用于界面设置的属性,如定义页码按钮的背景,字体颜色等等。


       二 代码结构

       如图,利用WPF自定义控件(CustomControl)的方式对分页控件进行封装,其中Pagination.xaml为控件的默认模板,Pagination.cs为控件的逻辑控制代码。BoolToVisibilityConverter.cs用于绑定数据类型转换的数据转换类(bool值转元素可见性的Visibility)。

       


       三 模板代码

       控件的默认模板包括:一个ComboBox,用于控制每页可显示数据的最大数量;四个Button,用于跳转到首页、尾页、上一页、下一页四个操作的控制;一个ListBox,用于显示当前可供选择的页码;一个TextBlock,用于展示分页信息。

<ResourceDictionary    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    xmlns:local="clr-namespace:Pagination"    xmlns:cvt="clr-namespace:Paginations.Converters">        <cvt:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>        <Style x:Key="ListBoxItemStyle" TargetType="{x:Type ListBoxItem}">        <Setter Property="Background" Value="{Binding PageSelectorBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Pagination}}"/>        <Setter Property="Padding" Value="0,0,0,0"/>        <Setter Property="BorderThickness" Value="0"/>        <Setter Property="Template">            <Setter.Value>                <ControlTemplate TargetType="{x:Type ListBoxItem}">                    <Border x:Name="Bd" Height="auto" Margin="1" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"  SnapsToDevicePixels="true">                        <ContentPresenter HorizontalAlignment="Stretch" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>                    </Border>                    <ControlTemplate.Triggers>                        <Trigger Property="IsMouseOver" Value="True">                            <Setter TargetName="Bd"                                    Property="BorderBrush"                                    Value="Transparent" />                            <Setter TargetName="Bd"                                    Property="Background"                                    Value="Transparent" />                        </Trigger>                        <Trigger Property="IsSelected" Value="true">                            <Setter Property="Background"                                    TargetName="Bd"                                    Value="{Binding SelectedPageBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Pagination}}"/>                        </Trigger>                    </ControlTemplate.Triggers>                </ControlTemplate>            </Setter.Value>        </Setter>    </Style>        <Style TargetType="{x:Type local:Pagination}">        <Setter Property="Template">            <Setter.Value>                <ControlTemplate TargetType="{x:Type local:Pagination}">                    <Border Background="{TemplateBinding Background}"                            BorderBrush="{TemplateBinding BorderBrush}"                            BorderThickness="{TemplateBinding BorderThickness}">                        <Grid Margin="0,8,0,8">                            <Grid.ColumnDefinitions>                                <ColumnDefinition Width="auto"/>                                <ColumnDefinition Width="*"/>                            </Grid.ColumnDefinitions>                                                        <StackPanel Orientation="Horizontal" Height="32" Grid.Column="0">                                <!--每页数据量选择-->                                <ComboBox x:Name="PART_ComboBox"                                             Visibility="{TemplateBinding IsShowPageDataCountSelector,Converter={StaticResource BoolToVisibilityConverter}}"                                                                               Width="65" Height="25"                                          Background="Transparent"                                          Padding="5,0,0,0"                                          SelectedIndex="0"                                          HorizontalContentAlignment="Center"                                          VerticalContentAlignment="Center"                                          ItemsSource="{TemplateBinding PageDataCountCollection}">                                </ComboBox>                                <Button x:Name="PART_ButtonFirstPage"                                        Content="首页"                                        Width="46"                                        Padding="0"                                        Margin="10,0,0,0"                                        BorderThickness="1"/>                                                                <Button x:Name="PART_ButtonPrePage"                                        Content="《"                                        Width="30"                                        Padding="0"                                        Margin="10,0,0,0"                                        BorderThickness="1"/>                                <ListBox x:Name="PART_ListBoxPages"                                         HorizontalAlignment="Left"                                         ItemContainerStyle="{StaticResource ListBoxItemStyle}"                                         ItemsSource="{TemplateBinding ShowingPageNumberCollection}"                                         SelectedIndex="0"                                         ScrollViewer.VerticalScrollBarVisibility="Hidden">                                    <ListBox.ItemTemplate>                                        <DataTemplate>                                            <Grid>                                                <Label Content="{Binding}" Width="24"                                                        HorizontalAlignment="Center"                                                       VerticalAlignment="Center"                                                       HorizontalContentAlignment="Center"                                                       VerticalContentAlignment="Center"/>                                            </Grid>                                        </DataTemplate>                                    </ListBox.ItemTemplate>                                    <ListBox.ItemsPanel>                                        <ItemsPanelTemplate>                                            <VirtualizingStackPanel Orientation="Horizontal"                                                                    IsItemsHost="True" />                                        </ItemsPanelTemplate>                                    </ListBox.ItemsPanel>                                </ListBox>                                <Button x:Name="PART_ButtonNextPage"                                        Content="》"                                        Width="30"                                        Padding="0"                                        Margin="0,0,0,0"                                        BorderThickness="1"/>                                                                <Button x:Name="PART_ButtonLastPage"                                        Content="尾页"                                        Width="46"                                        Padding="0"                                        Margin="10,0,0,0"                                        BorderThickness="1"/>                            </StackPanel>                                                        <StackPanel x:Name="PART_PageInfo" Grid.Column="1"                                         Orientation="Horizontal"                                         HorizontalAlignment="Right"                                         VerticalAlignment="Center"                                        Margin="0,0,0,0"                                        Visibility="{TemplateBinding IsShowPageInfo,Converter={StaticResource BoolToVisibilityConverter}}">                                <TextBlock>                                    <TextBlock.Text>                                        <MultiBinding StringFormat="{}{0}-{1}条,共{2}页,共{3}条记录">                                            <Binding Path="ShowingPageDataStartNumber" RelativeSource="{RelativeSource TemplatedParent}"/>                                            <Binding Path="ShowingPageDataEndNumber" RelativeSource="{RelativeSource TemplatedParent }"/>                                            <Binding Path="TotalPageCount" RelativeSource="{RelativeSource TemplatedParent}"/>                                            <Binding Path="TotalDataCount" RelativeSource="{RelativeSource TemplatedParent}"/>                                        </MultiBinding>                                    </TextBlock.Text>                                </TextBlock>                            </StackPanel>                        </Grid>                    </Border>                </ControlTemplate>            </Setter.Value>        </Setter>    </Style></ResourceDictionary>


       四 定义依赖属性

       如下所示,在Pagination.cs中定义了一些用于与控件交互的自定义依赖属性,这些属性用于控件外观的控制,分页信息的获取与设置等。

        /// <summary>        /// 是否显示每页数据量选择控件        /// </summary>        public static readonly DependencyProperty IsShowPageDataCountSelectorProperty = DependencyProperty.Register("IsShowPageDataCountSelector", typeof(bool), typeof(Pagination),            new PropertyMetadata(true, null));        /// <summary>        /// 可选择的每页显示的数据条数集合        /// </summary>        public static readonly DependencyProperty PageDataCountCollectionProperty = DependencyProperty.Register("PageDataCountCollection", typeof(ObservableCollection<int>), typeof(Pagination),            new PropertyMetadata(new ObservableCollection<int> { 20, 30, 50 }, null));        /// <summary>        /// 每页最多显示的数据条数        /// </summary>        public static readonly DependencyProperty PageDataCountProperty = DependencyProperty.Register("PageDataCount", typeof(int), typeof(Pagination),            new PropertyMetadata(20, OnPageDataCountPropertyChanged));        /// <summary>        /// 当前显示的可供选择的分页号集合        /// </summary>        public static readonly DependencyProperty ShowingPageNumberCollectionProperty = DependencyProperty.Register("ShowingPageNumberCollection", typeof(ObservableCollection<int>), typeof(Pagination),            new PropertyMetadata(null, null));        /// <summary>        /// 当前选择的页数        /// </summary>        public static readonly DependencyProperty CurrentPageNumberProperty = DependencyProperty.Register("CurrentPageNumber", typeof(int), typeof(Pagination),            new PropertyMetadata(1, OnCurrentPageNumberChanged));        /// <summary>        /// 是否显示分页信息        /// </summary>        public static readonly DependencyProperty IsShowPageInfoProperty = DependencyProperty.Register("IsShowPageInfo", typeof(bool), typeof(Pagination),            new PropertyMetadata(true, null));        /// <summary>        /// 总的数据量        /// </summary>        public static readonly DependencyProperty TotalDataCountProperty = DependencyProperty.Register("TotalDataCount", typeof(int), typeof(Pagination),            new PropertyMetadata(0, null));        /// <summary>        /// 当前页显示的数据条数        /// </summary>        public static readonly DependencyProperty CurrentPageDataCountProperty = DependencyProperty.Register("CurrentPageDataCount", typeof(int), typeof(Pagination),            new PropertyMetadata(0, null));        /// <summary>        /// 总页数        /// </summary>        public static readonly DependencyProperty TotalPageCountProperty = DependencyProperty.Register("TotalPageCount", typeof(int), typeof(Pagination),            new PropertyMetadata(1, null));        /// <summary>        /// 当前显示页的数据起始编号        /// </summary>        public static readonly DependencyProperty ShowingPageDataStartNumberProperty = DependencyProperty.Register("ShowingPageDataStartNumber", typeof(int), typeof(Pagination),            new PropertyMetadata(0, null));        /// <summary>        /// 当前显示页的数据结束编号        /// </summary>        public static readonly DependencyProperty ShowingPageDataEndNumberProperty = DependencyProperty.Register("ShowingPageDataEndNumber", typeof(int), typeof(Pagination),            new PropertyMetadata(0, null));        /// <summary>        /// 显示的可选择页的最大数量        /// </summary>        public static readonly DependencyProperty MaxShownPageCountProperty = DependencyProperty.Register("MaxShownPageCount", typeof(int), typeof(Pagination),            new PropertyMetadata(7, null));        /// <summary>        /// 选中页的背景色        /// </summary>        public static readonly DependencyProperty SelectedPageBackgroundProperty = DependencyProperty.Register("SelectedPageBackground", typeof(Brush), typeof(Pagination),            new PropertyMetadata(new SolidColorBrush(Colors.Red), null));        /// <summary>        /// 未选择的页码的背景色        /// </summary>        public static readonly DependencyProperty PageSelectorBackgroundProperty = DependencyProperty.Register("PageSelectorBackground", typeof(Brush), typeof(Pagination),            new PropertyMetadata(null, null));
      
       属性改变时候的对应回调方法:当前选择页改变时候设置按钮控件是否可用,每页显示最大数量发生改变的时候刷新当前页数据:

        /// <summary>        /// 当前选择的页数发生改变时的回调方法        /// </summary>        /// <param name="d"></param>        /// <param name="e"></param>        private static void OnCurrentPageNumberChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)        {            Pagination pagination = d as Pagination;            if (pagination == null)            {                return;            }            if (pagination._lstShowingPage != null)            {                pagination._lstShowingPage.SelectedItem = e.NewValue;            }            pagination.SetBtnEnable();        }        /// <summary>        /// 每页显示的最大数据量发生改变时的回调方法        /// </summary>        /// <param name="d"></param>        /// <param name="e"></param>        private static void OnPageDataCountPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)        {            Pagination pagination = d as Pagination;            if (pagination == null)            {                return;            }            pagination.InitData();        }


     五 控件初始化

     1 复习OnApplyTemplate方法,初始化控件和数据

        public override void OnApplyTemplate()        {            base.OnApplyTemplate();            //初始化控件            InitControls();            //初始化数据            ShowingPageNumberCollection = new ObservableCollection<int>();            InitData();        }

        /// <summary>        /// 初始化控件        /// </summary>        private void InitControls()        {            _cbbPageDataCount = GetTemplateChild("PART_ComboBox") as ComboBox;            if (_cbbPageDataCount != null)            {                _cbbPageDataCount.SelectionChanged += _cbbPageDataCount_SelectionChanged;            }            _lstShowingPage = GetTemplateChild("PART_ListBoxPages") as ListBox;            if (_lstShowingPage != null)            {                _lstShowingPage.SelectionChanged += _lstShowingPage_SelectionChanged;            }            _btnFirstPage = GetTemplateChild("PART_ButtonFirstPage") as Button;            if (_btnFirstPage != null)            {                _btnFirstPage.Click += _btnFirstPage_Click;            }            _btnPrePage = GetTemplateChild("PART_ButtonPrePage") as Button;            if (_btnPrePage != null)            {                _btnPrePage.Click += _btnPrePage_Click;            }            _btnNextPage = GetTemplateChild("PART_ButtonNextPage") as Button;            if (_btnNextPage != null)            {                _btnNextPage.Click += _btnNextPage_Click;            }            _btnLastPage = GetTemplateChild("PART_ButtonLastPage") as Button;            if (_btnLastPage != null)            {                _btnLastPage.Click += _btnLastPage_Click;            }        }        /// <summary>        /// 初始化数据        /// </summary>        private void InitData()        {            try            {                _isIgnoreListBoxSelectionChanged = true;                if (PageDataCount > 0)                {                    //根据总的数据量和每页最大显示的数据量计算总的页数                    if (TotalDataCount % PageDataCount > 0)                    {                        TotalPageCount = TotalDataCount / PageDataCount + 1;                    }                    else                    {                        TotalPageCount = TotalDataCount / PageDataCount;                    }                    //将可选择页码加入到数据绑定集合中                    if (ShowingPageNumberCollection != null)                    {                        lock (_lock)                        {                            ShowingPageNumberCollection.Clear();                            int addPageCount = MaxShownPageCount;                            if (TotalPageCount < MaxShownPageCount)                            {                                addPageCount = TotalPageCount;                            }                            for (int i = 1; i <= addPageCount; i++)                            {                                ShowingPageNumberCollection.Add(i);                            }                        }                    }                    //初始化选中页                    if (_lstShowingPage != null)                    {                        _lstShowingPage.SelectedIndex = 0;                        CurrentPageNumber = 1;                    }                    //更新分页数据信息                    UpdateShowingPageInfo();                }                SetBtnEnable();            }            finally            {                _isIgnoreListBoxSelectionChanged = false;            }        }

      注册的控件的事件方法,处理跳转到首页、尾页、上一页、下一页的逻辑:

        private void _cbbPageDataCount_SelectionChanged(object sender, SelectionChangedEventArgs e)        {            ComboBox cbb = sender as ComboBox;            if (cbb == null || cbb.SelectedItem == null)            {                return;            }            string selectedCountString = cbb.SelectedItem.ToString();            if (!int.TryParse(selectedCountString, out int selectedDataCount))            {                return;            }            PageDataCount = selectedDataCount;            InitData();        }        private void _lstShowingPage_SelectionChanged(object sender, SelectionChangedEventArgs e)        {            if (_isIgnoreListBoxSelectionChanged)            {                return;            }            try            {                _isIgnoreListBoxSelectionChanged = true;                ListBox lst = sender as ListBox;                if (lst == null || lst.SelectedItem == null)                {                    return;                }                string selectedPageString = lst.SelectedItem.ToString();                if (!int.TryParse(selectedPageString, out int selectedPageNumber))                {                    return;                }                //总页数小于最大可显示页数表明无论选中的页数为何,均不需要改动页码集合中的数据,此时直接返回                if (TotalPageCount <= MaxShownPageCount)                {                    CurrentPageNumber = selectedPageNumber;                    UpdateShowingPageInfo();                    return;                }                //计算为保持选中项居中而需要向右移动的次数 比较中间位置与选中项的下标                int moveCount = MaxShownPageCount / 2 - _lstShowingPage.SelectedIndex;                int startPageNumber = ShowingPageNumberCollection.First();                if (moveCount > 0) //向右移动                {                    int realMoveCount = moveCount;                    if (ShowingPageNumberCollection.First() - 1 < moveCount)                    {                        realMoveCount = ShowingPageNumberCollection.First() - 1;                    }                    startPageNumber = ShowingPageNumberCollection.First() - realMoveCount;                }                else if (moveCount < 0) //向左移动                {                    int realMoveCount = -moveCount;                    if (TotalPageCount - ShowingPageNumberCollection.Last() < realMoveCount)                    {                        realMoveCount = TotalPageCount - ShowingPageNumberCollection.Last();                    }                    startPageNumber = ShowingPageNumberCollection.First() + realMoveCount;                }                lock (_lock)                {                    ShowingPageNumberCollection.Clear();                    for (int i = 0; i < MaxShownPageCount; i++)                    {                        ShowingPageNumberCollection.Add(startPageNumber + i);                    }                }                int selectedItemIndex = ShowingPageNumberCollection.IndexOf(selectedPageNumber);                _lstShowingPage.SelectedIndex = selectedItemIndex;                CurrentPageNumber = selectedPageNumber;                UpdateShowingPageInfo();            }            finally            {                _isIgnoreListBoxSelectionChanged = false;            }        }        /// <summary>        /// 跳转到首页        /// </summary>        /// <param name="sender"></param>        /// <param name="e"></param>        private void _btnFirstPage_Click(object sender, RoutedEventArgs e)        {            if (_lstShowingPage == null                || ShowingPageNumberCollection == null                || ShowingPageNumberCollection.Count == 0)            {                return;            }            if (ShowingPageNumberCollection[0] != 1)            {                try                {                    _isIgnoreListBoxSelectionChanged = true;                    lock (_lock)                    {                        ShowingPageNumberCollection.Clear();                        for (int i = 1; i <= MaxShownPageCount; i++)                        {                            ShowingPageNumberCollection.Add(i);                        }                    }                }                finally                {                    _isIgnoreListBoxSelectionChanged = false;                }            }            _lstShowingPage.SelectedIndex = 0;        }        /// <summary>        /// 跳转到尾页        /// </summary>        /// <param name="sender"></param>        /// <param name="e"></param>        private void _btnLastPage_Click(object sender, RoutedEventArgs e)        {            if (_lstShowingPage == null              || ShowingPageNumberCollection == null              || ShowingPageNumberCollection.Count == 0)            {                return;            }            if (ShowingPageNumberCollection.Last() != TotalPageCount)            {                try                {                    _isIgnoreListBoxSelectionChanged = true;                    lock (_lock)                    {                        ShowingPageNumberCollection.Clear();                        for (int i = 0; i < MaxShownPageCount; i++)                        {                            ShowingPageNumberCollection.Add(TotalPageCount - MaxShownPageCount + i + 1);                        }                    }                }                finally                {                    _isIgnoreListBoxSelectionChanged = false;                }            }            _lstShowingPage.SelectedIndex = _lstShowingPage.Items.Count - 1;        }        /// <summary>        /// 跳转到前一页        /// </summary>        /// <param name="sender"></param>        /// <param name="e"></param>        private void _btnPrePage_Click(object sender, RoutedEventArgs e)        {            if (_lstShowingPage == null                || ShowingPageNumberCollection == null                || ShowingPageNumberCollection.Count == 0)            {                return;            }            if (_lstShowingPage.SelectedIndex > 0)            {                _lstShowingPage.SelectedIndex--;            }        }        /// <summary>        /// 跳转到后一条        /// </summary>        /// <param name="sender"></param>        /// <param name="e"></param>        private void _btnNextPage_Click(object sender, RoutedEventArgs e)        {            if (_lstShowingPage == null                || ShowingPageNumberCollection == null                || ShowingPageNumberCollection.Count == 0)            {                return;            }            if (_lstShowingPage.SelectedIndex < MaxShownPageCount - 1)            {                _lstShowingPage.SelectedIndex++;            }        }

       更新界面显示信息、根据选中的页来控制按钮的可用性

        private void UpdateShowingPageInfo()        {            if(TotalPageCount == 0)            {                ShowingPageDataStartNumber = 0;                ShowingPageDataEndNumber = 0;            }            else if(CurrentPageNumber < TotalPageCount)            {                ShowingPageDataStartNumber = (CurrentPageNumber - 1) * PageDataCount + 1;                ShowingPageDataEndNumber = CurrentPageNumber * PageDataCount;            }            else if(CurrentPageNumber == TotalPageCount)            {                ShowingPageDataStartNumber = (CurrentPageNumber - 1) * PageDataCount + 1;                ShowingPageDataEndNumber = TotalDataCount;            }        }        /// <summary>        /// 设置按钮的可用性        /// </summary>        private void SetBtnEnable()        {            if (_btnFirstPage == null || _btnPrePage == null                || _btnNextPage == null || _btnLastPage == null)            {                return;            }            _btnPrePage.IsEnabled = true;            _btnNextPage.IsEnabled = true;            _btnFirstPage.IsEnabled = true;            _btnLastPage.IsEnabled = true;            if (ShowingPageNumberCollection == null || ShowingPageNumberCollection.Count == 0)//集合为空或者无数据,则所有按钮不可用            {                _btnPrePage.IsEnabled = false;                _btnNextPage.IsEnabled = false;                _btnFirstPage.IsEnabled = false;                _btnLastPage.IsEnabled = false;            }            else            {                if (CurrentPageNumber == 1)                {                    _btnFirstPage.IsEnabled = false;                    _btnPrePage.IsEnabled = false;                }                if (CurrentPageNumber == TotalPageCount)                {                    _btnNextPage.IsEnabled = false;                    _btnLastPage.IsEnabled = false;                }            }        }

       六 控件的使用

       使用控件的时候可以通过绑定获得分页信息:

<Window x:Class="Test.MainWindow"        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"        xmlns:local="clr-namespace:Test"        xmlns:pg="clr-namespace:Pagination;assembly=Paginations"        mc:Ignorable="d"        Title="MainWindow" Height="600" Width="800">    <Grid>        <Grid.RowDefinitions>            <RowDefinition/>            <RowDefinition/>        </Grid.RowDefinitions>        <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">            <StackPanel Orientation="Horizontal">                <Label Content="每页最大显示数据量: "/>                <Label Content="{Binding PageDataCount, ElementName=pg}"/>            </StackPanel>            <StackPanel Orientation="Horizontal">                <Label Content="当前选择页数: "/>                <Label Content="{Binding CurrentPageNumber, ElementName=pg}"/>            </StackPanel>        </StackPanel>        <pg:Pagination x:Name="pg" Grid.Row="1"                        TotalDataCount="255" Margin="20"                       IsShowPageInfo="True"                       MaxShownPageCount="8"                       IsShowPageDataCountSelector="True"                       SelectedPageBackground="Chartreuse"                       PageSelectorBackground="Cyan"/>    </Grid></Window>

       效果图:


        由于是自定义控件,所以可以很容易通过更改控件模板来改变控件的外观:

       


       效果图:



原文链接:http://blog.csdn.net/zhuo_wp/article/details/78350760 转载请注明出处

源代码

原创粉丝点击