【问题标题】:Is it possible to implement smooth scroll in a WPF listview?是否可以在 WPF 列表视图中实现平滑滚动?
【发布时间】:2025-11-25 14:05:01
【问题描述】:

是否可以像在 Firefox 中一样在 WPF listview 中实现平滑滚动?
当 Firefox 浏览器包含所有 listview 项目并且您按住鼠标中键(但不释放)并拖动它时,它应该平滑滚动 listview 项目。当你释放它应该停止。

看起来这在winforms中是不可能的,但我想知道它是否可以在WPF中使用?

【问题讨论】:

    标签: c# .net wpf listview scroll


    【解决方案1】:

    你可以实现平滑滚动,但你失去了项目虚拟化,所以基本上你应该只在列表中的元素很少时使用这种技术:

    这里的信息:Smooth scrolling on listbox

    你试过设置吗:

    ScrollViewer.CanContentScroll="False"
    

    在列表框上?

    这样滚动是由面板而不是列表框来处理的......如果你这样做,你会失去虚拟化,所以如果你有很多内容,它可能会变慢。

    【讨论】:

    • 谢谢流行。什么是虚拟化?
    • 虚拟化是 VirtualizingStackPanel 中的内容(这是 ListBox 等的默认 ItemsPanel)实际上不会呈现项目的布局,直到/除非项目可见。因此,对于大量或视觉复杂的项目,它可以显着提升性能,因为它一次只生成一小部分的 ui。更多信息在这里:msdn.microsoft.com/en-us/library/…
    • 这样做是从逻辑滚动切换到物理滚动(假设您使用的是实现 IScrollInfo 的 StackPanel)。如果您仍然想要逻辑滚动但要使其平滑,这无济于事
    • 感谢 ScrollViewer.CanContentScroll="False" 非常有用
    【解决方案2】:

    确实可以按照您的要求进行操作,但需要大量的自定义代码。

    通常在 WPF 中,ScrollViewer 使用所谓的逻辑滚动,这意味着它将逐项滚动,而不是按偏移量滚动。其他答案涵盖了一些可以将逻辑滚动行为更改为物理滚动行为的方法。另一种方法是利用 ScrollViwer 和 IScrollInfo 公开的 ScrollToVertialOffset 和 ScrollToHorizo​​ntalOffset 方法。

    要实现较大的部分,即按下鼠标滚轮时的滚动,我们需要使用 MouseDown 和 MouseMove 事件。

    <ListView x:Name="uiListView"
              Mouse.MouseDown="OnListViewMouseDown"
              Mouse.MouseMove="OnListViewMouseMove"
              ScrollViewer.CanContentScroll="False">
        ....
    </ListView>
    

    在 MouseDown 中,我们将记录当前鼠标位置,我们将使用它作为相对点来确定我们滚动的方向。在鼠标移动中,我们将获取 ListView 的 ScrollViwer 组件和然后相应地滚动它。

    private Point myMousePlacementPoint;
    
    private void OnListViewMouseDown(object sender, MouseButtonEventArgs e)
    {
        if (e.MiddleButton == MouseButtonState.Pressed)
        {
            myMousePlacementPoint = this.PointToScreen(Mouse.GetPosition(this));
        }
    }
    
    private void OnListViewMouseMove(object sender, MouseEventArgs e)
    {
        ScrollViewer scrollViewer = ScrollHelper.GetScrollViewer(uiListView) as ScrollViewer;
    
        if (e.MiddleButton == MouseButtonState.Pressed)
        {
            var currentPoint = this.PointToScreen(Mouse.GetPosition(this));
    
            if (currentPoint.Y < myMousePlacementPoint.Y)
            {
                scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - 3);
            }
            else if (currentPoint.Y > myMousePlacementPoint.Y)
            {
                scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + 3);
            }
    
            if (currentPoint.X < myMousePlacementPoint.X)
            {
                scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - 3);
            }
            else if (currentPoint.X > myMousePlacementPoint.X)
            {
                scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + 3);
            }
        }
    }
    
    public static DependencyObject GetScrollViewer(DependencyObject o)
    {
        // Return the DependencyObject if it is a ScrollViewer
        if (o is ScrollViewer)
        { return o; }
    
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++)
        {
            var child = VisualTreeHelper.GetChild(o, i);
    
            var result = GetScrollViewer(child);
            if (result == null)
            {
                continue;
            }
            else
            {
                return result;
            }
        }
        return null;
    }
    

    它有一些不足之处,因为它只是一个概念证明,但它肯定会让你朝着正确的方向开始。为了让它在鼠标离开初始 MouseDown 点后不断滚动,滚动逻辑可以进入 DispatcherTimer 或类似的东西。

    【讨论】:

      【解决方案3】:

      尝试在 ListView 上将 ScrollViewer.CanContentScroll 附加属性设置为 false。但是就像 Pop Catalin 说的那样,你失去了项目虚拟化,这意味着列表中的所有项目都会立即加载和填充,而不是在需要显示一组项目时 - 所以如果列表很大,这可能会导致一些内存和性能问题。

      【讨论】:

      • 谢谢。我明白了,是的,那很好。但是当我将此属性设置为 false 时,我将如何实现滚动?就像我中间单击+向下拖动一样,我希望它平滑地向下滚动一个量。还是这个特性已经内置但被这个属性激活了?
      • 属性自动激活。我不确定中间按钮滚动功能,但列表仍应滚动。 CanContentScroll 名称有点误导,恕我直言 :) 我会尝试仔细检查它。
      • 谢谢埃迪。是的,奇怪的名字肯定。因此,启用属性时的这种滚动适用于滚动条,对吧?
      • 看看这个相关的问题/答案 - 看起来你必须做一些手动工作来处理你想要的鼠标,但它也可以让你控制平滑度/多少像素你滚动:*.com/questions/1009036/…
      • 就是这个样子。 CanContentScroll 属性似乎是快速的出路,但有副作用(没有虚拟化)。但抓住实际的 ScrollViewer 似乎给了你更多的力量——更多的代码,但可以让你做你想做的事。它可能仍然存在虚拟化问题。我不确定 - 我很难看到自定义滚动仍然允许一次只将所需的项目加载到内存中。这是另一个很棒的帖子:*.com/questions/876994/…
      【解决方案4】:

      尝试将列表视图的高度设置为自动并将其包装在滚动查看器中。

      <ScrollViewer IsTabStop="True" VerticalScrollBarVisibility="Auto">
           <ListView></ListView>
      </ScrollViewer>
      

      别忘了提一下ScrollViewer的高度 希望这会有所帮助....

      【讨论】:

        最近更新 更多