代码改变世界

GDI+实时绘制签名

2025-05-30 11:53  一只老老老菜鸟  阅读(11)  评论(0)    收藏  举报

思路:

1. 工作线程处理签名点数据(从设备获取,循环上报数据)

2. 工作线程将处理后的点数据通过消息发送到UI线程,或使用定时器

3. UI线程接收到消息后,将点数据绘制到内存位图

4. UI线程将内存位图绘制到窗口上

关键点:

- 工作线程不能直接操作UI,通过发送自定义消息给UI线程

- UI线程收到消息后,在消息处理函数中安全地更新内存位图并刷新窗口,使用双缓冲避免闪烁

- 多线程访问资源,同步问题

 

 

完整实现代码

MyDialog.h 头文件

 1 #pragma once
 2 #include <gdiplus.h>
 3 #include <afxtempl.h>
 4 #include <mutex>
 5 
 6 #define WM_UPDATE_SIGNATURE (WM_USER + 100)
 7 
 8 class CMyDialog : public CDialogEx {
 9     DECLARE_DYNAMIC(CMyDialog)
10 public:
11     CMyDialog(CWnd* pParent = nullptr);
12     virtual ~CMyDialog();
13 
14 protected:
15     virtual void DoDataExchange(CDataExchange* pDX);
16     virtual BOOL OnInitDialog();
17     virtual void OnCancel();
18     
19     // 签名点结构
20     struct SignaturePoint {
21         int x;
22         int y;
23         bool isPenDown;
24     };
25     
26     // 线程安全队列
27     class ThreadSafeQueue {
28     public:
29         void Push(const SignaturePoint& point) {
30             std::lock_guard<std::mutex> lock(m_mutex);
31             m_points.push(point);
32         }
33         
34         bool Pop(SignaturePoint& point) {
35             std::lock_guard<std::mutex> lock(m_mutex);
36             if (m_points.empty()) return false;
37             point = m_points.front();
38             m_points.pop();
39             return true;
40         }
41         
42         bool Empty() {
43             std::lock_guard<std::mutex> lock(m_mutex);
44             return m_points.empty();
45         }
46         
47         void Clear() {
48             std::lock_guard<std::mutex> lock(m_mutex);
49             while (!m_points.empty()) m_points.pop();
50         }
51         
52     private:
53         std::queue<SignaturePoint> m_points;
54         std::mutex m_mutex;
55     };
56     
57     ThreadSafeQueue m_pointQueue;  // 线程安全点队列
58     
59     // GDI+对象
60     Gdiplus::Pen* m_pPen;
61     Gdiplus::Bitmap* m_pMemBitmap;
62     Gdiplus::Graphics* m_pMemGraphics;
63     
64     // 工作线程相关
65     CWinThread* m_pWorkerThread;
66     std::atomic<bool> m_bWorkerThreadRunning;
67     static UINT WorkerThreadProc(LPVOID pParam);
68     
69     // 消息处理
70     afx_msg void OnPaint();
71     afx_msg void OnSize(UINT nType, int cx, int cy);
72     afx_msg LRESULT OnUpdateSignature(WPARAM wParam, LPARAM lParam);
73     
74     // 辅助函数
75     void InitMemBitmap();
76     void UpdateSignatureDisplay();
77     
78     DECLARE_MESSAGE_MAP()
79 };

 

 

