1. 什么是滚动轮劫持

这篇文章介绍一个很简单的继承自ScrollViewer的控件:

public class ExtendedScrollViewer : ScrollViewer
{
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
if (ViewportHeight + VerticalOffset >= ExtentHeight && e.Delta <= 0)
return; if (VerticalOffset == 0 && e.Delta >= 0)
return; base.OnMouseWheel(e);
}
}

所有代码就这么多,这个ExtendedScrollViewer 只是用来解决滚动轮劫持(scroll-wheel-hijack)的问题。所谓的滚动轮劫持,简单来说即是在一个可以滚动的页面使用鼠标滚轮滚动页面的过程中鼠标进入某个可以滚动的子元素导致只在这个子元素中滚动而整个页面想滚滚不动了。

具体看看这个例子:

这个情况相信很多人都遇到过,滚轮被“劫持”后索性去拖动滚动条。有次我遇到个内嵌了很多ScrollViewer的长页面,使用起来真的很恼人,所以我使用ExtendedScrollViewer 解决了这个问题。当然还有另外很多种情况的滚动轮劫持,也有很多解决方案,这篇文章只介绍我遇到的情况和我的解决方案。

2. 实现

在WPF中要禁止ScrollViewer捕获鼠标滚动时间,可以重写OnMouseWheel成一个空的方法:

protected override void OnMouseWheel(MouseWheelEventArgs e)
{
}

OnMouseWheel方法用于响应鼠标滚轮的事件,将它重载成空方法即不再处理鼠标滚利事件。注意在这种情况下不可以使用e.Handled = true,因为我们的目标是让外层的ScrollViewer可以接收到鼠标滚轮事件,所以不能更改MouseWheelEventArgs 的Handled。

当然我们不满足于无脑禁用鼠标滚轮,我们应该更智能些,先让ScrollViewer滚到底,再交由外层的ScrollViewer滚下去。这里面用到几个属性:

MouseWheelEventArgs中的Delta表示鼠标滚轮的变更量,当这个值为正数时表示滚轮向上。

ExtentHeight,获取ScrollViewer内容的实际高度。

ViewportHeight,获取当前可视区域的高度。

VerticalOffset,包含滚动内容对应于页首的垂直偏移量的值,有效值介于 0 与 ExtentHeight 减去 ViewportHeight 所得的数值之间。

熟悉了上面几个属性的作用后我们可以更好地控制鼠标滚轮的行为,当鼠标向上滚动时,判断现在是否已经滚到顶了,如果是就不处理鼠标滚轮事件:

if (VerticalOffset == 0 && e.Delta >= 0)
return;

而当鼠标向下滚动时,需要根据ViewportHeightVerticalOffsetExtentHeight判断当前是否已经滚动到底,如果是就不处理鼠标滚轮事件:

if (ViewportHeight + VerticalOffset >= ExtentHeight && e.Delta <= 0)
return;

3. 其他ScrollViewer方案

ScrollViewer还有很多中玩法,但我工作中不常用到所以就没做。如果觉得不满足还可以参考HandyControl的ScrollViewer,它直接提供了一个CanMouseWheel属性用于控制是否响应鼠标滚轮,另外还支持了滚动等功能。

4. 参考

ScrollViewer.OnMouseWheel(MouseWheelEventArgs) Method (System.Windows.Controls) Microsoft Docs

MouseWheelEventArgs.Delta Property (System.Windows.Input) Microsoft Docs

ScrollViewer.ExtentHeight Property (System.Windows.Controls) Microsoft Docs

ScrollViewer.ViewportHeight Property (System.Windows.Controls) Microsoft Docs

ScrollViewer.VerticalOffset Property (System.Windows.Controls) Microsoft Docs

5. 源码

ExtendedScrollViewer.cs at master

