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

Windows零基础入门:2.21 窗口类结构体之窗口类风格CS_CLASSDC

更新时间:2015-09-16 17:23:25浏览次数:1+次

    CS_CLASSDC风格注册的窗口类,系统会为这个类分配一个窗口类DC。用这个窗口类创建的所有窗口,都共享这一个DC句柄。也就是说,这些窗口所使用的DC是公用的。
    这个和前面一讲《Windows零基础入门:2.20 窗口类结构体之窗口类绘图风格CS_OWNDC》说到的不一样。前面一讲的DC是窗口相关的,是窗口独有的DC。在窗口内部执行绘图,不需要反复的创建和释放。而这里的窗口类DC,则不是某个窗口独有,而是用这个窗口类创建的所有窗口共同拥有。一个窗口使用了之后,设置的画笔颜色,文字颜色等信息,都会保留。另一个窗口使用时,依然是上一个窗口使用留下的样子。
    相对于窗口独有的DC,窗口类DC成了共有的资源,也就表示,这个资源必然不是窗口能够控制的,窗口最多就是拿来用用而已。窗口无权创建也无权释放。这个和窗口独有DC一样,你最多就是走走形式,使用完后的释放并不会起作用。当应用程序结束后,所注册的窗口类也就被销毁,这个时候系统自然就会释放窗口类DC。自然是窗口类注册的时候系统创建的窗口类DC。
    因为窗口类是和进程相关的,所以,一个进程中的多个线程都可以基于这个窗口类创建窗口。
那么,这些线程就可能同时去使用这个DC。如果发生这种情况,系统会保证一次只有一个线程能够成功的完成绘图。也就是说,这个DC是一次只能被一个线程拥有。
    这么来理解,窗口类在进程中注册,只要使用了这个窗口类来创建的窗口,都共享这个窗口类DC。注册窗口类时指定好CS_CLASSDC这个标志哦。不管是应用程序局部窗口类还是应用程序全局窗口类,对于一个进程的所有线程来说,都没有关系。所有线程都在进程空间中,都是一家人。这个好比,你在家里放一个WiFi,不管是说这是家里私用的,还是提供路人也可以使用的,对于一家人来说,都是可以用的。只是,如果你设置为家里私用的,路人就看不到,对于家人来说,没关系。这些线程就是进程这个家里的所有家人。
    虽然说这个窗口类DC是这个窗口类创建的所有窗口共有的,但是,一次只能有一个线程在使用。一个线程就相当于一个使用者。因为窗口类DC只有一个,你用了,他就只能等。你可以把DC想象为一套画图的工具箱。一次一个人用,是相互排斥的。对于一个线程来讲,就只有一个人会用,那么他什么时候用都不会用不了。而涉及到多线程,就会产生抢的可能。因为在多CPU的环境里,是可以允许多个线程同时执行的。比如双核CPU,可以同时执行两个线程,四核CPU,可以同时执行四个线程。还有一种情况,多线程就算在单核CPU执行,他们也是轮流使用CPU。而一个线程在使用窗口类DC的时候,可能要执行比较长时间,这样一次执行过程完成不了绘画过程。但是此时又轮到其他线程执行。因为一个线程执行时已经在用这窗口类DC了,必然会对DC里的一些参数进行设置,比如设置文字颜色,画刷颜色等等,如果切换到其他线程执行的时候,其他线程也可以使用这个DC的话,其他线程也要设置DC,这样就导致混乱,结果画的稀里糊涂。所以系统就要保证,一次只能有一个线程占有窗口类DC,即使它现在在睡觉,也是它的。其他线程不要企图使用。只有当他释放了窗口类DC之后,就解除占用,那么其他的线程就可以使用这个窗口类DC了。而说到这里,可以知道,释放窗口类DC并不是真的释放窗口类DC,而是解除占用而已。如果你不解除占用,其他线程画图就无法使用了。
    下面用代码来解释这个机制。为了突出主题,将不必要的注释去掉了。详细的注释可以参考前面课程文章。

