资讯动态

易语言调用绘图API实战:双缓冲框架与GDI对象管理

发布时间:2026/10/14 1:16:18 来源:尧图企业网站定制
1. 为什么要在易语言里折腾绘图API很多人对易语言的印象还停留在“拖控件、写窗口、做小工具”的阶段觉得它就是个快速拼界面的工具跟图形绘制这种偏底层的事情沾不上边。我刚开始也是这么想的直到有一次需要在一个数据采集工具里实时画出采集到的信号波形用易语言自带的画板控件刷新起来闪得眼睛疼数据量一上来直接卡成幻灯片。那时候我才认真去研究易语言调用系统绘图API这条路子实测下来性能提升非常明显而且可控性完全不是一个级别。所谓“易语言调用API之绘图函数”说白了就是在易语言里通过DLL命令声明的方式去调用操作系统底层提供的图形设备接口函数把绘制工作直接交给系统去处理而不是依赖易语言自带的那套封装控件。这套东西能做什么小到画一条线、一个圆、一段文字大到做实时波形图、自定义进度条、异形窗口、图像滤镜处理都能覆盖。它解决的核心问题是性能和自由度——自带控件刷新慢、样式死板、双缓冲支持差而API绘制可以自己控制刷新时机、自己管理内存位图、自己决定每一个像素怎么画。这篇文章适合谁看如果你已经会用易语言写基本的窗口程序知道什么是子程序、什么是变量类型但一看到DLL命令、句柄、设备上下文这些词就头大那这篇就是写给你的。我会从最基础的概念讲起把每个参数为什么这么传、每个步骤为什么这么设计都掰开说清楚最后给出一套可以直接抄作业的完整绘制框架。如果你已经是老手也可以看看后面关于双缓冲和资源释放的避坑部分那些都是我实际踩过的坑。2. 绘图API的核心概念拆解2.1 设备上下文到底是个什么东西要理解绘图API第一个绕不开的概念就是设备上下文英文叫Device Context简称DC。你可以把它想象成一张“画布的通行证”。在系统里任何绘制操作都不是凭空画在屏幕上的而是先画到某个“目标”上这个目标可能是一个窗口、一张内存里的位图、一台打印机甚至是一个虚拟的图形表面。设备上下文就是描述这个目标当前状态的一堆信息的集合——画笔颜色、画刷样式、字体、坐标系原点、裁剪区域等等。为什么要有这么个东西因为系统需要知道你到底想往哪儿画、用什么工具画。你调用绘图函数的时候几乎每个函数的第一个参数都是这个DC句柄。句柄你可以理解成一个编号系统内部维护了一张表你拿着这个编号去操作系统就知道对应的是哪块画布。在易语言里句柄就是一个整数型变量但它背后代表的东西远比一个数字复杂。获取DC有几种常见方式。最直接的是调用GetDC传入窗口句柄系统返回这个窗口客户区的DC。还有GetWindowDC返回的是包含标题栏和边框的整个窗口DC。另外在响应WM_PAINT消息的时候可以用BeginPaint获取一个专用于重绘的DC配合EndPaint使用。这几种方式拿到的DC用途和释放方式都不一样后面实操部分我会详细说。2.2 画笔、画刷与GDI对象的基本关系有了DC这张通行证接下来要准备“画笔”和“画刷”。这两个概念很容易混我用一个生活化的类比画笔决定线条长什么样画刷决定填充区域长什么样。你画一个矩形边框用画笔内部填充用画刷。画一条直线只用画笔。画一段文字用的是字体对象。在系统底层这些统称为GDI对象。常见的GDI对象包括HPEN画笔、HBRUSH画刷、HFONT字体、HBITMAP位图、HRGN区域。它们都有一个共同特点——需要你先创建使用完之后必须销毁否则就会造成资源泄漏。这一点非常关键我见过太多人写绘图代码画是画出来了程序跑一会儿内存和GDI句柄数就爆了最后整个系统都变卡。创建画笔用CreatePen需要指定线型、线宽和颜色。线型有实线、虚线、点线等几种线宽是像素值颜色用RGB值表示。创建画刷用CreateSolidBrush可以创建纯色画刷用CreateHatchBrush可以创建带纹理的画刷。创建好之后用SelectObject把它们选进DC这时候DC才知道接下来绘制要用什么工具。这里有个细节SelectObject会返回之前被选入的那个同类型对象的句柄你一定要把这个旧句柄保存下来等绘制完成后把它选回去然后再销毁你自己创建的对象。这个“选入-保存旧句柄-选回旧句柄-销毁新对象”的流程是GDI编程的铁律。2.3 坐标系与映射模式绘图离不开坐标。默认情况下DC的坐标原点在客户区左上角X轴向右递增Y轴向下递增单位是像素。这个叫MM_TEXT映射模式也是最常用的模式。但有些场景下你可能需要不同的坐标系比如画数学函数图像的时候希望原点在中间、Y轴向上递增这时候就需要切换映射模式。系统提供了多种映射模式比如MM_LOENGLISH单位是0.01英寸Y轴向上、MM_ANISOTROPIC可以自定义X和Y的缩放比例。切换映射模式用SetMapMode函数。不过说实话在实际项目里我很少去改映射模式因为改完之后所有坐标计算都要跟着变容易出错。更常见的做法是保持默认的MM_TEXT模式自己在代码里做坐标变换——比如画波形图的时候自己写一个把数据值映射到像素坐标的函数这样逻辑更清晰调试也方便。坐标变换里还有一个常用的函数是SetViewportOrgEx可以设置坐标原点偏移。比如你想让原点移到窗口中心就调用它把原点设成宽度的一半和高度的一半。这个比切换映射模式要轻量得多推荐优先考虑。3. 易语言中声明与调用API的完整流程3.1 DLL命令的声明格式与参数类型对应易语言调用API的第一步是在“DLL命令”表里声明函数。声明的时候需要填写几个关键信息命令名称你自己起的名字、返回值类型、库文件名、在库中的对应名称、参数列表。库文件名对于系统绘图API来说就是gdi32.dll或者user32.dll具体看函数属于哪个库。参数类型的对应是个容易出错的地方。系统API的参数类型和易语言的数据类型不是一一对应的需要你手动转换。我整理了一张常用对照表API参数类型易语言声明类型说明HWND整数型窗口句柄HDC整数型设备上下文句柄HPEN/HBRUSH/HFONT整数型GDI对象句柄COLORREF整数型颜色值0x00BBGGRR格式int整数型32位整数BOOL整数型返回1成功0失败LPRECT整数型传结构体地址时用整数型LPCTSTR文本型字符串参数这里要特别说一下COLORREF。系统API里的颜色值不是我们常见的RGB顺序而是BGR顺序也就是低字节是红色中间字节是绿色高字节是蓝色。易语言里可以用RGB函数来生成但要注意易语言自带的RGB函数返回的格式和API要求的可能不一致最稳妥的做法是自己用位运算拼颜色值 红色 绿色 × 256 蓝色 × 65536。这个坑我踩过当时画出来的颜色红蓝颠倒排查了半天才发现是字节序的问题。3.2 结构体参数的传递技巧有些API需要传结构体指针比如RECT结构体用于指定矩形区域PAINTSTRUCT用于绘制消息。易语言里没有直接的指针类型通常的做法是定义一个自定义数据类型然后取它的地址传进去。取地址用取变量地址命令返回一个整数把这个整数传给API的指针参数即可。以RECT为例定义一个自定义数据类型包含left、top、right、bottom四个整数型成员。使用的时候先给成员赋值然后调用取变量地址(矩形变量)传给API。需要注意的是结构体的内存对齐方式要和系统API一致。系统API默认使用4字节对齐易语言的自定义数据类型默认也是4字节对齐所以一般情况下没问题。但如果结构体里有字节型或短整数型成员就要小心对齐填充的问题必要时手动加填充字段。还有一个细节结构体作为参数传递时生命周期要保证覆盖整个API调用过程。如果你在一个子程序里定义局部结构体变量取地址传给APIAPI执行完之前这个变量不能被销毁。局部变量的生命周期是子程序执行期间所以只要API调用在同一个子程序内完成就没问题。但如果你把地址存起来异步使用那就必须用全局变量或者手动分配内存。3.3 返回值与错误码的处理系统API的返回值含义各不相同。有的返回句柄失败时返回0有的返回布尔值1表示成功0表示失败有的返回具体数值。判断调用是否成功不能一概而论要看具体函数的文档说明。当API调用失败时可以调用GetLastError获取错误码。这个函数在kernel32.dll里返回一个整数型错误码。常见的GDI错误码比如6表示句柄无效87表示参数错误。不过说实话在实际开发中GDI函数失败的原因往往比较隐蔽错误码不一定能直接定位问题。我的经验是在关键调用之后加日志输出把返回值、参数值都记录下来出问题的时候对照排查比单纯看错误码有效得多。另外要提醒一点GetLastError返回的是“最近一次失败”的错误码如果你在API调用失败之后又调用了其他API这个错误码可能被覆盖。所以正确的做法是API调用失败后立即调用GetLastError不要中间插入其他操作。4. 从零搭建一个双缓冲绘图框架4.1 为什么必须用双缓冲先说说不用双缓冲会怎样。如果你直接在窗口DC上绘制每次重绘的时候系统会先发WM_ERASEBKGND消息擦除背景然后再发WM_PAINT消息让你绘制。擦除和绘制之间有个时间差用户看到的就是闪烁。数据量越大、绘制越复杂闪烁越明显。我最早做的那个波形工具就是这样每刷新一次闪一次眼睛都快看瞎了。双缓冲的思路很简单先在内存里画好一整张图然后一次性把这张图贴到窗口上。因为贴图是一个原子操作中间没有擦除步骤所以不会闪烁。具体做法是创建一个内存DC创建一个和窗口客户区同样大小的内存位图把位图选进内存DC所有绘制操作都往内存DC上画画完之后用BitBlt函数把内存DC的内容一次性复制到窗口DC。这个方案的好处不仅仅是消除闪烁。因为所有绘制都在内存里完成你可以在贴图之前对整张图做后处理比如整体调色、加滤镜、叠加水印。而且内存DC的绘制不依赖窗口是否可见即使窗口被遮挡或者最小化绘制逻辑照样能跑数据不会丢。4.2 内存DC与兼容位图的创建创建内存DC用CreateCompatibleDC函数传入窗口DC作为参数系统会返回一个和窗口DC兼容的内存DC。所谓“兼容”主要是指颜色格式和位深度一致这样后面贴图的时候不需要做颜色转换速度快。有了内存DC接下来创建兼容位图用CreateCompatibleBitmap传入窗口DC和位图的宽高。位图的宽高应该和窗口客户区一致这样贴图的时候才能完整覆盖。创建好位图之后用SelectObject把它选进内存DC。注意刚创建的内存DC默认只有一张1×1像素的单色位图必须选入自己的位图之后才能正常绘制。这里有个资源管理的要点内存DC和位图都是GDI对象用完之后必须销毁。销毁的顺序是先把原来的位图选回内存DCSelectObject返回的那个旧位图句柄然后DeleteObject删除自己创建的位图最后DeleteDC删除内存DC。顺序反了会导致资源泄漏或者程序崩溃。窗口大小变化的时候内存DC和位图需要重建。我的做法是在窗口尺寸改变的消息处理里先销毁旧的内存DC和位图然后按新尺寸重新创建。重建的时机很重要太频繁会影响性能太晚又会导致绘制区域不对。一般是在WM_SIZE消息里做但要注意WM_SIZE可能被连续触发多次可以加一个判断只有尺寸真正变化了才重建。4.3 绘制流程的完整代码结构下面给出一个完整的绘制框架结构。我用的是伪代码加关键API调用的形式你可以直接对应到易语言的DLL命令和子程序里。首先是初始化部分在窗口创建完成后执行 获取窗口DC 窗口DC GetDC(窗口句柄) 创建内存DC 内存DC CreateCompatibleDC(窗口DC) 获取客户区尺寸 GetClientRect(窗口句柄, 取变量地址(客户区矩形)) 位图宽 客户区矩形.right - 客户区矩形.left 位图高 客户区矩形.bottom - 客户区矩形.top 创建兼容位图 内存位图 CreateCompatibleBitmap(窗口DC, 位图宽, 位图高) 选入内存DC保存旧位图句柄 旧位图 SelectObject(内存DC, 内存位图) 释放窗口DC注意GetDC获取的DC必须ReleaseDC释放 ReleaseDC(窗口句柄, 窗口DC)然后是绘制部分在需要刷新的时候调用 设置背景模式为透明避免文字有底色 SetBkMode(内存DC, 1) 创建画笔 新画笔 CreatePen(0, 1, 颜色值) 旧画笔 SelectObject(内存DC, 新画笔) 执行绘制操作比如画线 MoveToEx(内存DC, 起点X, 起点Y, 0) LineTo(内存DC, 终点X, 终点Y) 恢复旧画笔 SelectObject(内存DC, 旧画笔) DeleteObject(新画笔) 贴图到窗口 窗口DC GetDC(窗口句柄) BitBlt(窗口DC, 0, 0, 位图宽, 位图高, 内存DC, 0, 0, 13369376) ReleaseDC(窗口句柄, 窗口DC)最后是清理部分在窗口销毁时执行SelectObject(内存DC, 旧位图) DeleteObject(内存位图) DeleteDC(内存DC)这个结构看起来简单但每个环节都有细节要注意。比如BitBlt的最后一个参数是光栅操作码13369376对应的是SRCCOPY表示直接复制源像素。这个值你可以用计算器算也可以用易语言的位运算拼出来。我习惯直接写常量省得每次算。5. 常见绘图函数的实战用法5.1 线条与形状绘制画线用MoveToEx加LineTo的组合。MoveToEx设置当前画笔位置LineTo从当前位置画一条线到指定位置同时把当前位置更新到终点。如果要画多条连续的线只需要多次调用LineTo即可。注意MoveToEx的第三个参数可以传0表示不需要获取之前的位置。画矩形用Rectangle传入左上角和右下角坐标。边框用当前选中的画笔内部用当前选中的画刷填充。如果不想填充可以把画刷选成NULL_BRUSH系统预定义的空画刷句柄值为5。画椭圆用Ellipse参数和矩形一样椭圆内切于指定的矩形。画圆角矩形用RoundRect多两个参数指定圆角的宽高。画多边形用Polygon需要传一个点数组的地址和点的数量。在易语言里点数组可以用自定义数据类型POINTAPI的数组然后取数组地址传进去。注意数组在内存里是连续存放的每个点占8字节两个整数型所以点的数量要准确否则会读到越界内存。这里分享一个实用技巧批量绘制时尽量减少画笔切换。比如你要画100条不同颜色的线不要每画一条就创建和销毁一次画笔。更好的做法是按颜色分组同一种颜色的线一起画这样只需要切换一次画笔。如果颜色种类很多可以预先创建一组画笔放在数组里用的时候直接选用完统一销毁。5.2 文本输出与字体控制输出文本用TextOut函数传入DC、X坐标、Y坐标、文本内容、文本长度。文本内容在易语言里是文本型但API需要的是字节集指针所以要用取变量地址或者先转换成字节集再取地址。文本长度是字节数不是字符数中文一个字占两个字节计算的时候要注意。字体控制用CreateFont创建字体对象参数非常多包括字高、字宽、倾斜角、粗细、下划线、斜体、字符集、精度、字体系列名等。实际使用的时候大部分参数可以传0或默认值重点设置字高、粗细和字体名。创建好之后用SelectObject选进DC用完恢复旧字体并销毁。文本颜色用SetTextColor设置背景模式用SetBkMode设置。如果背景模式设为TRANSPARENT值为1文本背景透明不会覆盖下面的图形。如果设为OPAQUE值为2文本背景会用当前背景色填充。我一般都用透明模式这样文字叠加在图形上比较好看。还有一个常用的函数是GetTextExtentPoint32可以测量一段文本在指定字体下的宽度和高度。这个在做文本居中、自动换行、布局计算的时候非常有用。比如你要把一段文字水平居中先用这个函数测出文本宽度然后用区域宽度减文本宽度除以2得到起始X坐标。5.3 位图操作与图像合成除了画线和文字绘图API还能直接操作位图。BitBlt是最常用的位图复制函数可以把一个DC的内容复制到另一个DC。除了SRCCOPY还有几种常用的光栅操作码SRCAND与运算、SRCOR或运算、SRCINVERT异或运算、SRCPAINT或运算用于透明叠加。利用这些操作码可以实现简单的图像合成效果。比如要实现透明贴图可以先用SRCAND把掩码位图和背景做与运算再用SRCPAINT把彩色位图贴上去。这个原理和Windows早期图标透明处理是一样的。不过这种方法只支持单色透明也就是要么完全透明要么完全不透明做不了半透明效果。半透明需要用AlphaBlend函数这个函数在msimg32.dll里支持指定透明度。StretchBlt是BitBlt的增强版可以在复制的同时缩放图像。参数比BitBlt多两个指定目标宽高。缩放模式用SetStretchBltMode设置常用的有COLORONCOLOR直接删像素速度快但质量差和HALFTONE半色调质量好但速度慢。如果缩放比例不大用COLORONCOLOR就够了如果要大幅缩小图像建议用HALFTONE否则会有严重的锯齿。6. 资源管理与性能优化避坑指南6.1 GDI对象泄漏的排查方法GDI对象泄漏是绘图程序最常见的问题症状是程序运行一段时间后变卡严重时整个系统都变慢打开任务管理器看GDI对象数一直往上涨。排查方法很简单在任务管理器的“详细信息”标签页里右键列标题勾选“GDI对象”然后观察你的程序这一列的数字。正常情况下这个数字应该稳定在一个范围内波动如果持续增长不回落那就是泄漏了。泄漏的原因通常是创建了GDI对象但没有销毁或者选入了DC但没有选回旧对象。我总结了一个检查清单每次写完绘图代码都对照过一遍每个CreatePen、CreateSolidBrush、CreateFont、CreateCompatibleBitmap、CreateCompatibleDC都有对应的DeleteObject或DeleteDC每次SelectObject都把返回的旧句柄保存了并且在销毁新对象之前把旧句柄选回去了GetDC和ReleaseDC成对出现BeginPaint和EndPaint成对出现异常分支和提前返回的路径上也要保证资源被释放最后一条特别容易被忽略。比如你在绘制过程中判断某个条件不满足就提前返回了但前面创建的资源还没释放。我的做法是把资源释放统一放在子程序的末尾用跳转或者标志位控制确保无论从哪条路径退出都会执行清理。6.2 刷新频率与重绘策略绘图程序的性能瓶颈往往不在绘制本身而在刷新频率。如果你每收到一个数据点就刷新一次界面数据量大的时候CPU全花在重绘上了。正确的做法是控制刷新频率比如用定时器每50毫秒刷新一次把这段时间内积累的数据一次性画出来。另一个优化点是局部重绘。如果只有一小块区域需要更新不要整个窗口重绘。可以用InvalidateRect函数指定需要重绘的矩形区域系统只会对这块区域发WM_PAINT消息。在双缓冲框架下局部重绘的实现稍微复杂一点因为内存位图是整个窗口大小的你需要只把变化区域贴到窗口上。BitBlt可以指定源坐标和目标坐标以及宽高所以只贴变化区域是可行的。还有一个技巧是脏矩形合并。如果短时间内有多个区域需要重绘不要每个区域都触发一次重绘而是把所有脏矩形合并成一个大矩形一次性重绘。这样可以减少重绘次数提高效率。6.3 高DPI环境下的适配问题现在高分辨率屏幕越来越普及在高DPI环境下系统默认会对程序窗口进行缩放导致绘图坐标和实际像素不对应画出来的线条模糊、文字发虚。解决办法是在程序启动时声明DPI感知让程序自己处理缩放。在易语言里可以通过调用SetProcessDPIAware函数来声明DPI感知。这个函数在user32.dll里没有参数调用一次即可。声明之后程序获取到的窗口尺寸就是实际像素尺寸绘图坐标也对应实际像素线条和文字都会清晰很多。不过声明DPI感知之后你需要自己处理缩放逻辑。比如在100%缩放下按钮是30像素高在150%缩放下应该变成45像素高。我的做法是定义一个缩放比例变量在程序启动时根据系统DPI计算出来所有涉及尺寸的地方都乘以这个比例。这样一套代码可以适配不同DPI的屏幕。7. 实操案例实时波形绘制器7.1 需求分析与方案设计假设我们要做一个实时波形绘制器需求是从某个数据源持续接收数值在窗口里画出最近N个点的波形图要求刷新流畅不闪烁支持窗口缩放支持暂停和继续。方案设计上核心是双缓冲加定时刷新。数据接收和界面绘制分离数据接收线程只管往一个环形缓冲区里写数据绘制线程其实就是主线程的定时器每隔固定时间从缓冲区读取最新数据并重绘。这样数据接收不会阻塞界面界面刷新也不会丢数据。缓冲区的大小根据采样率和显示窗口决定。比如采样率是1000Hz显示最近5秒的数据那缓冲区至少需要5000个点。用环形缓冲区的好处是不需要频繁移动内存写入和读取都是O(1)复杂度。7.2 关键代码实现与参数计算坐标映射是波形绘制的核心。假设窗口客户区宽度是W像素要显示N个数据点每个数据点之间的水平间距是W / N像素。数据值的范围假设是0到100窗口高度是H像素那数据值v对应的Y坐标是H - (v / 100) * H也就是把0映射到窗口底部100映射到窗口顶部。 坐标映射函数 子程序 数据转坐标, 整数型, 公开, 把数据值转换为屏幕Y坐标 参数 数据值, 双精度小数型 参数 窗口高度, 整数型 参数 数据最大值, 双精度小数型 返回 (窗口高度 - 数据值 / 数据最大值 * 窗口高度)绘制的时候先清空内存DC用PatBlt填充背景色然后创建画笔从第一个点开始依次LineTo到下一个点。如果数据点很密集比如一个像素对应多个数据点可以做降采样每个像素只取该列数据的最小值和最大值画一条垂直线段。这样既保留了波形特征又减少了绘制量。刷新定时器的间隔设置也有讲究。太短了CPU占用高太长了波形不流畅。一般30到60毫秒比较合适对应大约16到33帧每秒。如果数据更新很快可以在定时器里判断是否有新数据没有新数据就跳过重绘进一步节省资源。7.3 效果验证与性能对比我用同一台机器做了对比测试。用易语言自带画板控件绘制5000个点的波形刷新一次大约需要80到120毫秒CPU占用率在30%以上而且有明显闪烁。改用API双缓冲方案后刷新一次大约需要8到15毫秒CPU占用率降到5%以下完全没有闪烁。数据量越大差距越明显。这个差距主要来自两个方面。一是自带控件每次重绘都会触发完整的消息流程包括背景擦除和控件内部的状态维护开销大。二是自带控件的绘制没有硬件加速而API的BitBlt在多数情况下会走显卡的快速路径。所以如果你的程序涉及大量图形绘制强烈建议走API这条路。8. 几个容易踩的坑和排查技巧8.1 画不出来的几种常见原因第一种情况是选错了DC。比如你在WM_PAINT消息里用GetDC获取DC画完之后没有释放下次重绘的时候又获取一个新的旧的DC一直没释放最后系统DC耗尽什么都画不出来。正确的做法是在WM_PAINT里用BeginPaint和EndPaint在其他地方用GetDC和ReleaseDC。第二种情况是画笔颜色和背景色一样。这个听起来很蠢但实际排查的时候经常遇到。特别是用黑色背景的时候默认画笔也是黑色画出来的线完全看不见。解决办法是显式创建画笔并设置一个对比明显的颜色。第三种情况是坐标超出了可见区域。比如窗口大小是800×600你画的线坐标是(1000, 700)那肯定看不见。排查的时候可以在绘制前输出一下坐标值确认在有效范围内。第四种情况是忘记恢复旧对象。如果你把画笔选入DC之后没有选回旧画笔下次绘制的时候DC里还是你上次选的画笔可能导致颜色或线型不对。更严重的是如果你在选入新画笔之前把旧画笔删了DC里就有一个悬空句柄后续绘制行为不可预测。8.2 颜色显示异常的排查颜色不对通常有三个原因。一是字节序问题前面说过API用的是BGR顺序如果你按RGB顺序传红色和蓝色会颠倒。二是调色板问题在某些老旧的显示模式下颜色会被映射到最近的调色板颜色导致偏色。现在基本都是真彩色显示这个问题很少见了。三是Alpha通道问题如果你从带透明通道的位图里取颜色忽略Alpha值可能导致颜色变淡或变深。排查颜色问题的方法很简单画一个纯红色的矩形然后用取色工具看看实际显示的颜色值。如果红色和蓝色互换了那就是字节序问题。如果颜色整体偏暗可能是Alpha通道没处理好。8.3 窗口缩放时的重绘处理窗口缩放时客户区尺寸变了内存位图也需要重建。如果重建不及时贴图的时候就会出错表现为画面拉伸、错位或者部分区域空白。正确的处理流程是在WM_SIZE消息里先销毁旧的内存DC和位图然后按新尺寸重新创建最后触发一次重绘。但这里有个陷阱WM_SIZE消息可能在窗口创建过程中就被触发那时候你的内存DC还没初始化直接去销毁会出错。所以要在销毁之前判断句柄是否有效无效就跳过。另外窗口最小化的时候客户区尺寸会变成0这时候创建0×0的位图会失败也要做特殊处理尺寸为0时直接跳过重建。还有一个细节是缩放过程中的刷新。用户拖动窗口边缘的时候WM_SIZE会连续触发很多次如果每次都重建内存位图性能会很差。我的做法是加一个延时收到WM_SIZE后不立即重建而是启动一个短定时器比如100毫秒定时器到期后再重建。这样连续缩放过程中只会重建一次流畅度好很多。9. 进阶方向与扩展思路掌握了基础的绘图API之后可以往几个方向深入。一个是图像处理利用BitBlt和像素操作实现滤镜效果比如灰度化、反色、模糊、锐化。这些操作的原理都是直接读写位图的像素数据用GetDIBits和SetDIBits可以在位图和字节数组之间转换然后对字节数组做运算。另一个方向是自定义控件。用绘图API完全从零绘制一个按钮、滑块、进度条外观和交互都可以自己定义不受系统控件样式的限制。做出来的界面可以非常个性化适合有特定视觉要求的项目。还有一个方向是离屏渲染与打印输出。绘图API不仅可以画到窗口上还可以画到内存位图或者打印机DC上。这意味着你可以用同一套绘制代码既在屏幕上显示又输出到打印机或者保存为图片文件。做报表、图表导出的时候非常实用。我个人在实际操作中的体会是绘图API的学习曲线前期比较陡主要是概念多、参数多、容易出错。但一旦把双缓冲框架搭起来后面就是不断往里面加绘制逻辑越用越顺手。最关键的是把资源管理的习惯养好创建和销毁严格配对这样代码跑多久都不会出问题。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