
一、它演示什么
画板上的图形不是像素,而是被录制的绘制命令:每画一笔记一条命令,控件重绘(被盖住又露出、改大小)时按序回放 —— 所以没有"擦掉某一笔"的接口。示例把每条命令另存进一张固定数组,它才是可撤销的"真相来源":撤销 = 丢掉最后一条 → 清空记录 → 重放剩下的。
鼠标事件挂在画板自己(mousedown / mousemove / mouseup),按下时运行时自动捕获鼠标,拖出边界也不断笔;直线 / 矩形 / 椭圆在拖动中有橡皮筋预览,松手才提交。
下面是「录制-回放」模型的核心流程:
二、怎么用
- 在项目根编译:
build-app.bat drawpad,双击examples\drawpad\drawpad.exe。 - 顶部「工具」下拉选一种(直线 / 矩形 / 椭圆 / 文字 / 自由笔);点「颜色」色块弹系统取色器;「笔宽」数值框调 1…12。
- 在白色画板上按下拖动:直线 / 矩形 / 椭圆拖动中就有预览、松手提交;文字工具单击一次落一个字;自由笔一路划过去逐段提交。
- 「撤销」退掉最后一笔,「重做」放回来,「清空」全部丢弃;状态栏三段分别是当前工具、命令条数、最近一次操作。
- 「导出 PNG」把画板截图存进用户数据目录(文件名带时间戳);底部终端会记下每一步作图 / 撤销 / 导出。
- 命令行:
drawpad.exe(自动找同名.cgui)、drawpad.exe xxx.cgui、--selftest。
三、一屏总览
| 控件 id | 类型 | 干什么 |
|---|---|---|
cbo_tool | combo | 工具:0 直线 / 1 矩形 / 2 椭圆 / 3 文字 / 4 自由笔 |
cp_ink | colorpicker | 当前色(value = 0xRRGGBB),点它弹系统取色器 |
edt_pen | edit(spin) | 笔宽数值框,1…12 |
btn_undo / btn_redo / btn_clear | button | 撤销 / 重做 / 清空,共用一个回调 |
btn_export | button | 导出 PNG |
cv_board | canvas | 画板本身,挂 mousedown / mousemove / mouseup |
term_log | terminal | 每一步操作记一行 |
sb_main | statusbar | 三段:当前工具 / 命令条数 / 最近操作 |
四、关键接口
- 画板绘制:
cg_canvas_clear、cg_canvas_set_color、cg_canvas_set_line_width、cg_canvas_line、cg_canvas_rect、cg_canvas_ellipse、cg_canvas_text - 鼠标坐标:
cg_event_x、cg_event_y(就是画板内的逻辑像素) - 截图与落盘:
cg_widget_screenshot、cg_app_data_dir、cg_path_join - 记录与状态:
cg_term_write、cg_status_set、cg_status_setf
五、录制-回放 vs 像素擦除
| 维度 | 录制-回放(本示例) | 像素擦除(位图) | 结论 |
|---|---|---|---|
| 撤销粒度 | 按「命令」撤销,可精确回退到任意一笔 | 只能整块擦除像素,无法还原被覆盖的笔画 | 录制-回放更精细,支持逐笔撤销 |
| 内存占用 | 只存命令参数(类型 + 坐标 + 颜色 + 笔宽),固定数组上限 kMaxOps = 4096 | 每帧都要存整张位图,画布越大内存越高 | 录制-回放更省内存,且占用可预估 |
| 缩放质量 | 重放时按当前尺寸重新绘制,任意缩放都清晰 | 位图缩放会模糊 / 锯齿,需额外重采样 | 录制-回放缩放无损,质量更高 |
| 实现复杂度 | 需维护命令表 + 重放逻辑,预览要「临时命令 + 整表重放」 | 直接改像素,实现直观但撤销 / 缩放难做 | 录制-回放略复杂,但换来前三个维度的收益 |
两种方案的取舍可以这样看:
六、容易踩的坑
- 鼠标事件必须挂在画板这个子控件上,不能挂窗口级
app.events—— 按下 / 移动 / 抬起落在画板上时消息只送给画板,父窗口收不到(早先挂窗口的写法在真机上"按下拖动毫无反应")。 - 画板事件里的坐标就是画板内像素,不用再减
cg_widget_x/cg_widget_y。 - 画板的绘制是"录制 → 回放"模型,没有"画一笔但不作数"的接口,所以拖动预览只能"把临时命令写进命令表空槽 + 整表重放"(见
preview_op)。 - 命令表是固定数组(上限
kMaxOps = 4096):满了只报一次提示并拒绝起笔;自由笔贴着上一个点 2px 以内的移动不记,免得几秒见底。 - 画板重绘走双缓冲,所以"每步重放整张画布"的预览不会闪。
七、常见问题排查
Q1:画板无响应,按下拖动毫无反应?
- 原因:鼠标事件挂错了对象,挂到了窗口级
app.events而不是画板子控件cv_board上。 - 解决:把
mousedown/mousemove/mouseup全部挂到cv_board上;按下时运行时自动捕获鼠标,拖出边界也不断笔。
Q2:拖动预览闪烁?
- 原因:画板重绘没有走双缓冲,或预览时直接改像素导致整块重绘。
- 解决:确认画板重绘走双缓冲;预览用「临时命令写进命令表空槽 + 整表重放」的方式,避免逐像素直接改。
Q3:命令表满了,画不进去?
- 原因:命令表是固定数组,上限
kMaxOps = 4096,画满后拒绝起笔。 - 解决:先「清空」或「撤销」释放空间;自由笔贴着上一个点 2px 以内的移动不记,可有效延缓命令表见底。
Q4:导出 PNG 失败?
- 原因:用户数据目录不可写,或文件名带非法字符。
- 解决:确认
cg_app_data_dir返回的目录存在且可写;导出文件名带时间戳,若仍失败请检查磁盘空间与目录权限。
Q5:文字工具单击落字位置偏移?
- 原因:文字工具单击落字时,坐标换算用了窗口坐标而不是画板内坐标,或没考虑画板在窗口内的偏移。
- 解决:确认落字坐标直接用
cg_event_x/cg_event_y(就是画板内逻辑像素),不要再减cg_widget_x/cg_widget_y。
问题速查表
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
| Q1 画板无响应 | 按下拖动毫无反应 | 鼠标事件挂到了窗口级 app.events,而非画板子控件 cv_board | 把 mousedown / mousemove / mouseup 全部挂到 cv_board 上 |
| Q2 预览闪烁 | 拖动中画布闪烁 | 画板重绘未走双缓冲,或预览时直接改像素 | 重绘走双缓冲;预览用「临时命令 + 整表重放」 |
| Q3 命令表满 | 画不进去,拒绝起笔 | 命令表固定数组上限 kMaxOps = 4096 已满 | 先「清空」或「撤销」释放空间;自由笔 2px 内移动不记 |
| Q4 导出 PNG 失败 | 导出报错或文件未生成 | 用户数据目录不可写,或文件名带非法字符 | 确认 cg_app_data_dir 目录存在且可写;检查磁盘空间与权限 |
| Q5 文字落字偏移 | 单击落字位置不对 | 坐标用了窗口坐标,或没考虑画板偏移 | 直接用 cg_event_x / cg_event_y,不再减 cg_widget_x / cg_widget_y |
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/qq_29246181/article/details/167226799




