由于WinApi诞生之时还没有Material Design这样美观的视觉表达规范,其系统控件样式相当的匮乏且充满工程师设计风格,因此大多数时候,控件都需要实现自定义绘图,即使只是简单的设置背景颜色。
完成了上面最简单的视窗控件后,我们来给它添加一个背景色。
绘图方法在系统更新控件时被调用。因此它也依赖于消息循环。我们可以在消息的定义文件中找到它。
与OnClose一样的添加方式
该方法的参数 CDCHandle 是一个用于绘图的对象但是我们并不能直接使用这个参数。
简要解析一下绘图对象
前往CDCHandle的定义
可以看到他是一个CDCT类不同泛型参数的别名,与之对应的还有一个CDC类
接下来前去看看CDCT的泛型参数发挥了什么作用
通过CDCT的定义 可以看出来 泛型参数 t_bManaged声明了一个CDCT对象所持有的HDC对象是否由自己管理,进而在CDCT销毁时销毁HDC。
此处的HDC是用于绘图的由WinApi定义的句柄。
简单来说这样的做法是为了绘图器持有的句柄可以被以值传递的方式传递,不影响句柄。但是最终句柄只会在创建者销毁时销毁
当然就算不理解也没关系。知道接下来应该这么做就行了。
使用CPrintDC类,它在ATL中被定义用来从窗口句柄获取HDC句柄
看一看CPrintDC的定义
使用CPrintDC
HBRUSH是WinApi提供的画刷工具 CreateSolidBrush是创建单色画刷,这里我们创建一个保护色画刷
GetClientRect是获取当前控件相对于自身的位置。也就是自己的大小。传递一个CRect的地址会对它赋值。
FillRect则是对对应的区域指定画刷。也就是绘图操作。
动态绘图
如果你接触过其他平台的界面API应该可以直接想到下面这个注意点:
绘图操作仅可以由系统调用绘图方法是一个特定过程执行的方法。不能主动调用
比如如下操作
通过消息添加一个鼠标点击的响应事件,当鼠标左键在窗口内点击的时候会调用OnLbuttonDown
在此处添加想要做的操作
Invalidate() 方法是可以用来更新控件的方法,他会使得该控件失效并由系统在空闲的时间更新。
UpdateWindow() 方法可以让失效的控件立即刷新,也就会重新绘制。如果不需要立即刷新 可以不用添加。
可以看到,不论是直接使用画刷 还是在外部调用OnPaint方法 都没有让界面颜色发生改变。
界面变为蓝色 实际上是系统自动调用了OnPaint方法之后生效的。
接下来我用这段代码演示一下这个过程
黑色方块缺失是因为GIF录制会有丢帧
添加的代码在鼠标点击时先绘制背景然后在鼠标周围绘制一个黑点。在鼠标抬起时将黑点的位置取消,然后再次更新绘图。
然后补充一点功能
画笔CPen和文字绘制
void printLine( CDCHandle dc) {
CPen pen;
pen.CreatePen(PS_SOLID, 2, RGB(255, 255, 255));
dc.SelectPen(pen.m_hPen);
CPoint start;
start.x = 10;
start.y = 10;
dc.MoveTo(start);
CPoint dest;
dest.x = mouseClickLocation.left;
dest.y = mouseClickLocation.top;
if (dest.x != 0)
{
dc.LineTo(dest);
}
}
void printText(CDCHandle dc) {
CString text;
CRect textLocation;
text = "hello world";
dc.SetTextColor(RGB(0, 0, 0));
dc.ExtTextOutA(mouseClickLocation.left, mouseClickLocation.top, ETO_OPAQUE, NULL, text, text.GetLength(), NULL);
}
通常来说如果你对 CPringDC的调用是一次独立绘图的话
你应该在绘图前使用SaveDC保存并在最后对它调用
RestoreDC(-1) 方法以恢复你使用之前的状态
也许你注意到了 这里我已经将OnPaint方法替换为了DoPaint方法,这牵扯到接下来要补充的一点
双缓冲
在进行绘图时 如果你反复重绘,会因为绘图事会绘制一部分 显示一部分,重绘过快就会在上一次绘制完成前就进行下一次绘制,进而导致闪烁。
此时就需要用到 CDoubleBufferImpl,它被设计成类一个基类,相当于扩展,此时产生多继承。
它的实现
CDoubleBufferImpl自身接收了会调用绘图的消息。然后提供了一个接口 DoPaint
因此要这样使用
1.添加类继承
2.添加消息链接
CHAIN_MSG_MAP可以将对应类中的消息链接到当前类中
由于CDoubleBufferImpl已经实现了MSG_WM_PAINT(OnPaint),因此需要注释掉。
3.实现DoPaint方法
也就是将你本来需要绘图的内容都放到这里。DC已经在CDoubleBufferImpl中被获取并传递到DoPaint了,因此直接使用就行了。
看看效果
在鼠标移动事件中如果进行复杂绘图有可能导致绘图错误,这可能是由于鼠标回报率(中高端鼠标可以达到1000HZ)过高对绘图的调用超过GPU的渲染能力导致的。这种情况下需要主动降低绘图频率,如限制在60帧内之类的方案,同时对独立窗口是分别独立绘制的,所以应该尽量避免大量自定义绘图窗口同时重绘
关于绘图 这里就不过多延申了。