自定义WPF滚动条实现

在开发用户界面时,经常需要展示一系列的图标或者控件,但是当窗口大小不足以展示所有内容时,就需要使用滚动条。然而,标准的滚动条可能会影响界面的美观。本文将介绍一种技术,通过在顶部和底部添加滚动按钮,实现垂直滚动区域,同时隐藏滚动条。同样的方法也可以用于实现水平滚动区域。

前提条件

示例应用程序是用Visual Studio 2010和WPF4编写的。

理解代码

在示例应用程序中,使用了带有形状或文本的小色块来代替实际的图标。这是因为不是艺术家,不想让示例应用程序附带一堆图像文件。

将彩色矩形放入了一个StackPanel中,因为StackPanel实现了IScrollInfo接口,这意味着可以设置ScrollViewer一次滚动一个项目。项目逐个滚动也被称为“逻辑滚动”。如果使用不同的容器,比如GridDockPanel,滚动条将无法在项目之间干净地滚动。

ScrollViewer中,需要设置CanContentScroll="True"来实现逻辑滚动。如果想使用标准的物理滚动,设置CanContentScroll="False"

代码实现

以下是创建自定义滚动条的代码示例:

<ScrollViewer x:Name="VerticalScroller" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Disabled" CanContentScroll="True" SizeChanged="VerticalScrollViewer_SizeChanged" Loaded="VerticalScrollViewer_Loaded" ScrollChanged="VerticalScrollViewer_ScrollChanged"> <StackPanel x:Name="VerticalContentPanel"> ... </StackPanel> </ScrollViewer>

通过这种方法,使用自己的箭头按钮,所以设置VerticalScrollBarVisibility="Hidden"。这个设置意味着StackPanel将垂直滚动,但WPF不会为绘制任何滚动控件(如滚动条或按钮)。

控制滚动的按钮是RepeatButton而不是普通的Button。当使用Blend查看ScrollViewer及其子ScrollBar的控件模板时,看到ScrollBar中的每个按钮(上按钮和下按钮)都是RepeatButton类型,具有特殊行为:如果按住它,它会重复动作。所以决定为上下按钮使用相同类型的按钮。也从控件模板复制了RepeatButton的样式。

当滚动查看器加载时,将滚动条和上、下按钮保存在成员变量中。这是通过获取对象的ControlTemplate然后调用FindName()来实现的。

private ScrollBar _verticalScrollBar; private RepeatButton _upButton; private RepeatButton _downButton; private void VerticalScrollViewer_Loaded(object sender, System.Windows.RoutedEventArgs e) { ScrollViewer scrollViewer = sender as ScrollViewer; _verticalScrollBar = scrollViewer.Template.FindName("PART_VerticalScrollBar", scrollViewer) as ScrollBar; _upButton = _verticalScrollBar.Template.FindName("PART_UpButton", _verticalScrollBar) as RepeatButton; _downButton = _verticalScrollBar.Template.FindName("PART_DownButton", _verticalScrollBar) as RepeatButton; UpdateVerticalScrollBarButtons(); }

当滚动条加载时,还调用了UpdateVerticalScrollBarButtons(),它计算滚动按钮是否应该可见。这个方法也响应SizeChangedScrollChanged事件。这些事件中的任何一个都可能导致滚动按钮的可见性发生变化。例如,这是ScrollChanged处理程序:

private void VerticalScrollViewer_ScrollChanged(object sender, System.Windows.Controls.ScrollChangedEventArgs e) { UpdateVerticalScrollBarButtons(); }

这个项目的核心是设置滚动按钮可见性的代码。关键思想是:如果有足够的空间在StackPanel中显示每个图标(或任何其他类型的对象;在演示项目中它们是Border对象),那么想隐藏两个滚动按钮。

要计算StackPanel的所有子项将占用多少空间,简单地遍历它们并获取每个项目的高度。

double desiredPanelHeight = 0; foreach (UIElement uiElement in VerticalContentPanel.Children) { if (uiElement is FrameworkElement) { FrameworkElement wpfElement = (FrameworkElement)uiElement; desiredPanelHeight += wpfElement.Height; } }

要找出有多少空间可用,获取ScrollBar的高度。由于ScrollViewer位于高度为*的网格行中,ScrollBar将是尽可能大的。此外,如果当前可见的滚动按钮,加上它的滚动条按钮高度,因为这些滚动按钮将在StackPanel内容有足够的空间时被折叠。所以这是可用空间的计算:

double availablePanelHeight = VerticalScroller.ActualHeight; if (UpButton.Visibility == Visibility.Visible) availablePanelHeight += UpButton.Height; if (DownButton.Visibility == Visibility.Visible) availablePanelHeight += DownButton.Height;

通过比较两个计算出的高度(desiredPanelHeightavailablePanelHeight),可以判断是否需要滚动按钮,但仍然想要在滚动条位于顶部时隐藏上按钮,或者在滚动条位于底部时隐藏下按钮。这些计算占据了方法的其余部分:

Visibility upButtonVisibility; Visibility downButtonVisibility; if (availablePanelHeight < desiredPanelHeight) { // scroll buttons are needed but we will still hide the Up button // if the scroll bar is at the top, and we will hide the Down button // if the scroll bar is at the bottom. bool isAtTheTop = false; bool isAtTheBottom = false; if (_verticalScrollBar != null) { if (_verticalScrollBar.Value == _verticalScrollBar.Maximum) isAtTheBottom = true; if (_verticalScrollBar.Value == _verticalScrollBar.Minimum) isAtTheTop = true; } if (isAtTheTop) upButtonVisibility = Visibility.Collapsed; else upButtonVisibility = Visibility.Visible; if (isAtTheBottom) downButtonVisibility = Visibility.Collapsed; else downButtonVisibility = Visibility.Visible; } else { // scroll bars are not needed upButtonVisibility = Visibility.Collapsed; downButtonVisibility = Visibility.Collapsed; } UpButton.Visibility = upButtonVisibility; DownButton.Visibility = downButtonVisibility;

最后,处理点击事件。当按下上按钮时,调用ScrollBar.LineUp,当按下下按钮时,调用ScrollBar.LineDown

private void UpButton_Click(object sender, System.Windows.RoutedEventArgs e) { VerticalScroller.LineUp(); } private void DownButton_Click(object sender, System.Windows.RoutedEventArgs e) { VerticalScroller.LineDown(); }

其他未成功的想法

最初,尝试修改ScrollViewer及其子ScrollBar的控件模板。尽管能够将箭头移动到顶部和底部并隐藏条,但无法找到一种一致且可靠的方法来隐藏和显示滚动条按钮。此外,无法使滚动条按钮启用和禁用。看来标准滚动条并不真正支持这些,也许没有必要。例如,可以从视觉上看出滚动条位于顶部,所以不需要禁用顶部箭头。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485