Win32窗口之初级封装

封装一个Win32窗口类,还真不是那么容易的事。我打算分成几个等级的封装,后面的会解决一些前面留下的问题。接下来就看看最初级最粗糙的封装。

新建一个Window类,声明如下:

 1 class Window
 2 {
 3 public:
 4     Window(char* szClassName, char* szWindowTitle);
 5 
 6     void show() const;
 7 
 8 protected:
 9     virtual void draw() const;
10 
11     virtual HRESULT winProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
12 
13 private:
14     static  HRESULT CALLBACK WinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
15 
16     bool createWin32Window(char* szClassName, char* szWindowTitle);
17 
18 protected:
19     HWND mhWnd;
20 
21     bool mbExit;
22 };

 

构造函数有两个参数分别代表窗口类名字和显示在标题栏上的文字,构造函数调用了createWin32Window并将两个参数传进去,createWin32Window定义如下:

void Window::createWin32Window(char* szClassName, char* szWindowTitle)
{
    WNDCLASS wc;
    wc.lpfnWndProc = (WNDPROC)&Window::WinProc;
    wc.lpszClassName = szClassName;
    //......

    RegisterClass(&wc); // 注册窗口类

    mhWnd = CreateWindowEx(0, szClassName, szWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, wc.hInstance, NULL);// 创建窗口
}

这里我没有做任何异常检查,一般情况下我们应该检查RegisterClass和CreateWindowEx的返回值,看看是否正确如期望中的一样。特别是在构造函数或者会通过构造函数调用的函数中,请不要添加一些可能会执行不成功的代码或者抛出异常。这里也没有添加一些函数来设定窗口的显示位置和显示样式,都是使用默认。窗口创建成功后,可以通过调用show显示出来,show函数定义如下:

void Window::show() const
{
    ShowWindow(mhWnd, SW_SHOWNORMAL);

    UpdateWindow(mhWnd);

    MSG msg;
    while (!mbExit)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            draw();
        }
    }

    DestroyWindow(mhWnd);
}

这个函数首先通过API显示出窗口,然后进入消息循环。PeekMessage会查看消息队列中是否有消息没有处理,如果有,移除该消息并返回true,接下来分派消息,然后由系统调用Window类的静态成员函数WinProc(注意首字母大写)进行消息响应,这部分会在后面介绍。如果消息队列中没有消息,PeekMessage会立即返回false,并调用draw虚函数。这种情况在一些游戏程序中很常见,我们希望当应用程序空闲(没有待处理的消息)的时候去更新下一帧,而不是傻等。那非游戏程序一般是这样的方式:

while(GetMessage(&msg), NULL, NULL, NULL)
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

注意,这种方式show窗口会造成只要窗口没有关闭,show后面的代码不会执行,这也是一个很要命的缺点,我们将在以后的版本中解决这个问题。下面来看看由系统调用的回调函数WinProc,由于WinProc的签名不能随便更改,因此它不能作为类的非静态成员函数,因为非静态成员函数隐含了this指针作为参数,只能是非成员函数或者静态成员函数。到底是设计为非成员函数好呢还是静态成员函数好呢?

HRESULT CALLBACK Window::WinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    return window.winProc(hWnd, msg, wParam, lParam);
}

在这里,WinProc内部调用了成员函数winProc(注意首字母小写),而winProc是protected访问级别的,不想让客户端调用它。如果WinProc是非成员函数的话,还必须将它作为类的friend才行,而我要告诉大家,friend这东西能不用就不要用,破坏封装性,虽然生活中多几个friend总是好的。而将它设为类的静态成员函数的话,则可以免掉这个问题。大家有没有注意到WinProc中有一个window对象,其实它是一个全局对象,定义在其他文件(main.cpp)中,并在此文件中加上一句extern Window window;告诉编译器这个window对象是全局对象,定义在其他文件中。所以这相对于上面那个确定,这个更致命。
最后看看成员函数winProc

HRESULT Window::winProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_CLOSE: mbExit = true; break;
        case WM_PAINT: draw(); break;
        // more to come
        default: return DefWindowProc(hWnd, msg, wParam, lParam);
    }

    return 0;
}

你可以在这里处理任何消息,也可以在子类中去重写它处理其他消息。比较好的做法是,针对每一个需要处理的消息添加并调用一个虚函数,让子类去重写这个虚函数。比如添加一个响应鼠标移动的 virtual void mouseMove(int x, int y)然后再switch分支中增加一个case WM_MOUSEMOVE:mouseMove(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));break;即可。
现在这个窗口类只是被很粗糙地封装完成了,它能完成的就只能是显示一个窗口,点击关闭按钮退出程序。听起来好像一个空白的窗口算是已经完成了,但是还早,它有很多不足。

1. 创建窗口的时候,虽然第一个参数ClassName是客户端穿进去的,如果创建两个窗口并且这个参数相同的话,会创建失败,因为Windows不允许一个类名被注册两次。加上这个参数是为以后做铺垫。

2. 显示窗口后,show函数后面的代码将不会执行,直到窗口被关闭。

3. 必须定义一个全局的window对象,且名称固定(因为在WinProc中需要能访问到这个对象),从这点看来,这个封装基本一无是处。

4. 定义多个对象(szClassName参数前提要不相同)的话,其他将无法响应消息。

差不多就这些吧,但是每个都很要命,等下一篇来改进吧。

 

 

posted @ 2012-09-11 22:18  剑起飞虹  阅读(511)  评论(0编辑  收藏  举报