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. CF356E - Xenia and String Problem

    也许更好的阅读体验 \(\mathcal{Description}\) 定义一种字符串\(gray\)串满足: 长度为奇数 正中间的字母只出现一次 左右两端相同,左右两端也是gray串 一个\(gra ...

  2. django中ORM的model对象和querryset 简单解析

    欢迎大家查看我的博客,我会不定时的用大白话发一些看了就能懂的文章,大家多多支持!如您对此文章内容有独特见解,欢迎与笔者练习一起探讨学习!原创文创!转载请注明出处! ORM是干嘛的? 介绍orm之前我应 ...

  3. 不为人知的easy-mock-cli

    初识easy-mock-cli,始于一场缘分 在谈及easy-mock-cli的时候,先说一下easy-mock是什么把?官方解释是:Easy Mock 是一个可视化,并且能快速生成 模拟数据 的持久 ...

  4. sqlserver 2008 无法使用特殊主体‘sa’,错误15405

    今天莫名其妙的遇到一个问题,还原了几个数据库到新的服务器上,突然发现sa用户对某几个数据库没有权限(用户映射): 我手工勾选相应数据库的db_owner权限之后,报错:无法使用特殊主体'sa',错误1 ...

  5. 【TensorFlow 3】mnist数据集:与Keras对比

    在TF1.8之后Keras被当作为一个内置API:tf.keras. 并且之前的下载语句会报错. mnist = input_data.read_data_sets('MNIST_data',one_ ...

  6. JAVA 基于TCP协议的一对一,一对多文件传输实现

    最近老师给我们上了多线程和TCP和UDP协议,其中一个要求就是我们用JAVA协议一个基于TCP和UDP这两种协议的一对一文件上传和一对多文件上传. 然后我就开始分析TCP和UDP这两个协议的特点,发现 ...

  7. javascript基础学习第一天

    Javascript 发展过程: 1.出现:为了解决用户和游览器之间的交互. 2.概念:基于对象和事件驱动,运行在游览器客户端的脚本语言. -js在游览器中运行的.(js引擎:执行js代码) -事件: ...

  8. bean的创建(五)第一部分

    AbstractBeanFactory.doGetBean protected <T> T doGetBean( final String name, final Class<T&g ...

  9. rm -rf /*时遇到的趣事

    今天在一个linux群里面闲逛的时候,突然看见一个愤青把自己的linux系统给 rm -rf /* 了 ,感觉很好玩就看了一下,突然我发现了有趣的事情! 我的朋友问我,这个为什么显示没有删除,我看了确 ...

  10. IIS网站服务器性能优化攻略

    Windows Server自带的互联网信息服务器(Internet Information Server,IIS)是架设网站服务器的常用工具,它是一个既简单而又麻烦的东西,新手都可以使用IIS架设一 ...