[WPF自定义控件库] 关于ScrollViewr和滚动轮劫持(scroll-wheel-hijack)的更多相关文章

  1. [WPF自定义控件库] 关于ScrollViewer和滚动轮劫持(scroll-wheel-hijack)

    原文:[WPF自定义控件库] 关于ScrollViewer和滚动轮劫持(scroll-wheel-hijack) 1. 什么是滚动轮劫持# 这篇文章介绍一个很简单的继承自ScrollViewer的控件 ...

  2. WPF 如何创建自己的WPF自定义控件库

    在我们平时的项目中,我们经常需要一套自己的自定义控件库,这个特别是在Prism这种框架下面进行开发的时候,每个人都使用一套统一的控件,这样才不会每个人由于界面不统一而造成的整个软件系统千差万别,所以我 ...

  3. [WPF自定义控件库]使用WindowChrome自定义RibbonWindow

    原文:[WPF自定义控件库]使用WindowChrome自定义RibbonWindow 1. 为什么要自定义RibbonWindow 自定义Window有可能是设计或功能上的要求,可以是非必要的,而自 ...

  4. [WPF自定义控件库] 让Form在加载后自动获得焦点

    原文:[WPF自定义控件库] 让Form在加载后自动获得焦点 1. 需求 加载后让第一个输入框或者焦点是个很基本的功能,典型的如"登录"对话框.一般来说"登录" ...

  5. [WPF自定义控件库]以Button为例谈谈如何模仿Aero2主题

    1. 为什么选择Aero2 除了以外观为卖点的控件库,WPF的控件库都默认使用"素颜"的外观,然后再提供一些主题包.这样做的最大好处是可以和原生控件或其它控件库兼容,而且对于大部分 ...

  6. [WPF自定义控件库]简单的表单布局控件

    1. WPF布局一个表单 <Grid Width="400" HorizontalAlignment="Center" VerticalAlignment ...

  7. [WPF自定义控件库]使用WindowChrome的问题

    1. 前言 上一篇文章介绍了使用WindowChrome自定义Window,实际使用下来总有各种各样的问题,这些问题大部分都不影响使用,可能正是因为不影响使用所以一直没得到修复(也有可能别人根本不觉得 ...

  8. [WPF自定义控件库] 自定义控件的代码如何与ControlTemplate交互

    1. 前言 WPF有一个灵活的UI框架,用户可以轻松地使用代码控制控件的外观.例设我需要一个控件在鼠标进入的时候背景变成蓝色,我可以用下面这段代码实现: protected override void ...

  9. [WPF自定义控件库]为Form和自定义Window添加FunctionBar

    1. 前言 我常常看到同一个应用程序中的表单的按钮----也就是"确定"."取消"那两个按钮----实现得千奇百怪,其实只要使用统一的Style起码就可以统一按 ...

随机推荐

  1. 题解 P1864 【[NOI2009]二叉查找树】

    #include<cstdio> #include<string> #include<cstring> #include<iostream> #incl ...

  2. Golang 高效实践之defer、panic、recover实践

    前言 我们知道Golang处理异常是用error返回的方式,然后调用方根据error的值走不同的处理逻辑.但是,如果程序触发其他的严重异常,比如说数组越界,程序就要直接崩溃.Golang有没有一种异常 ...

  3. HBase的优化

    HBase的优化 高可用 在 HBase 中 Hmaster 负责监控 RegionServer 的生命周期,均衡 RegionServer 的负载,如果 Hmaster 挂掉了,那么整个 HBase ...

  4. 跟着大彬读源码 - Redis 6 - 对象和数据类型(下)

    继续撸我们的对象和数据类型. 上节我们一起认识了字符串和列表,接下来还有哈希.集合和有序集合. 1 哈希对象 哈希对象的可选编码分别是:ziplist 和 hashtable. 1.1 ziplist ...

  5. leetcode 48 矩阵旋转可以这么简单

    一行代码解决矩阵旋转(方法三). 方法1: 坐标法 def rotate(self, matrix): n = len(matrix) # 求出矩阵长度 m = (n + 1) // 2 # 求出层数 ...

  6. 入门训练-4. Fibonacci数列

    问题描述 Fibonacci数列的递推公式为:Fn=Fn-1+Fn-2,其中F1=F2=1. 当n比较大时,Fn也非常大,现在我们想知道,Fn除以10007的余数是多少. 输入格式 输入包含一个整数n ...

  7. C#中谁最快:结构还是类?

    前言 在内存当道的日子里,无论什么时候都要考虑这些代码是否会影响程序性能呢? 在现在的世界里,几乎不会去考虑用了几百毫秒,可是在特别的场景了,往往这几百毫米确影响了整个项目的快慢. 通过了解这两者之间 ...

  8. spark 源码分析之二十一 -- Task的执行流程

    引言 在上两篇文章 spark 源码分析之十九 -- DAG的生成和Stage的划分 和 spark 源码分析之二十 -- Stage的提交 中剖析了Spark的DAG的生成,Stage的划分以及St ...

  9. 二、PyTorch 入门实战—Variable(转)

    目录 一.概念 二.Variable的创建和使用 三.标量求导计算图 四.矩阵求导计算图 五.Variable放到GPU上执行 六.Variable转Numpy与Numpy转Variable 七.Va ...

  10. 前后端分离 之vue-cli 搭建项目mac 系统讲解

    前端项目搭建必备技术 webpack nodejs 搭建 vue-cli 的安装 以上技术自行了解安装 一:创建前端项目 采用vue-cli 脚手架 1:终端执行如下命令 vue init webpa ...