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+绘图流程
-
工作线程:
-
生成签名点数据
-
将点添加到线程安全队列
-
定期发送
WM_UPDATE_SIGNATURE消息通知UI线程
-
-
UI线程:
-
在
OnUpdateSignature消息处理中调用UpdateSignatureDisplay -
从队列取出点数据并绘制到内存位图
-
在
OnPaint中将内存位图绘制到窗口
-
4. 内存位图管理
-
初始化:在
InitMemBitmap中创建与窗口大小匹配的位图 -
更新:在
UpdateSignatureDisplay中绘制新点 -
渲染:在
OnPaint中复制到窗口 -
重置:在
OnSize中响应窗口大小变化
常见问题解决
1. 内存泄漏问题
-
确保所有GDI+对象在析构函数中正确释放
-
使用RAII技术管理资源
-
使用调试工具检查内存泄漏
2. 线程同步问题
-
使用
std::mutex或CCriticalSection保护共享数据 -
使用原子操作
std::atomic<bool>控制线程状态 -
避免在UI线程执行耗时操作
3. 绘图性能优化
-
批量处理点数据,减少绘制调用次数
-
使用
InvalidateRect代替Invalidate减少重绘区域 -
使用双缓冲技术
-
避免频繁创建/销毁GDI对象
浙公网安备 33010602011771号