在對大量資料進行可視化展示的時候,我們常常會用到分頁的方法,這樣一方面可以避免因大量資料在同一頁上展示所産生的計算機性能問題,同時,每頁上相對少量的資料也有利于浏覽時候的快速定位。但是在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 轉載請注明出處