一、前言

“GDI+”与“鼠标交互”,乍一听好像不可能,也无从下手,但是实现原理比想象中要简单很多。

基于“GDI+”的“交互”,应用场景也很多,比如:流程图、数据图表、思维导图等等。

本篇文章就通过多个示例来讲解一下 GDI+ 与鼠标交互的原理,以及如何去实现。

每一个示例实现后,都会对示例进行优化,主要是解决一些在实际应用中比较常见的问题,比如:闪烁、资源占用高等等。

而在最后,会基于实际的应用场景——在背景图上绘制图形并进行鼠标交互——编写一个示例。

接着会使用实际应用场景内必备的、也是核心的“局部刷新”技术对示例进行优化。

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

本文地址:https://www.cnblogs.com/lesliexin/p/16554752.html


二、基本原理

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+ 的极限了。

当然,本人的水平也有限,只能说勉强够用而已。

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

所以请对自己掌握的技术多一些信心,多一些耐心。

在此,作者与诸君共勉!

本人水平有限,文章难免有所疏漏,欢迎大家评论指正。


-【END】-

(原创)[C#] GDI+ 之鼠标交互:原理、示例、一步步深入、性能优化的更多相关文章

  1. C++与Lua交互之配置&交互原理&示例

    |Lua 简介 Lua 是一种轻量小巧的脚本语言,也是号称性能最高的脚本语言,它用C语言编写并以源代码形式开放. 某些程序常常需要修改内容,而修改的内容不仅仅是数据,更要修改很多函数的行为. 而修改函 ...

  2. How Javascript works (Javascript工作原理) (十一) 渲染引擎及性能优化小技巧

    个人总结:读完这篇文章需要20分钟,这篇文章主要讲解了浏览器中引擎的渲染机制. DOMtree       ----|   |---->  RenderTree CSSOMtree  ----| ...

  3. Mui --- app与服务器之间的交互原理、mui ajax使用

    1.APP与服务器之间的交互原理 app端(客户端)与服务端的交互其实理解起来和容易,客户端想服务器端发送请求,服务器端进行数据运算后返回最终结果.结果可以是多种格式: 1.text 文本格式 2.x ...

  4. 从零开始openGL——三、模型加载及鼠标交互实现

    前言 在上篇文章中,介绍了基本图形的绘制.这篇博客中将介绍模型的加载.绘制以及鼠标交互的实现. 模型加载 模型存储 要实现模型的读取.绘制,我们首先需要知道模型是如何存储在文件中的. 通常模型是由网格 ...

  5. 浅析Java web程序之客户端和服务器端交互原理(转)

    转载自http://www.cnblogs.com/lys_013/archive/2012/05/05/2484561.html 1. 协议 a. TCP/IP整体构架概述 TCP/IP协议并不完全 ...

  6. [置顶] Asp.Net底层原理(一、浏览器和服务器的交互原理)

    …… 一.浏览器和服务器的交互原理 二.写自己的"迷你"Asp.net框架 三.Asp.Net的请求与响应过程 1.在此之前,首先简单的模拟一下我们去请求一个网址的时候,浏览器和服 ...

  7. 【quickhybrid】H5和Native交互原理

    前言 Hybrid架构的核心就是JSBridge交互,而实现这个交互的前提是弄清楚H5和Native端的交互 本文主要介绍Native端(Android/iOS)和H5端(泛指前端)的交互原理 (之前 ...

  8. Hybrid APP之Native和H5页面交互原理

    Hybrid APP之Native和H5页面交互原理 Hybrid APP的关键是原生页面与H5页面直接的交互,如下图,痛过JSBridge,H5页面可以调用Native的api,Native也可调用 ...

  9. 浏览器与服务器交互原理以及用java模拟浏览器操作v

    浏览器应用服务器JavaPHPApache * 1,在HTTP的WEB应用中, 应用客户端和服务器之间的状态是通过Session来维持的, 而Session的本质就是Cookie, * 简单的讲,当浏 ...

随机推荐

  1. unity---脚本创建按钮

    脚本创建按钮 新建文件夹 Resources 方便引用图片 在文件Resources中新建Images,并且下载一个图片 没有图片,按钮内容无法显示 图片需要处理一下 Textrue Type 改为 ...

  2. 第06组 Beta冲刺 (1/6)

    目录 1.1 基本情况 1.2 冲刺概况汇报 1.郝雷明 2. 方梓涵 3.曾丽莉 4.黄少丹 5. 董翔云 6.杜筱 7.鲍凌函 8.詹鑫冰 9.曹兰英 10.吴沅静 1.3 冲刺成果展示 1.1 ...

  3. CF335E Counting Skyscrapers 题解

    提供一种最劣解第一且巨大难写的做法( Bob 显然真正的楼量可以达到 \(314!\),是没办法直接做的,再加上唯一方案的样例,可以猜测有简单的结论. 考虑当楼高度为 \(k(k<h)\) 时, ...

  4. 【leetcode 206】 反转链表(简单)

    链表 概念: 区别于数组,链表中的元素不是存储在内存中连续的一片区域,链表中的数据存储在每一个称之为「结点」复合区域里,在每一个结点除了存储数据以外,还保存了到下一个结点的指针(Pointer). 由 ...

  5. SpringBoot Restful 接口实现

    目录 SpringBoot 核心注解 SpringBoot Restful 接口实现 封装响应数据 SpringBoot 核心注解 SpringBoot 基础入门 注解 说明 Component 声明 ...

  6. Docker权限 “Got permission denied while trying to connect to the Docker daemon socket at unix:///var/”

    问题及解决办法 在普通用户下执行docker命令需要用sudo,没加sudo出现了下图所示的提示: 从上图看出,权限不足连接/var/run/docker.sock,我们看下这个文件: 可以看出,这个 ...

  7. ICDAR2013

    参考:https://www.cnblogs.com/dmyu/p/6483357.html以及博主相关文章等.

  8. Redis配置文件所在位置

    更新记录 2022年6月13日 发布. Windows系统 Redis 配置文件位于 Redis 安装目录下文件名为 redis.conf 注意:Windows系统下名为 redis.windows. ...

  9. C#取消正在运行的Task

    更新记录 本文迁移自Panda666原博客,原发布时间:2021年6月29日. 一.说明 Task默认就依托于底层线程池中的线程,使用.NET为我们定义好的CancellationTokenSourc ...

  10. Vue回炉重造之封装一个实用的人脸识别组件

    前言 人脸识别技术现在越来越火,那么我们今天教大家实现一个人脸识别组件. 资源 element UI Vue.js tracking-min.js face-min.js 源码 由于我们的电脑有的有摄 ...