当前位置:C++技术网 > 精选软件 > Windows零基础入门:2.20 窗口类结构体之窗口类绘图风格CS_OWNDC

Windows零基础入门:2.20 窗口类结构体之窗口类绘图风格CS_OWNDC

更新时间:2015-09-14 16:58:53浏览次数:1+次

    本节课,我们讲解窗口类风格的另外一组,绘图方面的风格。绘图方面的知识,我们在后面的课程会详细讲解,本节课只是针对这个窗口类风格来讲几个风格的特性作用等。争取对此风格进行整体的了解。至于绘图方面的知识,不做深入介绍。重点是理解这几个的区别和作用。当然,我还是会以理解的形式分析出来,并不会给你造成很大的负担。
    首先说清楚一个概念。前面谈到了水平重绘和垂直重绘,大概接触到了屏幕绘制的一些大概的印象。不过,我们在此基础上,还要深入了解。
    我们在电脑屏幕上看到的,不管是文字,还是图形,还是颜色,还是动画视频,都是绘图做出来的。而绘图的基础的技术就是屏幕刷新和像素点的点亮和熄灭。因为屏幕的刷新,也就是周期性的对屏幕上的所有像素点进行点亮或者熄灭,从而形成各种画面。这是在物理层面的操作。
    然而,我们看到的各种画面,是要转换到物理层面然后执行绘制的。转换的基本思路就是,将各种图形或者文字组成的像素排列,按照行列表格的形式,送给显卡,显卡根据写入的数据,显示对应的画面。比如一个黑白的画面,纯黑和纯白两种颜色,0表示黑,1表示白。那么根据你送入的数据的排列顺序,显卡一次进行屏幕像素点设置,1对应的像素就点亮,0对应的像素就不点亮,在扫描时就直接跳过。这样,从头到尾将屏幕的所有像素点设置一遍,就是刷新一次。刷新频率就是一秒钟这样设置的次数。
    看看下面的一组数据:

