[C#] GDI+ 之鼠标交互:原理、示例、一步步深入、性能优化

news2025/2/26 10:02:58

一、前言

“GDI+”与“鼠标交互”,乍一听好像不可能,也无从下手,但是实现原理比想象中要简单很多。
基于“GDI+”的“交互”,应用场景也很多,比如:流程图、数据图表、思维导图等等。

本篇文章就通过多个示例来讲解一下 GDI+ 与鼠标交互的原理,以及如何去实现。
每一个示例实现后,都会对示例进行优化,主要是解决一些在实际应用中比较常见的问题,比如:闪烁、资源占用高等等。
而在最后,会基于实际的应用场景——在背景图上绘制图形并进行鼠标交互——编写一个示例。
接着会使用实际应用场景内必备的、也是核心的“局部刷新”技术对示例进行优化。

相信看完的你,一定会有所收获!

本文地址:(原创)[C#] GDI+ 之鼠标交互:原理、示例、一步步深入、性能优化 - leslie_xin - 博客园


二、基本原理

GDI+ 与鼠标交互的原理非常简单:判断鼠标是否在 GID+ 图形上,然后根据鼠标的不同状态,执行不同的效果。

估计很多人看到这句话就直接恍然大悟了。确实,原理就是这么简单。

下面,我们首先来简单实现一个简单的交互效果:可以用鼠标拖动的矩形。


三、示例1:可以用鼠标拖动的矩形

(一)设计器界面

程序界面如下:

我们的绘制及交互区域就是 panel1,所以为 panel1 绑定以下几个鼠标相关的事件:

(二)代码实现

1,添加全局变量

为了与鼠标交互,我们需要以下两个全局变量:

其中,rectShape 是我们所绘制矩形的位置和尺寸;pointLast 是上次鼠标的位置。

2,绘制矩形方法

绘制矩形很简单,直接在背景上画一个矩形即可。
GDI+ 中绘制矩形的方法如下:
(下图来自MSDN)

不过为了防止残留,我们在画矩形前需要先清空一下背景。
(下图来自MSDN)

原理示意如下:

对应的代码如下:

3,鼠标交互操作实现

(1)当鼠标在 panel1 中点击时,我们要判断鼠标点击的位置是否处于我们绘制的矩形内。

如果是,则记录当前鼠标的位置;
如果不是,则清空记录的鼠标位置;

(2)当按着鼠标按键并拖动鼠标时,我们要判断是否有记录过之前鼠标的位置。

如果满足条件,就证明是现在鼠标是按着所绘制的矩形进行拖动了。
所以,我们要计算一下这次鼠标的位移量,并计算矩形的新位置,然后重新在新位置绘制矩形
这一步,就是交互效果的核心。在拖动的过程中,我们会根据鼠标的位置不断的计算并重新绘制新的矩形。在视觉效果上,就是我们拖动着矩形在动。

因为不断在重新绘制矩形,所以这里是最能体现 GDI+ 性能的地方,不同的写法,性能相差很大,这也是后续所要优化的地方。

(3)当松开鼠标按键时,将记录的鼠标位置清空。

上面的 MouseMove 事件会因为不满足条件,而结束重绘。

(三)效果演示

编译运行程序,我们会发现已经可以使用鼠标拖动矩形了。

我们会发现,拖动矩形时会出现闪烁的情况。而且窗口越大,闪烁越明显。
这是因为我们是先清空背景、然后再绘制矩形,这个清空再绘制的过程,就会闪烁

下面,我们就来优化一下,解决闪烁的问题。

(四)“闪烁”问题优化

解决“闪烁”,我们最先想到的就是开启“双缓冲”,不过在这里,开启“双缓冲”效果不大,因为闪烁的原因在于我们自己不断的清空再绘制。
所以,我们优化的核心就是不再清空背景。
开启双缓冲的方式如下:

我们会发现,在两次拖动变化之间,可以看作是先将原矩形填充为背景色,再在新位置绘制一个新的矩形

示意图如下:

我们按照示意图编写代码如下:

(五)优化后效果演示

编译运行程序,我们再次拖动矩形,会发现不再有闪烁的情况。


四、示例2:可以用鼠标拖动的圆形

在实现了可以被鼠标拖动的矩形后,我们再来实现可以被鼠标拖动的圆形。
因为圆形和矩形是不一样的:圆形既有可见区域,也有不可见区域
如图所示:

我们本节就看一下在实现上都有哪些不同。

(一)设计器界面

设计器界面同上,增加一个按钮用来添加圆形。

(二)代码实现

1,添加全局变量

因为 GDI+ 中绘制圆形的参数和矩形是一样的,都是一个 Rectangle ,所以我们可以复用之前的全局变量,不用进行修改。
(下图来自MSDN)

2,绘制圆形方法

这里,我们直接采用上节优化后的方法去实现,即:将旧矩形填充背景色,再在新位置绘制新圆形

原理示意见上节,具体代码如下:

3,鼠标交互操作实现

这里与上节绘制矩形的原理一样,只需要在 MouseMove 事件中将绘制矩形的方法改为绘制圆形的方法即可。
代码修改如下:

(三)效果演示

编译运行,可以发现我们可以正常使用鼠标拖动绘制的圆形。
【注:我们会发现,同样是优化后的方法,在绘制“矩形”时不会闪烁,但是在绘制“圆形”时会闪烁,这是因为绘制圆形会更加消耗性能,关于如何解决闪烁的问题,参见下面:“六、使用“局部刷新”技术对【示例3】进行优化”。因为本节内容的重点不在于此,所以未在此节解决闪烁问题。】

在拖动的时候,我们会发现一个问题:就是我们的鼠标即不在圆形上,而是在圆的四个边角处,也能正常拖动圆形。
如下:

这是因为圆形和矩形不一样,圆形是有可见区域(即显示的圆形)和不可见区域(即非圆形区域),虽然不可见,但仍然是存在的,所以仍然会正常捕获到鼠标的点击。
这里,我们在绘制圆形时将真正的范围填充上颜色,效果会很明显。

下面,我们就针对这个鼠标捕获区域的问题进行优化。

(四)鼠标捕获区域优化

首先,最关键的地方就是在鼠标点击的时候,也就是 MouseDown 事件。

我们判断鼠标是否落在圆形内,不能再通过当前的方法。因为这个只能判断矩形。我们要判断鼠标是否在圆形内,通过通过 Region 去判断。
(下图来自MSDN)

首先,我们添加一条和圆形同尺寸的圆形路径,然后基于此路径创建 Region ,接着判断鼠标是否在此 Region 内。
具体的代码如下:

(五)优化后效果演示

我们再次编译运行程序,会发现只能我们的鼠标点击在圆形内,才能正常拖动圆形。
为了更明显的演示,我们为非圆形区域填充上颜色,再次操作如下:


五、示例3:可以用鼠标拖动的圆形,但背景图不受影响

上面的示例看下来,似乎已经没有问题了。但是在实际应用过程中,却有一个不可忽视的元素:背景图(此处的背景图是广义上的背景图,可指图片、其它GDI+ 图形等等,但原理都是一样的)。

因为前面的示例背景都是纯色,所以我们看不出来,现在我们为 panel1 加上背景图,再次运行程序,我们看下效果:

可以看到,拖动过的地方背景直接被擦了。这还是优化后的代码,如果是最开始的“先清除背景再绘制图形”,则在第一次拖动的时候,整个背景图就都没了。

本节,我们就来看一下:如何在用鼠标拖动圆形时,背景图还正常显示不受影响。

(一)设计器界面

设计器界面同上,不作变化。

(二)代码实现

1,生成背景图

首先,我们写一个方法,生成一张背景图,当然也可以使用现成的图片。
然后将这张背景图保存为全局变量,以供后续使用。

2,修改绘制圆形方法

既然背景图受到影响,我们想到的最直接方法便是在每次绘制圆形时,都重新将背景图绘制一遍。
不过将整个背景图完整的重绘一遍会太过消耗资源,所以我们可以采取之前的优化思路,就是填充原矩形、绘制背后矩形,不过这里的填充不再是背景色,而是背景图

首先,我们需要计算一下原矩形在背景图中对应的位置和尺寸,然后将这块背景绘制上去,接着再绘制新的矩形。
我们使用这个重载方法进行背景图的绘制:
(下图来自MSDN)

具体的代码如下:

(三)效果演示

编译运行,可以发现背景确实不受影响了。

不过上节中出现的在绘制圆形闪烁的问题也更严重了。
那么下面,我们就从根本上来解决一下闪烁的问题。


六、使用“局部刷新”技术对【示例3】进行优化

在前面的示例中,使用同样的优化方式,在绘制矩形时不闪烁,而在绘制圆形时却会闪烁,虽说是因为绘制圆形更耗性能,但也说明了前面的优化还远远不足。
而问题的根源,就在于刷新的面积太大了。所以我们的优化方向,就在于怎么将这个“刷新面积”减小,也就是所谓的“局部刷新”技术。

下面,我们就以【示例3】为例来演示下如何使用“局部刷新”技术。

(一)“剪辑区域”

与“局部刷新”所对应的,就是“剪辑区域”,顾名思义,就是专门剪辑出来用来重绘的区域。

在计算“剪辑区域”时,为了方便计算和演示,我们直接将拖动时刚好包含“原矩形”和“新矩形”的矩形区域当成“剪辑区域”。

(二)修改绘制圆形方法

在绘制圆形时,我们首先要计算剪辑区域,然后获取剪辑区域所对应的背景图,接着设置剪辑区域,并绘制新矩形。

(三)效果演示

编译运行程序,可以看到在拖动圆形时,不会再出现闪烁的问题,同时各种资源的占用也很低。


七、“局部刷新”技术在实际场景中的应用

在实际应用场景中,并不是简单的一个背景一个图形。在需要用到 GDI+ 交互的场景,往往都会在同一个区域内有好多个不同的 GDI+ 图形。

这种场景的基本绘制流程一般如下:
1,将诸多 GDI+ 图形保存到一个集合内,一般是以类的形式,类里面包含图形类型、绘制此图形所需要的参数、附加参数等。
2,在绘制时,将背景图(如果有的话)和图形集合绘制到一个临时Bitmap 上,然后将此临时Bitmap 绘制到窗口上。
3,释放临时Bitmap等资源。

在这种流程下,如果按照“局部刷新”的方式,就不免会出现闪烁、CPU内存占用高等问题。

所以,这种时候就必然要用到“局部刷新”技术。我们不用再将全部的图形集合和背景图绘制到一张临时Bitmap上,而是先计算剪辑区域,然后判断图形集合内有哪些图形在剪辑区域内,之后仅重新绘制这些图形即可。


八、源代码下载

本文演示的程序源代码如下:

https://files.cnblogs.com/files/lesliexin/GdiInteractive.7z


九、总结

在这个新技术层出不穷的时代,GDI+ 已经被冠上诸如“上个时代的技术、落后的技术、性能很差的技术”等等名词。

但是 GDI+ 的效率并不低下,只是很少有能够发挥出 GDI+ 的正常性能,更别说触摸到 GDI+ 的极限了。
当然,本人的水平也有限,只能说勉强够用而已。

新技术,给了我们更多的选择,不过技术是没有先进落后之分的,只有合适与不合适之别

所以请对自己掌握的技术多一些信心,多一些耐心。
在此,作者与诸君共勉!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

美新科技IPO过会:9个月营收6亿 林东融三兄弟为实控人

雷递网 雷建平 11月26日美新科技股份有限公司(简称:“美新科技”)日前IPO过会,准备在深交所创业板上市。美新科技计划募资9.58亿元,其中,5.1亿元用于美新科技新型环保塑木型材产业化项目(一期&a…

基于HTML+CSS+JavaScript制作学生网页——外卖服务平台10页带js 带购物车

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材,DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | HTML期末大学生网页设计作业 HTML:结构 CSS:样式 在操作方面上运用了html5和css3, 采用了divcss结构、表单、超链…

[附源码]SSM计算机毕业设计拾穗在线培训考试系统JAVA

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

JS逆向 Frida - 夜神模拟器安装配置 基本使用

JS逆向 Frida - 夜神模拟器安装配置 基本使用 文章目录JS逆向 Frida - 夜神模拟器安装配置 基本使用前言一、Frida简单介绍?1.Frida是什么2.Frida原理(建议了解一下,否则后续的安装会有些懵懂)二、Frida下载1.pip安装frida模块2.查看本地的frida版本&…

大数据项目之电商数仓DataX、DataX简介、DataX支持的数据源、DataX架构原理、DataX部署

文章目录1. DataX简介1.1 DataX概述1.2 DataX支持的数据源2. DataX架构原理2.1 DataX设计理念2.2 DataX框架设计2.3 DataX运行流程2.4 DataX调度决策思路2.5 DataX与Sqoop对比3. DataX部署3.1 下载DataX安装包并上传到hadoop102的/opt/software3.2 解压datax.tar.gz到/opt/modu…

一、微服务入门

文章目录一、微服务大概认识二、单体架构架构和分布式架构三、微服务架构特征四、微服务技术对比五、SpringCloud 与 SpringBoot版本兼容关系如下:一、微服务大概认识 二、单体架构架构和分布式架构 单体架构:将业务的所有功能集中在一个项目中开发&…

一文弄懂 Diffusion Model

什么是 Diffusion Model 一、前向 Diffusion 过程 Diffusion Model 首先定义了一个前向扩散过程,总共包含T个时间步,如下图所示: 最左边的蓝色圆圈 x0 表示真实自然图像,对应下方的狗子图片。 最右边的蓝色圆圈 xT 则表示纯高斯…

Tomcat安装及配置和常见的问题(2022最新详解、图文教程)

Tomcat的配置安装1. 关于WEB服务器软件2. 配置Tomcat的服务器第一步:配置Java的运行环境第二步:Tomcat的安装第三步:启动Tomcat3. 问题一:解决Tomcat服务器在DOS命令窗口中的乱码问题(控制台乱码)4. 测试To…

问题盘点|使用 Prometheus 监控 Kafka,我们该关注哪些指标

Kafka 作为当前广泛使用的中间件产品,承担了重要/核心业务数据流转,其稳定运行关乎整个业务系统可用性。本文旨在分享阿里云 Prometheus 在阿里云 Kafka 和自建 Kafka 的监控实践。01Kafka 简介Aliware01Kafka 是什么?Kafka 是分布式、高吞吐…

算法选修(J.琴和可莉)(为选修画上句号)

可莉又去池塘炸鱼啦!琴团长决定亲自捉拿可莉将其关禁闭。琴团长不断地追,可莉不断地跑。 琴团长和可莉的行动路线可以看做是一个有n个节点的无根树,初始时琴团长在A点,可莉在B点,她们互相知道对方的位置。 琴团长想尽…

P8869 [传智杯 #5 初赛] A-莲子的软件工程学

import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);long a sc.nextLong();long b sc.nextLong();System.out.println(Math.abs(a)*(b>0?1:-1));}} 题目背景 在宇宙射线的轰击下,莲子…

Day13--搜索建议-自动获取焦点与防抖处理

1.定义如下的 UI 结构: 我的操作: 第一次尝试:【出现轮廓】 官方文档: 1》在search.vue中: 效果图:【还是和博主的搜索框有区别的】 第二次尝试:【加上圆角】 官方文档: 第三次尝试…

58、ElasticSearch DSL Bucket聚合

1、聚合的分类 2、DSL实现Bucket聚合 # 集合, 1、bucket terms GET /hotel/_search { "size": 0, "aggs": { "brandAgg": { "terms": { "field": "brand", "size": 20 …

10.前端笔记-CSS-盒子模型-border和padding

页面布局的三大核心: 盒子模型浮动定位 1、盒子模型 1.1 盒子模型组成 盒子模型本质还是一个盒子,包括边框border、外边距margin、内边距padding和实际内容content 1.1.1 边框border 组成 组成:颜色border-color、边框宽度border-wid…

信息论与编码:线性分组码与性能参数

文章目录1.1 线性分组码(n,k)定义1.2 信道编码性能参数1.3基本线性分组码a.奇偶监督码b.恒比码c.汉明码1.4 差错控制类型对信道编码的要求1.5信道编码主要涉及的数学知识:有限域运算、矩阵运算1.1 线性分组码(n,k)定义 线性分组码是由 (n, k) 形式表示。编码器将一…

WEB安全技能树-安全漏洞类型-命令执行漏洞

题目类型 环境:CentOSApachePHPMySQL 题目:ping主机 考点分析 1.过滤 ; && || 等多条命令连接符; 2.过滤cat more less等文件读取命令; 解题思路 第一步 ping 127.0.0.1 看看命令是否能够正确执行 linux如果不指定-…

【Java第35期】:Bean的生命周期

作者:有只小猪飞走啦 博客地址:https://blog.csdn.net/m0_62262008?typeblog 内容:1,这篇博客要分析Bean生命周期有几个阶段? 2,每个阶段的效果是什么? 3,PostConstruct 和 PreDestroy 各自的效果是什…

如果线性变换可以模仿

🍿*★,*:.☆欢迎您/$:*.★* 🍿 正文 如何模仿一个 行为 假设这个行为是线性变换 A 通过权重w 变换为 B 假设可以通过 如下方式 模仿 A变换到B 线性变换 让 C 变换 D首先 计算A C 的距离 dx 计算 B D 的距离 dy假设 w 是通过等差求解权重的方…

(附源码)计算机毕业设计Java搬家预约系统

项目运行 环境配置: Jdk1.8 Tomcat8.5 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: Springboot mybatis Maven Vue 等等组成,B/…

Elon Musk 与开发者分享他的第一份代码评审

Elon Musk 比以往任何时候都更致力于 Twitter 2.0 的成功,与开发者分享他的第一份代码评审。 原文 https://ssaurel.medium.com/more-committed-than-ever-to-making-twitter-2-0-succeed-elon-musk-shares-his-first-code-review-a565e8df5e2f 前言 Elon Musk 也是…