引言

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

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

实现窗口类

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

在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 版本的代码讲解如何封装窗口消息处理程序。

写累了,待续

Notepad++源代码阅读——窗口封装与继承的更多相关文章

  1. Notepad++源代码阅读——窗口元素组织与布局

    1.1 前言 这两天在看notepad++ 1.0版本的源代码.看了许久终于把程序的窗口之间的关系搞清楚了现在把其组织的要点写于此,希望对大家有所帮助. 1.2 窗口元素之间的关系 Notepad++ ...

  2. 非常好!!!Linux源代码阅读——环境准备【转】

    Linux源代码阅读——环境准备 转自:http://home.ustc.edu.cn/~boj/courses/linux_kernel/0_prepare.html 目录 Linux 系统环境准备 ...

  3. Java语言简介、基础组成、封装、继承、多态、抽象类、内部类、接口

    目录 Java简介 Java语言基础组成 面向对象 对象 封装 构造函数 this关键字 static(静态关键字) 主函数 静态什么时候用呢? 面向对象(数组工具对象建立) 设计模式 继承 成员变量 ...

  4. Mongodb源代码阅读笔记:Journal机制

    Mongodb源代码阅读笔记:Journal机制 Mongodb源代码阅读笔记:Journal机制 涉及的文件 一些说明 PREPLOGBUFFER WRITETOJOURNAL WRITETODAT ...

  5. 【转】Tomcat总体结构(Tomcat源代码阅读系列之二)

    本文是Tomcat源代码阅读系列的第二篇文章,我们在本系列的第一篇文章:在IntelliJ IDEA 和 Eclipse运行tomcat 7源代码一文中介绍了如何在intelliJ IDEA 和 Ec ...

  6. 利用doxygen提高源代码阅读效率

    阅读开源项目的源代码是提高自己编程能力的好方法,而有一个好的源代码阅读工具无疑能够让你在阅读源代码时事半功倍.之前找过不少源代码阅读工具,像SourceInsight.sourcenav.scitoo ...

  7. 04-OC属性的使用、自动释放池、封装和继承

    目录: 一.IOS6声明式属性的使用 二.autoreleasepool自动释放池 三.封装.继承 回到顶部 一.IOS6声明式属性的使用 注:声明式属性默认情况下,并没有解决内存问题, 当使用@pr ...

  8. C++内存中的封装、继承、多态(上)

    C++内存中的封装.继承.多态(上) 继我的上一篇文章:浅谈学习C++时用到的[封装继承多态]三个概念 此篇我们从C++对象内存布局和构造过程来具体分析C++中的封装.继承.多态. 一.封装模型的内存 ...

  9. JavaScript 定义类的最佳写法——完整支持面向对象(封装、继承、多态),兼容所有浏览器,支持用JSDuck生成文档

    作者: zyl910 [TOC] 一.缘由 由于在ES6之前,JavaScript中没有定义类(class)语法.导致大家用各种五花八门的办法来定义类,代码风格不统一.而且对于模拟面向对象的三大支柱& ...

随机推荐

  1. docker强制关闭命令

    删除容器: 优雅的关闭容器:docker stop  容器id/容器名字 强制关闭容器:docker kill 容器id/容器名字 删除镜像: docker rmi 容器id/容器名字

  2. python基础学习之路No.2 数据类型

    python中常见的数据类型有:整数.浮点数.字符串.列表.元组.字典 python相较其他语言,可以省略了声明,可以直接定义赋值使用. 例如: a=12 就相当于 其他语言中的  int a=12  ...

  3. 修饰符(动态String数组篇)--- 常用 解除疑问。

    1.无修饰符----是直接传基本类型的地址过来,并没有把基本类型的指针复制一份入栈,所以一旦修改就是修改原来的值. 2.const 修饰符 与 无修饰符一致. 3.var修饰符 与 上一致. 4.ou ...

  4. MySQL学习笔记:计算机服务中找不到MySQL服务

    不知道什么原因,连接了远程服务器之后,本地服务就连接不上. 经排查,发现服务中的MySQL服务居然不见,一番搜索之后,在CMD中进入到MySQL安装目录的bin下执行以下语句: mysqld.exe ...

  5. MySQL学习笔记:时间差

    1.MySQL计算同一张表中同一列的时间差,同一个id,有多个时间,求出每个id最早时间和最晚时间之间的差值. 原始表如下: 查询语句: SELECT id, MAX(TIME), MIN(TIME) ...

  6. 虚拟机的安装以及Linux的学习

    安装虚拟机 对虚拟机的认识 其实初中的时候我就听说过虚拟机这个名词,当时的我还小,也不知道虚拟机是个什么东西,那时我傻傻的认为虚拟机只不过是电脑中的一个虚拟的计算机,没有什么实在的作用.后来随着大学课 ...

  7. Spark介绍及安装部署

    一.Spark介绍 1.1 Apache Spark Apache Spark是一个围绕速度.易用性和复杂分析构建的大数据处理框架(没有数据存储).最初在2009年由加州大学伯克利分校的AMPLab开 ...

  8. 如何在k8s集群里快速运行一个镜像?

    在docker里,快速run一个镜像,很简单的. k8s的世界,与之类似. 但要注意一下,如果镜像本身没有提供command命令,这个容器由于前台输出完成,很快就退出了. 所以,遇到这种镜像,就最好自 ...

  9. mysql 5.1 下载地址 百度云网盘下载

    mysql 百度云下载 链接: https://pan.baidu.com/s/1fPSEcgtDN7aU2oQ_sL08Ww 提取码: 关注公众号[GitHubCN]回复2539获取

  10. Spark(四)Spark之Transformation和Action

    Transformation算子 基本的初始化 java static SparkConf conf = null; static JavaSparkContext sc = null; static ...