000011111000
000100000000
000100000000
000011111000

     你可以看到什么?这些就是我们程序里的一个二维数组,三行十二列的数组而已。但是,这些数据组成了一个图形字母C,将这组数据发送给显卡,显卡在刷新屏幕时,就将这一组数据,从左到右,从上到下的设置数据中的每一个数字所对应的屏幕的像素点,0则跳过,1则点亮像素。跳过即表示不点亮。那么最终屏幕显示的效果如下图所示:

   
    图中其他地方都是黑色的,白色的组成了字母C。这里就是一个简单的示意图了。不要去纠结一些小细节,比如字母只有这些数据,为什么图片那么大呢?因为这是简单的示意,只是表示了字母C的意思,字母所占的像素只是一个局部。还有,这个字母这么丑,这个就不要在意了哈。理解了这个过程是很重要的。
    那么以上这个过程,更加深入分析了重绘和绘制屏幕的概念。当然,这也就是说明一个概念。屏幕上不管是图形还是文字,所有的都是画出来的。很多初学者一开始,总是觉得,文字不属于图形范畴,因为它是规则的,有模有样的,不应该是画的。不过我问一句,不是画的,难道是写的吗?计算机有手吗?
    那么今天讲的三个风格都是与绘画有关。所以,开头就先介绍了一点背景知识。现在开始介绍窗口类的几个绘图相关的风格。不过,在此不会深入讲解绘图的知识,只是概要性的说一下。
    不管是画图、还是显示文字,都是绘图过程。而要绘图就要准备一个绘图需要的工具,我们叫它DC。全名为device context,至于英文意思,就是设备上下文,这个很拗口,这里不做解释,后面图形方面会详细解释的。不过和数码相机的缩写一样哦。可以联想为都与图形图像相关吧。
    而我们的绘图,则是在窗口上的。窗口就是我们程序的界面,我们的绘图也就是对程序的界面绘制。我们在程序中,要先创建或者得到一个DC,也就是画图的工具,然后才能画图。所谓的画图,其实就是调用一个函数而已,并不是要你徒手去设置像素亮暗之类的。放下这个担心吧。只要会用一些函数,就OK。后面会详细讲的。那么画完之后,就释放这个工具。
    而我们一般都是临时用就临时创建一个画图工具DC,然后开始作画。画完就扔掉。这个有点像一个诗人,走到一个地方,发现有了灵感,然后就地找了一个泥巴,在墙上涂了一首诗,写完后,洗个手就OK。
    我们在WM_PAINT中绘图,可以通过BeginPaint()函数得到一个绘图工具DC,也就是绘图工具DC的句柄。然后任意发挥咯。最后,调用EndPaint()函数结束绘图。像这样的绘图,都有一个特点,就是随取随用,用完就扔。那么也就有一个现象,我们每次重新获取的时候,这个DC就是全新的,都是最初始的样子。比如你用毛笔写字,上次写的是白色的,然后这次可能还想写白色的,但是,因为这样的随取随用,导致得到的就是默认的黑色的。你得重新上色成白色,然后就可以了。这样如果反反复复,很麻烦。而且,反复的重新得到和释放,也加重了系统的负担。
    那么,CS_OWNDC风格可以解决这个问题。这个风格的意思是,每一个窗口都会分配一个单独的DC,这个DC只有这个窗口可以使用,而且,直到窗口销毁,它才被释放。那么这个窗口独有的DC,在窗口创建时,系统就给它分配好了。而没有这个风格的,每次获取DC,实际上就是临时创建一个DC。而窗口独有的DC,获取就只是得到创建窗口时分配好的独有DC,这样DC获取的更快,效率更高。也避免了反复的创建和释放。
    那么很容易产生一种疑惑,就是,系统给窗口分配了,那我如何用呢?这个DC又和临时创建的DC有什么不一样呢?这个是初学者,甚至是很多很熟悉的人都不了解的地方。因为窗口独有的DC,在创建窗口时产生,窗口销毁时释放,也就是说,在窗口存活期间,这个独有DC一直都是活着的,也只有一个。那么也就意味着,这个DC就有状态的,上次设置的状态,比如设置笔是红色的,那么下次再使用,它依然是红色的。但是临时创建的,每一次创建都是新生儿。你不得不在上次使用后,将状态记下,然后后面再使用时,先将记下的状态设置一下,比如将上次设置为红色的,先设置一下。这样就显得麻烦了。当然,如果你只是临时使用,临时DC就很适合,而使用窗口独有的DC,则不合适,因为你还得让它恢复初始的样子,因为它的身上还残留其他女人的香味哦。哈哈哈。
    而在使用上,其实没有什么区别。平时怎么用,还是怎么用。比如在WM_PAINT中,你设置了这个CS_OWNDC风格,那么它得到的就是窗口独有的DC,而不是临时DC。如果没有设置,那就是临时创建DC。而对于释放的问题,还是一样。这个可能会给你造成疑惑,因为窗口独有的DC不是窗口销毁由系统释放的吗?是的。这个DC是有特殊标记的,你的释放,只不过是走走流程罢了。并不会真的释放掉独有的DC的。这样,可以保持编程的一致性,不用区分这两种情况,不至于让编程变得复杂。否则你得记得你的DC到底是哪样的。现在你只需要用前获取,用完释放,至于是不是真的释放,你不要太较真咯,系统自会分辨。
    那么,我们最后用代码来验证一下,让你体会的更加深刻。当然,你也一定要亲手摸一把,过过瘾哈。万一摸炸了呢,是不是很好玩呀,哈哈哈。
    为了方便和程序完整起见,我在此就将代码贴一遍,不过还是有不同之处的哦,细细观察一下。

#include <Windows.h>// - Win32程序基础的东西都在这个头文件里

