Delphi VCL 的 TScrollBox 响应鼠标滚轮的问题

盒子论坛有人说 ScrollBox 不响应鼠标滚轮。

然后有人贴了一条链接:

How to direct the mouse wheel input to control under cursor instead of focused?

上述链接里面,有人写了个很长的解释,也有很长的代码。看起来都头大。有没有简单一些的解决方案?

问题:ScrollBox 如何在鼠标指上去的时候,鼠标滚轮滚动,它就跟随卷动?

测试环境:Delphi 11 CE version + Windows 11; 其它版本的 Delphi 不一定相同。

测试方法

首先,创建一个用于测试的简单的 VCL 程序,有一个 Form1。

然后,拖一个 ScrollBox1 到 Form1 上面,置顶。底下留一些 Form1 出来方便放一个 Label 用于显示滚动时的数据。

在 ScrollBox1 上面从上到下摆一些控件,比如 Edit1, Button1, Memo1 等等。上下的摆放超出 ScrollBox1 的高度,因此,它就会出现垂直滚动条。

按 F9 编译运行。程序运行起来,把鼠标移动到 ScrollBox1 上面,滚动鼠标的中间那个滚轮,发现 ScrollBox1 没有卷动。拖动 ScrollBox1 右侧的垂直滚动条,ScrollBox1 会上下卷动。

如何让它响应鼠标滚轮

在 IDE 里面选择 ScrollBox1,查看属性面板,切换属性面板到 Event 查看它有什么事件,发现它有一个 OnMouseWheel 事件。就用它了。双击属性面板上的这个事件,Delphi IDE 自动创建事件代码框架如下:

procedure TForm1.ScrollBox1MouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
begin

end;

试着在这里面添加代码:

procedure TForm1.ScrollBox1MouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
begin
  ScrollBox1.VertScrollBar.Position := ScrollBox1.VertScrollBar.Position - WheelDelta;
  ScrollBox1.ScrollBy(0, ScrollBox1.VertScrollBar.Position);  
end;

再次编译运行。然后鼠标移动到 ScrollBox1 上面,滚动鼠标滚轮,ScrollBox1 确实随着鼠标卷动起来了。

并且,焦点在 Form1 上的另外一个 Edit1 的时候,它同样能滚动。也就是 ScrollBox1 不需要有焦点才滚动。

但是,问题没那么简单

反复测试,发现几个问题:

1. 鼠标滚轮滚一格,ScrollBox1 的滚动跑好远。不够平滑细腻。虽然,我已经设置了 ScrollBox1 的垂直滚动条的 Smooth 属性为 True。

2. 如果是拖动滚动条,拖到顶部,就不能再往上拖了。但上述滚动代码,鼠标滚轮继续往上滚,ScrollBox1 的滚动条也继续往上,里面的内容也继续往下,一直往下,内容都完全跑出显示区域了还可以继续滚动。滚轮往下滚,内容往上走,一直到内容的最底部都往上走出显示区域了,还可以继续滚动。显然不符合正常使用习惯。正常情况应该是滚到显示内容的顶,或者滚到显示内容的底部,就不再继续滚动。

上述问题的解决

1. 仔细观察发现 WheelDelta 的值很大。我的鼠标滚轮手感上有一格一格的感受,滚动一格,这个 WheelDelta 的值太大,导致上述代码里面采用它作为响应滚动事件的滚动偏移值太大。因此,必须另外搞一个滚动步进值。

2. 拖动 ScrollBox1 右侧垂直滚动条上下走,ScrollBox1.VertScrollBar.Position 值会变化,拖到顶它变为 0;但是,如果是鼠标滚动轮导致的滚动,这个值没变化,始终是初始化的 0;因此,无法用它作为滚动到顶或者滚动到底的判断,导致可以一直滚动把显示内容都滚动出显示区域。因此,这里需要自己定义一个滚动位置的变量,而不能依靠 ScrollBox1.VertScrollBar.Position 这个值。

新的代码如下:
procedure TForm1.ScrollBox1MouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
var
  GoStep: Integer;
begin
  Label1.Caption := WheelDelta.ToString;
  Self.Caption := FPosition.ToString;  //显示当前位置数据,调试用。

  if ((FPosition <= 0) and (WheelDelta > 0)) then Exit;

  if (FPosition >= (Panel2.Top )) and (WheelDelta < 0) then Exit; //Panel2 是最底下的控件。

  if WheelDelta > 0 then GoStep := 10 else GoStep := -10;
  ScrollBox1.ScrollBy(0, GoStep);
  FPosition := FPosition - GoStep;
  Handled := True;
end;

上述代码中,Panel2 是放在 ScrollBox1 中,从上到下的布局中,最底下的控件。因此,它露出来,就等于滚动到最底部了。用它来做判断,停止继续往下滚动。

