如何使 WPF ScrollViewer 中键单击滚动?

Posted

技术标签:

【中文标题】如何使 WPF ScrollViewer 中键单击滚动?【英文标题】:How can I make WPF ScrollViewer middle-click-scroll? 【发布时间】:2011-07-22 12:36:31 【问题描述】:

单击鼠标中键(又名:鼠标滚轮)然后稍微向下移动鼠标可以让用户在 IE 和大多数 Windows 应用程序中滚动。默认情况下,WPF 控件中似乎缺少此行为?是否有我遗漏的设置、解决方法或明显的东西?

【问题讨论】:

可以处理鼠标中键事件:***.com/questions/517556/…。我明天会尝试正确处理和处理这个事件。 【参考方案1】:

我发现了如何使用 3 个鼠标事件(MouseDownMouseUpMouseMove)来实现这一点。它们的处理程序附加到以下 xaml 中的 ScrollViewer 元素:

<Grid>
    <ScrollViewer MouseDown="ScrollViewer_MouseDown" MouseUp="ScrollViewer_MouseUp" MouseMove="ScrollViewer_MouseMove">
            <StackPanel x:Name="dynamicLongStackPanel">

            </StackPanel>
    </ScrollViewer>
    <Canvas x:Name="topLayer" IsHitTestVisible="False" />
</Grid>

在代码隐藏中写一个行为而不是事件会更好,但不是每个人都有必要的库,而且我不知道如何将它与Canvas联系起来。

事件处理程序:

    private bool isMoving = false;                  //False - ignore mouse movements and don't scroll
    private bool isDeferredMovingStarted = false;   //True - Mouse down -> Mouse up without moving -> Move; False - Mouse down -> Move
    private Point? startPosition = null;
    private double slowdown = 200;                  //The number 200 is found from experiments, it should be corrected



    private void ScrollViewer_MouseDown(object sender, MouseButtonEventArgs e)
    
        if (this.isMoving == true) //Moving with a released wheel and pressing a button
                this.CancelScrolling();
        else if (e.ChangedButton == MouseButton.Middle && e.ButtonState == MouseButtonState.Pressed)
        
            if (this.isMoving == false) //Pressing a wheel the first time
            
                this.isMoving = true;
                this.startPosition = e.GetPosition(sender as IInputElement);
                this.isDeferredMovingStarted = true; //the default value is true until the opposite value is set

                this.AddScrollSign(e.GetPosition(this.topLayer).X, e.GetPosition(this.topLayer).Y);
            
        
    

    private void ScrollViewer_MouseUp(object sender, MouseButtonEventArgs e)
    
        if (e.ChangedButton == MouseButton.Middle && e.ButtonState == MouseButtonState.Released && this.isDeferredMovingStarted != true)
            this.CancelScrolling();
    

    private void CancelScrolling()
    
        this.isMoving = false;
        this.startPosition = null;
        this.isDeferredMovingStarted = false;
        this.RemoveScrollSign();
    

    private void ScrollViewer_MouseMove(object sender, MouseEventArgs e)
    
        var sv = sender as ScrollViewer;

        if (this.isMoving && sv != null)
        
            this.isDeferredMovingStarted = false; //standard scrolling (Mouse down -> Move)

            var currentPosition = e.GetPosition(sv);
            var offset = currentPosition - startPosition.Value;
            offset.Y /= slowdown;
            offset.X /= slowdown;

            //if(Math.Abs(offset.Y) > 25.0/slowdown)  //Some kind of a dead space, uncomment if it is neccessary
            sv.ScrollToVerticalOffset(sv.VerticalOffset + offset.Y);
            sv.ScrollToHorizontalOffset(sv.HorizontalOffset + offset.X);
        
    

如果要删除方法调用AddScrollSignRemoveScrollSign,此示例将起作用。但我用 2 种设置滚动图标的方法对其进行了扩展:

    private void AddScrollSign(double x, double y)
    
        int size = 50;
        var img = new BitmapImage(new Uri(@"d:\middle_button_scroll.png"));
        var adorner = new Image()  Source = img, Width = size, Height = size ;
        //var adorner = new Ellipse  Stroke = Brushes.Red, StrokeThickness = 2.0, Width = 20, Height = 20 ;

        this.topLayer.Children.Add(adorner);
        Canvas.SetLeft(adorner, x - size / 2);
        Canvas.SetTop(adorner, y - size / 2);
    

    private void RemoveScrollSign()
    
        this.topLayer.Children.Clear();
    

图标示例:

最后一点:Press -&gt; Immediately Release -&gt; Move 的方式存在一些问题。如果用户单击鼠标左键或键盘的任何键,或者应用程序失去焦点,则应该取消滚动。有很多事件,我没有时间处理所有事件。

但标准方式Press -&gt; Move -&gt; Release 可以正常工作。

【讨论】:

+1 为解决方案,不过我有一些建议,请参阅我的单独回答【参考方案2】:

vorrtex 发布了一个不错的解决方案,请为他点赞

不过,我确实对他的解决方案有一些建议,这些建议太长了,无法将它们全部放在 cmets 中,这就是为什么我发布一个单独的答案并将其指向他!

你提到了 Press->Release->Move 的问题。即使鼠标不再位于 ScrollViewer 上方,您也应该使用 MouseCapturing 来获取 MouseEvents。我还没有测试过,但我猜你的解决方案在Press-&gt;Move-&gt;Move outside of ScrollViewer-&gt;Release 中也失败了,Mousecapturing 也会解决这个问题。

您还提到使用行为。我宁愿推荐一个不需要额外依赖的attached behavior。

您绝对不应该使用额外的 Canvas,而是在 Adorner 中这样做。

ScrollViewer 本身承载一个定义 AdornerLayer 的 ScrollContentPresenter。你应该在那里插入装饰器。这消除了对任何进一步依赖的需要,并使附加的行为像IsMiddleScrollable="true" 一样简单。

【讨论】:

MouseCapture 实现起来并不难,我会在几个小时后完成。但其他cmets并不那么简单。如果我使用附加属性或行为 - 我将无法将滚动图像添加到 Canvas。另一方面,ScrollContentPresenter' control is situated inside the template of ScrollViewer` 我认为我必须创建一个继承的控件来访问内部部件。我会尝试对此做点什么,但我不确定新控件是否比一组事件处理程序更好。 鼠标捕获后只调用一次 MouseMove 事件。我必须创建一个计时器,它将触发具有移动功能的方法。一切都变得越来越复杂。 @vorrtex 我明天晚上有时间。由于我也对一个不错的紧凑型解决方案感兴趣,因此我将深入研究它!

以上是关于如何使 WPF ScrollViewer 中键单击滚动?的主要内容,如果未能解决你的问题,请参考以下文章

WPF 如何流畅地滚动ScrollViewer

WPF 如何流畅地滚动ScrollViewer 简单实现下

使 Scrollviewer 填充 DockPanel 中的可用空间

如何删除WPF ScrollViewer边框

WPF:关于ScrollViewer中嵌套Datagrid的问题

WPF 在datagrid和ScrollViewer的滚动条上右键发现弹出的选项全是英文,怎么改成汉语?