LRESULT CALLBACK WinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);// - 窗口过程函数的声明
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev, PSTR szCmdLine, int iCmdShow)
{
    // - 创建窗口类结构体的窗口类实例,即创建自定义的窗口类
    WNDCLASS wndclass;
    // - 给创建的窗口类指定各种特性值
    wndclass.style = CS_HREDRAW | CS_VREDRAW /*| CS_OWNDC*/;// - 窗口基本风格特性
    wndclass.lpfnWndProc = WinProc;// - 窗口过程,回调函数名称,用于处理窗口的所有消息
    wndclass.cbClsExtra = 0;// - 窗口类变量额外分配的内存空间
    wndclass.cbWndExtra = 0;// - 窗口资源额外分配的内存空间
    wndclass.hInstance = hInstance;// - 窗口实例句柄,代表这个程序,是唯一的标志
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);// - 显示在窗口左上角、任务栏的小图标
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);// - 鼠标在窗口客户区的光标样子,默认为箭头
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);// - 背景画刷,决定窗口显示为什么颜色或者背景图片
    wndclass.lpszMenuName = NULL;// - 窗口菜单的名称,与资源文件rc文件中的菜单名称一致,或者直接使用MAKEINTRESOURCE宏将ID转成字符串也可以。
    wndclass.lpszClassName = L"MyClass";// - 窗口类名CS_
    // - 注册窗口类
    if (!RegisterClass(&wndclass))
    {
        MessageBox(NULL, L"注册窗口类失败,此程序需要运行在Windows NT平台下。", L"注册窗口类提示", MB_ICONERROR);
        return 0;
    }
    // - 创建窗口
    HWND hwnd = CreateWindow(L"MyClass", L"我的窗口标题_C++技术网", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
    // - 显示窗口
    ShowWindow(hwnd, iCmdShow);
    // - 更新窗口的客户区
    UpdateWindow(hwnd);//向客户区发送一个WM_PAINT消息来使客户区绘制
    //如果客户区是空的,则不会发送这个消息。
    //UpdateWindow是直接向窗口过程发送WM_PAINT消息的,而不是经过消息队列

    MSG  msg;
    // - 消息循环
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

LRESULT CALLBACK WinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // - 为了将重点放在讲解注册窗口类上,消息处理已经精简到不能再精简了。
    HDC hdc;
    PAINTSTRUCT ps;
    switch (message)
    {
    case WM_CREATE:
        hdc = GetDC(hwnd);
        SetTextColor(hdc, RGB(255, 0, 0));// - 窗口创建时,就将窗口独有的DC设置一下颜色
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        TextOut(hdc, 0, 0, L"Hello world", sizeof("Hello world")-1);// -显示文字
        EndPaint(hwnd, &ps);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

    上面的代码中,窗口类风格的CS_OWNDC我是注释的,你可以先运行一下,看看效果。你会发现,窗口输出的字是黑色的。因为此时WM_PAINT中使用的是临时创建的DC。所以尽管WM_CREATE消息中,设置了文字颜色,实际上,它也只是设置的WM_CREATE中创建的临时DC而已,和WM_PAINT的临时DC完全不相干,自然也就不会相互影响。

   
    现在你将/*  */这个四个字符去掉,让CS_OWNDC重获新生,让它成为窗口类风格的一员,那么此时你运行的结果就是文字是红色的。但是代码还是没有变化,效果却变了。而设置颜色的就是在WM_CREATE消息中,这个消息是窗口创建时就会接收到,所以,一开始就得到了这个消息,也就执行了这个语句。因为有CS_OWNDC风格,那么GetDC获取的就是窗口独有的DC了,设置的也是这个独有的DC,而这个一直存活着,那么这个设置一直有效,所以,在后面的WM_PAINT消息中,通过BeginPaint得到的还是窗口独有的DC,那么这时候去绘制文字,自然就是红色的。

   
    在到处都需要用到的时候,经常用的时候,要切换状态还想保留上一次的状态,那么CS_OWNDC风格很有用哦。每一个窗口都有这么一个DC。窗口相互之间没有任何影响。
    另外一种,就是所有窗口之间都会公用的一个DC,会相互影响的DC,就是CS_CLASSDC风格得到的。想了解这个,请听下节分解。