本博文由CSDN博主zuishikonghuan所作,版权归zuishikonghuan所有,转载请注明出处:http://blog.csdn.net/zuishikonghuan/article/details/47169605
上一篇博文(GdiplusFlat(1)GDI+平面API:用GDI的思想进行GDI+编程 ,地址:http://blog.csdn.net/zuishikonghuan/article/details/46982559)中,我介绍了GdiplusFlat的使用意义和使用方法——自己声明API原型,自己定义struct,并给出了具体的方法,然后连接Gdiplus.lib。同时说明了GdiplusFlat编程的意义。
那么我们如何用GDI+Flat绘图呢,我们知道,对于GDI,我们通常在窗口的WM_PAINT消息里绘图,这是为什么呢?让我们先来看看WM_PAINT消息是什么。
MSDN:https://msdn.microsoft.com/en-us/library/dd145213.aspx
The WM_PAINT message is sent when the system or another application makes a request to paint a portion of an application's window. The message is sent when the UpdateWindow or RedrawWindow function is called, or by the DispatchMessage function when the application obtains a WM_PAINT message by using the GetMessage or PeekMessage function.
A window receives this message through its WindowProc function.
简单说,就是窗口需要重新绘制时发送WM_PAINT消息。
如果我们不在WM_PAINT消息里面绘图,那么我们绘图后,窗口一刷新,我们绘制的东西就没了,所以,我们在这个消息里绘制,那么每次窗口刷新我们都可以绘制一便,就可以让绘制的图形一直存在了。
WM_PAINT消息使用方法
对于窗口,直接使用WndProc拦截WM_PAINT消息即可,但对于控件(子窗口),必须先子类化!
子类化:我们知道,对于控件(子窗口)(下面只说“控件”)而言,他也是有一个WndProc(窗口过程回调函数)的,但是控件都不是我们自己做的,而是使用的windows的通用控件,因此我们不能直接处理控件的windows消息,因为控件在收到特定消息时会对父窗口发送一个通知,我们一般在父窗口的窗口过程中处理这个通知即可。但不是什么消息都是可以利用通知间接控制的,比如窗口重新绘制的WM_PAINT消息,因此我们就需要子类化掉这个控件,修改控件的窗口过程回调函数,拦截WM_PAINT消息,对于我们不需要处理的消息,再重新丢给控件原来的回调函数,这个问题就解决了,而这种做法,就叫做“窗口子类化”或“窗口消息子类化”。
子类化的方法如下:
我们首先需要一个全局变量:
WNDPROC lastproc;
WNDPROC是回调函数指针类型,用于指向一个窗口过程回调函数。
第二步,我们写一个回调函数,只处理WM_PAINT消息。
对于不处理的消息,不再给DefWindowProc处理了,而是调用CallWindowProc丢给原来的回调函数,像这样:
<pre class="cpp" name="code">LRESULT CALLBACK Control1WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg){ case WM_PAINT: HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hwnd, &ps); //这里先用GDI演示一下绘图 MoveToEx(hdc, 20, 20, NULL); LineTo(hdc, 50, 50); EndPaint(hwnd, &ps); return 0;//告诉系统,WM_PAINT消息我已经处理了,你那儿凉快哪儿玩去吧。 } return CallWindowProc(lastproc, hwnd, uMsg, wParam, lParam);//让原来的窗口回调函数处理其他消息 }
lastproc现在还是一个空指针,别急,在创建控件下面进行子类化。
我就用我之前写的win32窗口控件的源码(参见我之前的博客)上进一步编写:
<pre class="cpp" name="code">case WM_CREATE: //... button1 = CreateWindow(TEXT("Button"), TEXT("默认按钮"), WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 10, 60, 100, 30, hwnd, (HMENU)4, (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE), NULL); SendMessage(button1, WM_SETFONT, (WPARAM)GetStockObject(17), 0); lastproc = (WNDPROC)SetWindowLongPtr(button1, GWL_WNDPROC, (LONG)&Control1WndProc); //...
使用SetWindowLongPtr修改了窗口的回调函数:
第一个参数:要修改的窗口的句柄
第二个参数:GWL_WNDPROC表示修改窗口回调函数
第三个参数:新回调函数的指针
返回值:以前的窗口回调函数指针。
为何用SetWindowLongPtr不用SetWindowLong?原因:便于移植到64位程序。
效果图:
可以看到,原来的“默认按钮”不见了,取而代之的是我们绘制的一条线段。同时,我们发现单击这个直线也能执行父窗口WM_COMMAND里预定的动作!这就CallWindowProc函数又把消息丢给了原来的回调函数的功劳了!
设备上下文:
在上面绘图的过程中,有这么一行代码:
hdc = BeginPaint(hwnd, &ps);
其实这个hdc就是设备上下文句柄。
什么是设备上下文(Device Context)呢?
MSDN的解释:
A device context is a structure that defines a set of graphic objects and their associated attributes, as well as the graphic modes that affect output. Thegraphic objects include a pen for line drawing, a brush for painting and filling, a bitmap for copying or scrolling parts of the screen, a palette for defining the set of available colors, a region for clipping and other operations, and a path for painting and drawing operations. The remainder of this section is divided into the following three areas.
我的理解:
1。设备上下文是一个内核对象,应用程序不需要也无法访问设备上下文struct在内存中的指针,应用程序只能通过设备上下文的句柄(HDC)访问设备上下文。
2。GDI函数的绘图就是基于设备上下文(DC)的
3。设备上下文可以抽象成一个设备场景,相当于给我们提供了一个画板,我们在画板上绘图,由图形驱动程序负责将设备场景中的数据绘制到显存中。
遗憾的是,GDI+虽然是对GDI的封装,但不再使用设备上下文了,使用Graphic对象,但幸运的是,GDI+类其实是对GdiplusFlat封装实现对GDI的间接封装,GdiplusFlat依旧需要使用设备上下文。
如何获取设备上下文句柄(HDC):
1。使用BeginPaint函数和EndPaint函数
就像上面的代码一样。
2。使用GetDC/GetWindowDC函数
参数:窗口句柄
返回值:窗口的设备上下文句柄
注意使用GetDC/GetWindowDC函数创建的DC应使用ReleaseDC释放
3。创建内存场景,比如CreateDC和CreateCompatibleDC函数,我会在以后一篇讲双缓冲自绘的博客中详细说。