#include <Windows.h>
LRESULT CALLBACK WinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);// - 窗口过程函数的声明
DWORD WINAPI ThreadProc( LPVOID lpParameter )
{
    // - 线程函数
    HINSTANCE hInstance = *(HINSTANCE*)lpParameter;
    HWND hwnd = CreateWindow(L"MyClass", L"线程函数", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
    // - 显示窗口
    ShowWindow(hwnd, SW_SHOWNORMAL);
    // - 更新窗口的客户区
    UpdateWindow(hwnd);//向客户区发送一个WM_PAINT消息来使客户区绘制
    MessageBox(NULL, L" 线程消息", L"提示", 0);
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev, PSTR szCmdLine, int iCmdShow)
{
    DWORD dwTID;
    CreateThread(NULL, 0, ThreadProc, &hInstance, 0, &dwTID);
    // - 创建窗口类结构体的窗口类实例,即创建自定义的窗口类

    WNDCLASS wndclass;
    // - 给创建的窗口类指定各种特性值
    wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_CLASSDC;// -添加窗口类DC风格
    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;
    wndclass.lpszClassName = L"MyClass";
    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);
    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_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        TextOut(hdc, 0, 0, L"Hello world", sizeof("Hello world")-1);// -显示文字
        SetTextColor(hdc, RGB(255, 0,0));// - 设置文字颜色为红色
        EndPaint(hwnd, &ps);// - 注释后就不释放DC了
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}


    因为是窗口类DC,所以涉及到了多线程。不过,你不需要太在意这个。我简单解释这个道理的需要,你明白这个道理就行了。

DWORD WINAPI ThreadProc( LPVOID lpParameter )
{
    ...
}


    ThreadProc是线程函数,创建新的线程使用CreateThread函数,然后指定线程函数。那么线程执行的时候,就会执行线程函数的代码。而我们的WinMain函数就是主线程执行的函数。主线程就是程序启动第一个执行的线程。我们在主线程注册了一个窗口类,指定了窗口类DC风格。那么后面使用的就是窗口类DC了。在绘图消息WM_PAINT中,显示了文字,默认是黑色的。然后再设置文字颜色为红色。因为是窗口类DC,所以这个设置会保存在DC中。那么子线程执行的时候,用的还是主线程注册的窗口类,那么此时子线程中的窗口也是关联的主线中的这个窗口过程。窗口过程的指定在窗口类中。既然子线程使用的是主线程注册的窗口类,自然他的窗口过程还是同样的。也就是也会执行WinMain中的消息处理。

    但是因为主线程执行的时候,设置了文字颜色,且设置保留,那么子线程窗口中显示的文字就是红色的。见下图所示:

   

    如果你去掉CS_CLASSDC这个风格,那么线程中文字也是黑色的,因为这样线程使用的DC就是临时创建的DC。如果你使用上节课的CS_OWNDC,自然还是同样的效果。因为是不同的窗口,CS_OWNDC是窗口独立的,主线程的窗口和子线程的窗口互不相干,自然就都是黑色的。你可以更改代码看看效果,一定要动手哈。效果如下:

   

    那么前面提到了,如果一个线程占有了窗口类DC,而不释放,窗口类风格设置为CS_CLASSDC,这样其他线程就无法使用。那么我们来实验一下。不释放的实现,我们可以注释掉EndPaint(hwnd, &ps);,但是你试试就会发现,效果图如下:

   

    和第一个图效果一样,似乎没有影响。其实不是的。因为WinProc函数的hdc变量是局部的,WinProc函数执行完后,hdc也就销毁了,那么它引用的窗口类DC,自然也就释放了。这才造成似乎并没有被占用的假象。

    那么你可以将hdc的定义,放在函数外面,就成了全局变量,这样,就不会因为WinProc执行完后释放掉,也就保持了占用。此时你会发现,效果图如下:

   

    这样就表示,主线程占用状态没有被解除,因此子线程的窗口无法使用窗口类DC,所以创建了临时的DC,那么默认的就是黑色的。

    相信通过这个程序代码和效果图的分析,你对这个会了解的更加深入。不过你可能一遍理不清这个关系,没关系,多看几遍。其实不难。只是你不熟悉而已。文章已经分析的很清楚了。后面对代码的小小改动验证,请你来完成咯。不过,如果还有任何疑问,请留言提问。