还有个问题:

ScrollBox1 里面摆放了一个 Memo1 或者 DBGrid 这样的本身就可以滚动的控件,怎么办?

DBGrid 我没有测试。我放了一个 Memo1 在里面,在这个 Memo1 里面放了很多行内容,设置了它的垂直滚动条显示属性为 True;

然后运行程序,鼠标移动到这个 Memo1 上面,滚动鼠标的滚轮,发现:

1. Memo1 里面的内容在滚动;

2. ScrollBox1 也在滚动。

这样不符合正常操作的直觉。

正常操作应该是:

1. 鼠标悬浮在 ScrollBox1 上面,但没有在 Memo1 上面,这个时候鼠标滚动,ScrollBox1 就滚动;

2. 鼠标如果悬浮在 Memo1 上面,鼠标滚动,则 Memo1 滚动,此时 ScrollBox1 不能滚动。

上述情况,类似浏览器的右侧上下滚动条,当浏览器内部页面上有一个内容滚动条时,鼠标指向内部的内容的滚动条,则浏览器页面的滚动条就不应该滚动。

解决方案

增加一个 ScrollBox1 是否需要滚动的变量。此变量由 Memo1 的鼠标进入/离开事件来改变。

完整代码如下:

procedure TForm1.Memo1MouseLeave(Sender: TObject);
begin
  FCanScroll := True;  //鼠标在 Memo1 上面,则鼠标滚轮动作, ScrollBox 不滚动,而是 Memo1 滚动。鼠标离开 Memo1 则 ScrollBox1 滚动。
end;

procedure TForm1.ScrollBox1MouseEnter(Sender: TObject);
begin
  FCanScroll := True;
end;

procedure TForm1.ScrollBox1MouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
var
  GoStep: Integer;
begin
  if not FCanScroll then Exit;

  Label1.Caption := WheelDelta.ToString;
  Self.Caption := FPosition.ToString;

  if ((FPosition <= 0) and (WheelDelta > 0)) then Exit;

  if (FPosition >= (Panel2.Top )) and (WheelDelta < 0) then Exit; //Panel2 是最底下的控件。

  if WheelDelta > 0 then GoStep := 10 else GoStep := -10;
  ScrollBox1.ScrollBy(0, GoStep);
  FPosition := FPosition - GoStep;
  Handled := True;
end;

结束语

到这里,基本的功能已经实现,ScrollBox1 已经可以正常响应鼠标的滚轮。代码基于 Delphi 本身控件的事件方法,没有去拦截系统的滚轮消息等。当然,拦截鼠标滚动消息然后写一堆复杂代码,可能通用性更强。但本文的做法,也是一个简单的解决方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/919267.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mysql复习题(实验7-8)

建立一个学生入学信息管理&#xff08;x_y&#xff09;数据库&#xff0c;设计其数据库模式为&#xff1a; 学生表&#xff08;学号&#xff0c;姓名&#xff0c;性别&#xff0c;入学成绩&#xff0c;籍贯&#xff0c;院系编号&#xff09; 院系表&#xff08;院系编号&…

详细分析ipvsadm负载均衡的命令

目录 前言1. 基本知识2. 命令参数3. 拓展 前言 LVS四层负载均衡架构详解Lvs推荐阅读&#xff1a;添加链接描述 1. 基本知识 ipvsadm 是用于管理和配置 Linux 服务器上 IP Virtual Server (IPVS) 的工具&#xff0c;是 Linux 提供的一个负载均衡模块&#xff0c;支持多种负载…

反向代理模块

1 概念 1.1 反向代理概念 反向代理是指以代理服务器来接收客户端的请求&#xff0c;然后将请求转发给内部网络上的服务器&#xff0c;将从服务器上得到的结果返回给客户端&#xff0c;此时代理服务器对外表现为一个反向代理服务器。 对于客户端来说&#xff0c;反向代理就相当于…

