在开发用户界面时,经常需要展示一系列的图标或者控件,但是当窗口大小不足以展示所有内容时,就需要使用滚动条。然而,标准的滚动条可能会影响界面的美观。本文将介绍一种技术,通过在顶部和底部添加滚动按钮,实现垂直滚动区域,同时隐藏滚动条。同样的方法也可以用于实现水平滚动区域。
示例应用程序是用Visual Studio 2010和WPF4编写的。
在示例应用程序中,使用了带有形状或文本的小色块来代替实际的图标。这是因为不是艺术家,不想让示例应用程序附带一堆图像文件。
将彩色矩形放入了一个StackPanel
中,因为StackPanel
实现了IScrollInfo
接口,这意味着可以设置ScrollViewer
一次滚动一个项目。项目逐个滚动也被称为“逻辑滚动”。如果使用不同的容器,比如Grid
或DockPanel
,滚动条将无法在项目之间干净地滚动。
在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()
,它计算滚动按钮是否应该可见。这个方法也响应SizeChanged
和ScrollChanged
事件。这些事件中的任何一个都可能导致滚动按钮的可见性发生变化。例如,这是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;
通过比较两个计算出的高度(desiredPanelHeight
和availablePanelHeight
),可以判断是否需要滚动按钮,但仍然想要在滚动条位于顶部时隐藏上按钮,或者在滚动条位于底部时隐藏下按钮。这些计算占据了方法的其余部分:
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
的控件模板。尽管能够将箭头移动到顶部和底部并隐藏条,但无法找到一种一致且可靠的方法来隐藏和显示滚动条按钮。此外,无法使滚动条按钮启用和禁用。看来标准滚动条并不真正支持这些,也许没有必要。例如,可以从视觉上看出滚动条位于顶部,所以不需要禁用顶部箭头。