• Notepad++源代码阅读——窗口封装与继承


    引言

    近期在看Notepad++的源代码,学习学习Win32 原生API的开发技巧。

    本文以Notepad++ 1.0版本的源代码为例讲解如何封装windows窗口,实现面向对象开发,如何通过窗口的继承实现代码的重用,并且利用C++的动态绑定特性实现多态,另外说明窗口封装过程中如何封装消息处理程序,这是实现面向对象的关键所在。听我细细道来。

    实现窗口类

    下图是Notepad++1.0版本窗口类的继承层次:

    clip_image002

    在Notepad++ 1.0 中所有的窗口元素:编辑窗口、选项卡窗口、工具栏、状态栏、对话框等等都有一个共同的父类:Window类,该类是一个虚基类,不能被实例化,其中的detroy函数是纯虚函数。里面声明了每个窗口所必须包含的变量:自身的窗口句柄_hSelf,父窗口句柄 _hParent 和 整个程序的实例句柄 _hInst。该类实现了一些窗口的基本操作,部分为虚函数。下面我们看看它的源代码:

    #include <windows.h>
    
    class Window //虚基类
    {
    public:
        Window():_hSelf(NULL), _hParent(NULL), _hInst(NULL){};    // 构造函数,在子类中的构造函数调用,为三个变量赋值,
        virtual ~Window() {};
    
        virtual void init(HINSTANCE hInst, HWND parent)    // 虚函数、子类中实现自己的版本,如注册窗口,创建窗口等等
        {
            _hInst = hInst;
            _hParent = parent;
        }
    
        virtual void destroy() = 0;    // 资源释放等等
    
        virtual void display(bool toShow = true) const {// 显示窗口
            ::ShowWindow(_hSelf, toShow?SW_SHOW:SW_HIDE);
        };
        
        virtual void reSizeTo(RECT & rc) // should NEVER be const !!!
        {                                // 这里特别强调rc不能为 const, 因为有时候要通过它返回
                                        // 它上面的客户区,让客户上的窗口重置大小。如选项卡窗口
                                        // reSizeTo返回选项卡的客户区、编辑窗口用返回的矩形区域
                                        // 重置大小
            ::MoveWindow(_hSelf, rc.left, rc.top, rc.right, rc.bottom, TRUE);
            redraw();
        };
    
        virtual void redraw() const {        // 强制刷新窗口
            ::InvalidateRect(_hSelf, NULL, TRUE);
            ::UpdateWindow(_hSelf);
        };
        
        virtual void getClientRect(RECT & rc) const {    // 得到用户区矩形
            ::GetClientRect(_hSelf, &rc);
        };
        
        virtual int getWidth() const {
            RECT rc;
            ::GetClientRect(_hSelf, &rc);
            return (rc.right - rc.left);
        };
    
        virtual int getHeight() const {
            RECT rc;
            ::GetClientRect(_hSelf, &rc);
            return (rc.bottom - rc.top);
        };
        
        virtual bool isVisible() const {    
            return bool(::IsWindowVisible(_hSelf));
        };
    
        HWND getHSelf() const {    // 得到自身窗口句柄
            if (!_hSelf)
            {
                ::MessageBox(NULL, "_hSelf == NULL", "class Window", MB_OK);
                throw int(999);
            }
            return _hSelf;
        };
    
        void getFocus() const {
            ::SetFocus(_hSelf);
        };
    
        HINSTANCE getHinst() const {
            if (!_hInst)
            {
                ::MessageBox(NULL, "_hInst == NULL", "class Window", MB_OK);
                throw int(1999);
            }
            return _hInst;
        };
    protected:
        HINSTANCE _hInst;    // 程序实例句柄
        HWND _hParent;        // 父窗口句柄
        HWND _hSelf;        // 自身窗口句柄
    };

    这就是窗口的基类,用这个基类我们就能派生出自己的实现特定功能的窗口。下面讲解几个典型的窗口。

    对话框的封装

    Notepad++ 的对话框继承StaticDialog,StaticDialog又继承上面的Window类。对话框基类的声明如下:

    class StaticDialog : public Window
    {
    public :
        StaticDialog() : Window() {};
        ~StaticDialog(){};
        virtual void create(int dialogID);
        virtual bool isCreated() const {
            return reinterpret_cast<bool>(_hSelf);
        };
        //virtual do    
        void destroy() {
            ::DestroyWindow(_hSelf);
        };
    protected :
        static BOOL CALLBACK dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
        virtual BOOL CALLBACK run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam) = 0;
    };
    对话框的封装关键在于create函数的实现。该函数传入对话框的资源ID然后创建,函数实现如下:

    void StaticDialog::create(int dialogID) 
    {
        _hSelf = ::CreateDialogParam(_hInst, MAKEINTRESOURCE(dialogID), _hParent,  (DLGPROC)dlgProc, (LPARAM)this);
        
        if (!_hSelf)
        {
            systemMessage("StaticDialog");
            throw int(666);
        }
        display();
    }

    函数基本就是对话框创建的API调用,传入对话框资源、消息处理程序:dlgProc,这个函数是静态 static 函数,因此可以传入该函数调用,最后将this 指针传入其中,WM_INITDIALOG消息中可以获取这个指针。

    下面看看dlgProc 的实现:

    BOOL CALLBACK StaticDialog::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 
    {
        switch (message) 
        {
            case WM_INITDIALOG :
            {
                StaticDialog *pStaticDlg = (StaticDialog *)(lParam);
                pStaticDlg->_hSelf = hwnd;
                ::SetWindowLong(hwnd, GWL_USERDATA, (long)lParam);
                pStaticDlg->run_dlgProc(message, wParam, lParam);
                return TRUE;
            }
    
            default :
            {
                StaticDialog *pStaticDlg = reinterpret_cast<StaticDialog *>(::GetWindowLong(hwnd, GWL_USERDATA));
                if (!pStaticDlg)
                    return FALSE;
                return pStaticDlg->run_dlgProc(message, wParam, lParam);
            }
        }
    }

    在WM_INITDIALOG 消息中将lParam转换成StaticDialog指针,这样就能获取窗口句柄_hSelf(基类成员), 同时将指针放在USERDATA中,在其他消息中取出,指针并调用成员函数:run_dlgProc,这个函数是纯虚函数,继承的对话框子类就能实现自己的特定消息处理了。这个就是消息处理程序的封装。在最后我们还将讲解主窗口的消息处理的封装,其实和对话框所用的方法大同小异。

    选项卡窗口

    写累了,待续

    主窗口类

    写累了,待续

    封装消息处理程序(Encapsulating WndProc)

    这里已Notepad++ 1.0 版本的代码讲解如何封装窗口消息处理程序。

    写累了,待续

  • 相关阅读:
    C++学习004-Go To 语句使用
    C++学习003-#define 自定义宏
    C++学习002-C++代码中插入汇编语句
    C++学习001-注释
    Qt 加载Leap motion 手势识别软件 二次开发 hello world
    C++知识点 内存占用问题
    虚拟现实-VR-UE4-编译源代码后,无法运行
    Loadrunner|录制脚本时出现乱码的解决方式
    2月14日学习内容
    构建之法读后感(三)
  • 原文地址:https://www.cnblogs.com/jimmysue/p/3844963.html
Copyright © 2020-2023  润新知