大数据新视界 -- Impala 性能突破:复杂数据类型处理的优化路径(上)(25 / 30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

Excel使用-弹窗“此工作簿包含到一个或多个可能不安全的外部源的链接”的发生与处理

文章目录 前言一、探讨问题发生原因1.引入外部公式2.引入外部数据验证二、问题现象排查及解决1.排查公式2.排查数据验证3.特殊处理方式总结前言 作为一种常用的办公软件,Excel被大家所熟知。尽管使用了多年,有时候在使用Excel时候也会发生一些不太常见的现象,需要用心核查下…

【小程序】dialog组件

这个比较简单 我就直接上代码了 只需要传入title即可&#xff0c; 内容部分设置slot 代码 dialog.ttml <view class"dialog-wrapper" hidden"{{!visible}}"><view class"mask" /><view class"dialog"><view …

跨平台WPF框架Avalonia教程 一

安装 安装 Avalonia UI 模板​ 开始使用 Avalonia 的最佳方式是使用模板创建一个应用程序。 要安装 Avalonia 模板&#xff0c;请运行以下命令&#xff1a; dotnet new install Avalonia.Templates 备注 对于 .NET 6.0 及更早版本&#xff0c;请将 install 替换为 --inst…

JSON.stringify的应用说明

前言 JSON.stringify() 方法将 JavaScript 对象转换为字符串,在日常开发中较常用&#xff0c;但JSON.stringify其实有三个参数&#xff0c;后两个参数&#xff0c;使用较少&#xff0c;今天来介绍一下后两个参数的使用场景和示例。 语法及参数说明 JSON.stringify()&#xf…

家庭网络常识:猫与路由器

这张图大家应该不陌生——以前家庭网络的连接方式。 图1 家庭网络连接示意图 来说说猫/光猫&#xff1a; 先看看两者的图片。 图2 猫 图3 光猫 这个东西因为英文叫“modem”&#xff0c;类似中文的“猫”&#xff0c;所以简称“猫”。 猫和光猫的区别就是&#xff0c;一…

core 不可变类型 线程安全 record

当一个类型的对象在创建时被指定状态后&#xff0c;就不会再变化的对象&#xff0c;我们称之为不可变类型。这种类型是线程安全的&#xff0c;不需要进行线程同步&#xff0c;非常适合并行计算的数据共享。它减少了更新对象会引起各种bug的风险&#xff0c;更为安全。 System.D…

机器学习 ---线性回归

目录 摘要&#xff1a; 一、简单线性回归与多元线性回归 1、简单线性回归 2、多元线性回归 3、残差 二、线性回归的正规方程解 1、线性回归训练流程 2、线性回归的正规方程解 &#xff08;1&#xff09;适用场景 &#xff08;2&#xff09;正规方程解的公式 三、衡量…

基于Java Springboot甘肃旅游管理系统

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

uniApp项目运行到鸿蒙手机,应用图标一直是H,应用名一直是HBuilder问题

项目运行到鸿蒙手机&#xff0c;应用图标一直是H,应用名一直是HBuilder问题 应用运行到鸿蒙手机和鸿蒙模拟器&#xff0c;应用图标一直是H,应用名一直是HBuilder&#xff0c;在自动生成的harmony-configs文件夹下也没有配置的文件&#xff0c; 这时候需要你将DevEco Studio 下…

Spring:IOC/DI注解开发管理第三方bean

前面定义bean的时候都是在自己开发的类上面写个注解就完成了&#xff0c;但如果是第三方的类&#xff0c;这些类都是在jar包中&#xff0c;我们没有办法在类上面添加注解&#xff0c;这个时候该怎么办? 遇到上述问题&#xff0c;我们就需要有一种更加灵活的方式来定义bean,这…

单片机学习笔记 5. 数码管静态显示

更多单片机学习笔记&#xff1a;单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~ 目录 0、实现的功能 1、Keil工程 1-1 数码管显示原理 1-2 静态与动态显示 1-3 74HC573锁存器的工作原理 1-…

使用Ollama和Open WebUI管理本地开源大模型

Open WebUI和Ollama介绍 Open WebUI 是一个功能丰富且用户友好的自托管 Web 用户界面&#xff08;WebUI&#xff09;&#xff0c;它被设计用于与大型语言模型&#xff08;LLMs&#xff09;进行交互&#xff0c;特别是那些由 Ollama 或与 OpenAI API 兼容的服务所支持的模型。O…

Debezium-EmbeddedEngine

提示&#xff1a;一个嵌入式的Kafka Connect源连接器的工作机制 文章目录 前言一、控制流图二、代码分析 1.构造函数2.完成回调3.连接器回调4.RUN总结 前言 工作机制&#xff1a; * 独立运行&#xff1a;嵌入式连接器在应用程序进程中独立运行&#xff0c;不需要Kafka、Kafka C…

【网络安全】SSL(二):Keyless SSL技术细节

未经许可,不得转载。 文章目录 TLS双重目标握手过程是什么?TLS 中的握手类型TLS 术语表RSA 握手协议临时 Diffie-Hellman 握手Diffie-Hellman 握手过程保护密钥服务器其他安全考虑性能提升场景分析持久连接精简握手会话恢复的问题Keyless SSL 的会话恢复功能会话票据恢复会话…

vue2侧边导航栏路由

<template><div><!-- :default-active"$route.path" 和index对应其路径 --><el-menu:default-active"active"class"el-menu-vertical-demo"background-color"#545c64"text-color"#fff"active-text-col…

ChatGPT Search VS Kimi探索版:AI搜索哪家强?!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;专注于分享AI全维度知识&#xff0c;包括但不限于AI科普&#xff0c;AI工…