是否可以更改 WPF ScrollViewer 滚动的量?我只是想知道是否可以更改滚动查看器,以便在使用鼠标滚轮或滚动查看器箭头时,可以更改增量滚动量。
5 回答
简短的回答是:如果不编写一些自定义滚动代码,就没有办法做到这一点,但不要让这吓到你,这并不难。
ScrollViewer 要么通过使用物理单位(即像素)滚动来工作,要么通过使用 IScrollInfo 实现来使用逻辑单位。这通过设置CanContentScroll 属性来控制,其中 false 值表示“使用物理单位滚动内容”,true 值表示“逻辑滚动内容”。
那么 ScrollViewer 是如何在逻辑上滚动内容的呢?通过与 IScrollInfo 实现通信。因此,当有人执行逻辑操作时,您可以准确控制面板内容的滚动量。查看 IScrollInfo 的文档以获取可以请求滚动的所有测量逻辑单元的列表,但是由于您提到了鼠标滚轮,因此您将对 MouseWheelUp/Down/Left/Right 方法最感兴趣。
这是一个简单、完整且有效的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 (!e.Handled &&
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>
快速鼠标滚轮:
我想添加到 Drew Marsh 接受的答案 - 虽然其他建议的答案解决了它,但在某些情况下,您无法覆盖PreviewMouseWheel
事件并处理它而不会导致其他副作用。即,如果您有子控件应该优先在父控件之前ScrollViewer
滚动- 例如嵌套ListBox
或ComboBox
弹出窗口。
在我的场景中,我的父控件是 a 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>
显然,我的场景是人为设计的,解决方案非常简单,但这可能会为其他人提供一条路径,以便更好地控制滚动行为,而不会产生我遇到的副作用。
您可以在滚动查看器上实现一个行为。在我的情况下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>
我这样做是为了确保 scrollbar1.ValueChanged 上的整数:
scrollbar1.Value = Math.Round(scrollbar1.Value, 0, MidpointRounding.AwayFromZero)