MyDialog.cpp 实现文件

  1 #include "stdafx.h"
  2 #include "MyDialog.h"
  3 #include "resource.h"
  4 #include <gdiplus.h>
  5 #include <cmath>
  6 
  7 using namespace Gdiplus;
  8 
  9 IMPLEMENT_DYNAMIC(CMyDialog, CDialogEx)
 10 
 11 CMyDialog::CMyDialog(CWnd* pParent) 
 12     : CDialogEx(IDD_MYDIALOG, pParent) {
 13     m_pPen = new Pen(Color(255, 0, 0, 255), 3); // 蓝色画笔,3像素宽
 14     m_pMemBitmap = nullptr;
 15     m_pMemGraphics = nullptr;
 16     m_pWorkerThread = nullptr;
 17     m_bWorkerThreadRunning = false;
 18 }
 19 
 20 CMyDialog::~CMyDialog() {
 21     // 停止工作线程
 22     m_bWorkerThreadRunning = false;
 23     if (m_pWorkerThread) {
 24         WaitForSingleObject(m_pWorkerThread->m_hThread, 1000);
 25     }
 26     
 27     // 清理GDI+对象
 28     delete m_pPen;
 29     delete m_pMemGraphics;
 30     delete m_pMemBitmap;
 31 }
 32 
 33 void CMyDialog::DoDataExchange(CDataExchange* pDX) {
 34     CDialogEx::DoDataExchange(pDX);
 35 }
 36 
 37 BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
 38     ON_WM_PAINT()
 39     ON_WM_SIZE()
 40     ON_MESSAGE(WM_UPDATE_SIGNATURE, OnUpdateSignature)
 41 END_MESSAGE_MAP()
 42 
 43 BOOL CMyDialog::OnInitDialog() {
 44     CDialogEx::OnInitDialog();
 45     
 46     // 设置对话框标题
 47     SetWindowText(_T("多线程签名绘制演示"));
 48     
 49     // 初始化内存位图
 50     InitMemBitmap();
 51     
 52     // 启动工作线程
 53     m_bWorkerThreadRunning = true;
 54     m_pWorkerThread = AfxBeginThread(WorkerThreadProc, this);
 55     
 56     return TRUE;
 57 }
 58 
 59 void CMyDialog::OnCancel() {
 60     // 停止工作线程
 61     m_bWorkerThreadRunning = false;
 62     
 63     CDialogEx::OnCancel();
 64 }
 65 
 66 // 初始化内存位图(根据窗口客户区大小)
 67 void CMyDialog::InitMemBitmap() {
 68     CRect rect;
 69     GetClientRect(&rect);
 70     
 71     // 清理旧资源
 72     if (m_pMemBitmap) {
 73         delete m_pMemBitmap;
 74         m_pMemBitmap = nullptr;
 75     }
 76     if (m_pMemGraphics) {
 77         delete m_pMemGraphics;
 78         m_pMemGraphics = nullptr;
 79     }
 80     
 81     // 创建与窗口客户区相同大小的内存位图
 82     if (rect.Width() > 0 && rect.Height() > 0) {
 83         m_pMemBitmap = new Bitmap(rect.Width(), rect.Height(), PixelFormat32bppARGB);
 84         m_pMemGraphics = new Graphics(m_pMemBitmap);
 85         
 86         // 设置抗锯齿和清空背景
 87         if (m_pMemGraphics) {
 88             m_pMemGraphics->SetSmoothingMode(SmoothingModeAntiAlias);
 89             m_pMemGraphics->Clear(Color(255, 255, 255)); // 白色背景
 90         }
 91     }
 92 }
 93 
 94 void CMyDialog::OnPaint() {
 95     CPaintDC dc(this);
 96     
 97     // 将内存位图绘制到窗口
 98     if (m_pMemBitmap) {
 99         Graphics graphics(dc.GetSafeHdc());
100         graphics.DrawImage(m_pMemBitmap, 0, 0);
101     }
102     
103     // 绘制标题
104     Font font(L"Arial", 14);
105     SolidBrush brush(Color(100, 0, 0, 0));
106     CRect rect;
107     GetClientRect(&rect);
108     if (m_pMemGraphics) {
109         m_pMemGraphics->DrawString(L"多线程签名绘制演示", -1, &font, PointF(10, 10), &brush);
110     }
111 }
112 
113 void CMyDialog::OnSize(UINT nType, int cx, int cy) {
114     CDialogEx::OnSize(nType, cx, cy);
115     
116     if (nType != SIZE_MINIMIZED) {
117         // 窗口大小变化时重新初始化内存位图
118         InitMemBitmap();
119         
120         // 重绘
121         Invalidate();
122     }
123 }
124 
125 // 工作线程函数
126 UINT CMyDialog::WorkerThreadProc(LPVOID pParam) {
127     CMyDialog* pDialog = (CMyDialog*)pParam;
128     
129     // 模拟签名数据生成
130     int centerX = 200;
131     int centerY = 150;
132     int radius = 100;
133     int angle = 0;
134     
135     while (pDialog->m_bWorkerThreadRunning) {
136         // 计算点位置(螺旋线)
137         double rad = angle * 3.14159 / 180.0;
138         double spiralRadius = radius * (angle / 720.0);
139         int x = centerX + static_cast<int>(spiralRadius * cos(rad));
140         int y = centerY + static_cast<int>(spiralRadius * sin(rad));
141         
142         // 创建点数据
143         SignaturePoint point;
144         point.x = x;
145         point.y = y;
146         point.isPenDown = true;
147         
148         // 添加到线程安全队列
149         pDialog->m_pointQueue.Push(point);
150         
151         // 每生成10个点发送一次更新消息
152         if (angle % 10 == 0) {
153             pDialog->PostMessage(WM_UPDATE_SIGNATURE);
154         }
155         
156         angle = (angle + 5) % 720;
157         
158         // 模拟数据处理延迟
159         Sleep(15);
160     }
161     
162     return 0;
163 }
164 
165 // 更新签名显示
166 void CMyDialog::UpdateSignatureDisplay() {
167     if (!m_pMemGraphics) return;
168     
169     // 从队列中取出所有点并绘制
170     SignaturePoint prevPoint = {0, 0, false};
171     SignaturePoint currPoint;
172     bool firstPoint = true;
173     
174     while (m_pointQueue.Pop(currPoint)) {
175         if (!firstPoint && prevPoint.isPenDown && currPoint.isPenDown) {
176             m_pMemGraphics->DrawLine(
177                 m_pPen, 
178                 Point(prevPoint.x, prevPoint.y),
179                 Point(currPoint.x, currPoint.y)
180             );
181         }
182         prevPoint = currPoint;
183         firstPoint = false;
184     }
185     
186     // 重绘窗口
187     Invalidate(FALSE);
188 }
189 
190 // 处理更新消息
191 LRESULT CMyDialog::OnUpdateSignature(WPARAM wParam, LPARAM lParam) {
192     UpdateSignatureDisplay();
193     return 0;
194 }

