Wpf滚动视图器滚动量

20

是否可以更改WPF ScrollViewer滚动的量?我只是想知道是否可以更改ScrollViewer,以便在使用鼠标滚轮或scrollviewer箭头时,可以更改增量滚动的数量。

5个回答

18
短答案是:没有办法不编写自定义滚动代码来完成此操作,但不要让这吓到你,它并不难。
ScrollViewer 通过使用物理单位(即像素)滚动或通过与 IScrollInfo 实现交互以使用逻辑单位来工作。这由设置 CanContentScroll 属性 控制,其中值为 false 表示“使用物理单位滚动内容”,而值为 true 表示“逻辑滚动内容”。
那么 ScrollViewer 如何逻辑滚动内容呢?通过与 IScrollInfo 实现通信。因此,您可以接管面板内容在执行逻辑操作时滚动的确切量。查看 IScrollInfo 的文档 以获取所有可请求滚动的逻辑度量单位列表,但由于您提到了鼠标滚轮,因此您主要会对 MouseWheelUp/Down/Left/Right 方法感兴趣。

9
这是一个简单、完整且可用的WPF ScrollViewer类,具有可数据绑定的SpeedFactor属性,可以调整鼠标滚轮的灵敏度。将SpeedFactor设置为1.0意味着与WPF ScrollViewer的行为相同。依赖属性的默认值是2.5,这样可以实现非常快速的滚动。
当然,您也可以通过绑定SpeedFactor属性本身来创建其他有用的功能,例如,轻松地允许用户控制乘数。
public class WheelSpeedScrollViewer : ScrollViewer
{
    public static readonly DependencyProperty SpeedFactorProperty =
        DependencyProperty.Register(nameof(SpeedFactor),
                                    typeof(Double),
                                    typeof(WheelSpeedScrollViewer),
                                    new PropertyMetadata(2.5));

    public Double SpeedFactor
    {
        get { return (Double)GetValue(SpeedFactorProperty); }
        set { SetValue(SpeedFactorProperty, value); }
    }

    protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
    {
        if (ScrollInfo is ScrollContentPresenter scp &&
            ComputedVerticalScrollBarVisibility == Visibility.Visible)
        {
            scp.SetVerticalOffset(VerticalOffset - e.Delta * SpeedFactor);
            e.Handled = true;
        }
    }
};

以下是大约3200个数据项的“快速鼠标滚轮滚动”的完整XAML演示:

注意:仅用于访问演示数据的“mscorlib”参考。

<UserControl x:Class="RemoveDuplicateTextLines.FastScrollDemo"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MyApp"
    xmlns:sys="clr-namespace:System;assembly=mscorlib">

    <local:WheelSpeedScrollViewer VerticalScrollBarVisibility="Auto">
        <ListBox ItemsSource="{Binding Source={x:Type sys:Object},Path=Assembly.DefinedTypes}" />
    </local:WheelSpeedScrollViewer>

</UserControl>

快速滚轮:

enter image description here


注:该段内容为HTML格式,无需翻译。

1
我希望补充Drew Marsh的回答 - 虽然其他建议的答案可以解决问题,但在某些情况下,您不能覆盖PreviewMouseWheel事件并处理它而不会导致其他副作用。特别是如果您有子控件应该优先滚动父ScrollViewer之前 - 如嵌套的ListBox或ComboBox弹出窗口。
在我的情况下,我的父控件是一个ItemsControl,其ItemsPanel是VirtualizingStackPanel。我希望其逻辑滚动每个项目为1个单位,而不是默认的3个单位。我没有使用附加的行为和拦截/处理鼠标滚轮事件,而是简单地实现了一个自定义的VirtualizingStackPanel来完成这个功能。
    public class VirtualizingScrollSingleItemAtATimeStackPanel : VirtualizingStackPanel
    {
        public override void MouseWheelDown()
        {
            PageDown();
        }

        public override void MouseWheelUp()
        {
            PageUp();
        }

        public override void PageDown()
        {
            LineDown();
        }

        public override void PageUp()
        {
            LineUp();
        }
    }

然后我们像往常一样在XAML标记中使用该面板:
<ItemsControl>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <controls:VirtualizingScrollSingleItemAtATimeStackPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

显然,我的场景是人为制造的,解决方案非常简单,但这可能为其他人提供了一条更好地控制滚动行为的路径,而不会遇到我遇到的副作用。

1
您可以在 ScrollViewer 上实现一种行为。 在我的情况下,CanContentScroll 不起作用。 以下解决方案适用于使用鼠标滚轮和拖动滚动条进行滚动。
public class StepSizeBehavior : Behavior<ScrollViewer>
{
    public int StepSize { get; set; }

    #region Attach & Detach
    protected override void OnAttached()
    {
        CheckHeightModulesStepSize();
        AssociatedObject.ScrollChanged += AssociatedObject_ScrollChanged;
        base.OnAttached();
    }

    protected override void OnDetaching()
    {
        AssociatedObject.ScrollChanged -= AssociatedObject_ScrollChanged;
        base.OnDetaching();
    }
    #endregion

    [Conditional("DEBUG")]
    private void CheckHeightModulesStepSize()
    {
        var height = AssociatedObject.Height;
        var remainder = height%StepSize;
        if (remainder > 0)
        {
            throw new ArgumentException($"{nameof(StepSize)} should be set to a value by which the height van be divised without a remainder.");
        }
    }

    private void AssociatedObject_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        const double stepSize = 62;
        var scrollViewer = (ScrollViewer)sender;
        var steps = Math.Round(scrollViewer.VerticalOffset / stepSize, 0);
        var scrollPosition = steps * stepSize;
        if (scrollPosition >= scrollViewer.ScrollableHeight)
        {
            scrollViewer.ScrollToBottom();
            return;
        }
        scrollViewer.ScrollToVerticalOffset(scrollPosition);
    }
}

你会这样使用它:

<ScrollViewer MaxHeight="248"
              VerticalScrollBarVisibility="Auto">
    <i:Interaction.Behaviors>
        <behaviors:StepSizeBehavior StepSize="62" />
    </i:Interaction.Behaviors>

我认为你的解决方案更好,因为它可以“拖动滚动条”。我正在尝试采用这种方法,但是出现了许多红色下划线......Behavior<ScrollViewer>,AssociatedObject,i:Interaction......你能否请友好地分享完整的代码?谢谢! - Kay Lee
我已经无法再查找那段代码了。我猜红线来自Intellisense,它不知道<i:Interaction.Behaviors>。你应该在XAML文档中包含命名空间,问题可能就解决了。关于相关对象,如果你在谷歌上搜索“Behavior.AssociatedObject Property”,你会找到一个MSDN页面。 - Mike de Klerk
嗯...通过NuGet添加了Interactivity.dll(Expression Blend),按照您上面的指南一切都很好。但是,我修改了XAML中的StepSize值以及_scrollchanged事件中的值,但感觉没有任何改进...我该如何设置值才能看到显著的变化呢? - Kay Lee

0
我这样做是为了确保滚动条1的值更改时是整数:
scrollbar1.Value = Math.Round(scrollbar1.Value, 0, MidpointRounding.AwayFromZero)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接