65.9K
CodeProject 正在变化。 阅读更多。
Home

仅带滚动按钮的滚动区域

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (14投票s)

2011年6月21日

CPOL

4分钟阅读

viewsIcon

66633

downloadIcon

2988

如何创建带有滚动按钮和隐藏滚动条的垂直或水平滚动区域。

引言

有些应用程序会在左侧或右侧显示工具图标。但如果用户决定缩小窗口,一些图标就会被裁剪掉。我们想使用滚动条,但为一堆图标设置标准滚动条会显得很笨拙。相反,我们希望在图标的顶部和底部有滚动箭头。

本文介绍了一种可以用来制作带有顶部和底部按钮但没有滚动条的垂直滚动区域的技术。或者,你也可以制作一个带有左侧和右侧按钮但没有滚动条的水平滚动区域。该应用程序包含垂直滚动区域和水平滚动区域。

ApplicationPicture.PNG

必备组件

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

理解代码

在示例应用程序中,我使用了带有形状或文本的小彩色区域,而不是实际的图标。那是因为我不是艺术家,也不想让示例应用程序带有许多附加的图像文件。

我将我的彩色矩形放入了一个 StackPanel 中,因为 StackPanel 实现了一个 IScrollInfo,这意味着你可以将 ScrollViewer 设置为一次滚动一个项目。逐项滚动也称为“逻辑滚动”。如果你使用的是其他容器,例如 GridDockPanel,滚动条将无法在项目之间平滑滚动。

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

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

    <StackPanel x:Name="VerticalContentPanel">
    ...

通过这种方法,我们使用了自己的箭头按钮,所以请设置 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 的控件模板。虽然我能够将箭头移动到顶部和底部并隐藏滚动条,但我无法找到一种方法来始终如一地可靠地隐藏和显示滚动条按钮。此外,我也无法使滚动条按钮启用和禁用。看起来标准滚动条并不真正支持这些,而且可能也不是必需的。例如,你可以直观地看到滚动条在顶部,所以你并不需要禁用顶部的箭头。

关注点

在这之前,我一直想知道为什么有人会设置 VerticalScrollBarVisibility="Hidden"。现在我知道了。区域是可滚动的,但其滚动操作由你自己处理。

历史

  • 2011年6月26日:添加了应用程序的图片。
© . All rights reserved.