MyApp.h 应用程序类

 1 #pragma once
 2 #include <gdiplus.h>
 3 
 4 class CMyApp : public CWinApp {
 5 public:
 6     ULONG_PTR m_gdiplusToken;
 7     
 8     virtual BOOL InitInstance() {
 9         // 初始化GDI+
10         Gdiplus::GdiplusStartupInput input;
11         Gdiplus::GdiplusStartup(&m_gdiplusToken, &input, NULL);
12         
13         // 创建并显示对话框
14         CMyDialog dlg;
15         m_pMainWnd = &dlg;
16         dlg.DoModal();
17         
18         return FALSE;
19     }
20     
21     virtual int ExitInstance() {
22         // 清理GDI+
23         Gdiplus::GdiplusShutdown(m_gdiplusToken);
24         return CWinApp::ExitInstance();
25     }
26     
27     // 应用程序实例
28     CMyApp theApp;
29 };

解决方案设计说明

1. 多线程架构

  • UI线程:主线程,负责窗口创建、消息处理和绘图

  • 工作线程:WorkerThreadProc,上报数据,处理数据

  • 通信机制:使用自定义消息WM_UPDATE_SIGNATURE进行线程间通信

2. 线程安全数据传递

  使用同步对象保护共享数据

3. GDI+绘图流程

  1. 工作线程:

    • 生成签名点数据

    • 将点添加到线程安全队列

    • 定期发送WM_UPDATE_SIGNATURE消息通知UI线程

  2. UI线程:

    • OnUpdateSignature消息处理中调用UpdateSignatureDisplay

    • 从队列取出点数据并绘制到内存位图

    • OnPaint中将内存位图绘制到窗口

4. 内存位图管理

  • 初始化:在InitMemBitmap中创建与窗口大小匹配的位图

  • 更新:在UpdateSignatureDisplay中绘制新点

  • 渲染:在OnPaint中复制到窗口

  • 重置:在OnSize中响应窗口大小变化

常见问题解决

1. 内存泄漏问题

  • 确保所有GDI+对象在析构函数中正确释放

  • 使用RAII技术管理资源

  • 使用调试工具检查内存泄漏

2. 线程同步问题

  • 使用std::mutexCCriticalSection保护共享数据

  • 使用原子操作std::atomic<bool>控制线程状态

  • 避免在UI线程执行耗时操作

3. 绘图性能优化

  • 批量处理点数据,减少绘制调用次数

  • 使用InvalidateRect代替Invalidate减少重绘区域

  • 使用双缓冲技术

  • 避免频繁创建/销毁GDI对象