Notepad++是一个小巧精悍的编辑器,其使用方法我就不多说了,由于notepad++是使用c++封装的windows句柄以及api来实现的,因此对于其源码的研究有助于学习如何封装自己简单的库(当然不是MTL、MFC或者QT那样大型的库)。Notepad++源码:https://github.com/notepad-plus-plus/notepad-plus-plus/releases/tag/v6.7.9.2。
下面是Notepad++源码的目录:
其主目录如第一张图所示,包含了两个开源工程,第一个PowerEditor就是notepad++;第二scintilla是一个代码编辑器的开源库,十分强大,许多编辑器都是基于这个库封装起来的,对于scintilla来说可以实现代码折叠、标记、缩进等等很多功能,具体情况可以去scintilla的官网以及一些博客来了解如何使用这个开源库:
http://www.cnblogs.com/superanyi/archive/2011/04/07/2008636.html
将第一个工程PowerEditor打开之后将如上右图所示,这里最重要的是src源码文件以及installer中的配置文件。当我用vs2012打开visual.net打开工程文件notepadPlus.vs2005.vcproj后出现了几个问题,第一个问题,就是一大堆找不到预编译头文件,解决方法是不使用预编译头文件即可。第二个问题出现在Sorters.h头文件中,vs2012虽然实现了c++11的部分特性,但是却没有实现std::unique_ptr,由此引发出很多语法错误,解决办法可以有自己写一个类似于unique_ptr这样的智能指针或者有办法替换vs2012内置编译器也可以。我比较懒,恰好笔记本中安装有vs2013,是支持unique_ptr的,我就直接换环境了。
然后程序跑起来还是有一些问题,在WinMain中作者对一些左值进行了赋值,语法直接报错了,对于这个直接注释掉这一行即可,其次再删除一个预编译源文件和实现一个函数声明之后程序就跑了起来,之后的小问题就不多说了,因为本文主要是讲notePad++的运行机制是啥。我稍微统计了下,整个工程大概是21W行代码,加上注释也比较少,实在花了我好几天才摸清楚大概情况。
从界面开始说起,整个工程中的窗口都是继承自Window这个类的,这个类封装最重要的一个成员就是HWND _hSelf,这个就是用来存放CreateWindow函数返回的窗口句柄,其次就是父窗口句柄HWND _hParent,以及实例HINSTANCE _hInst。还提供了许多窗口都能够用到的方法,都是以虚函数的方法来提供的,比如display函数用来显示窗口,reSizeTo用来调整窗口,redraw用来重绘窗口等等许多函数。有了Window类,后面的就容易理解一些了,其中整个NotePad++的主窗口类Notepad_plus_Window继承自Window,notepad++中所有的对话框都是继承自StaticDialog的,而StaticDialog也是继承自Window这个父类的。下面是Window类的源码:
class Window { public: Window(): _hInst(NULL), _hParent(NULL), _hSelf(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 !!! { ::MoveWindow(_hSelf, rc.left, rc.top, rc.right, rc.bottom, TRUE); redraw(); }; virtual void reSizeToWH(RECT & rc) // should NEVER be const { ::MoveWindow(_hSelf, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE); redraw(); }; virtual void redraw(bool forceUpdate = false) const { ::InvalidateRect(_hSelf, NULL, TRUE); if (forceUpdate) ::UpdateWindow(_hSelf); }; virtual void getClientRect(RECT & rc) const { ::GetClientRect(_hSelf, &rc); }; virtual void getWindowRect(RECT & rc) const { ::GetWindowRect(_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); if (::IsWindowVisible(_hSelf) == TRUE) return (rc.bottom - rc.top); return 0; }; virtual bool isVisible() const { return (::IsWindowVisible(_hSelf)?true:false); }; HWND getHSelf() const { //assert(_hSelf != 0); return _hSelf; }; HWND getHParent() const { return _hParent; }; void getFocus() const { ::SetFocus(_hSelf); }; HINSTANCE getHinst() const { //assert(_hInst != 0); return _hInst; }; protected: HINSTANCE _hInst; HWND _hParent; HWND _hSelf; };
从直观上来说,因为像菜单栏、工具栏、编辑框等等这些窗口应该属于主窗口,不过作者在主窗口Notepad_plus_Window和这些子窗口中间添加了一层,将所有的子窗口对象都封装在了Notepad_plus这个类中,再由Notepad_plus_Window来封装Notepad_plus对象_notepad_plus_plus_core。这样一来让主窗口的代码和子窗口的一些实现分离了,让Notepad_plus_Window的功能变得很清晰,不过Notepad_plus这个类因为封装可大量的子窗口对象变得十分复杂,另一个问题就是这些子窗口的父窗口需要指定,但是这个父窗口句柄被封装在Notepad_plus_Window中,于是Notepad_plus类中又封装了Notepad_plus_Window对象指针,机智的通过编译又能够拿到父窗口句柄了。下面是Notepad_plus_Window源码:
class Notepad_plus_Window : public Window { public: Notepad_plus_Window() : _isPrelaunch(false), _disablePluginsManager(false) {}; void init(HINSTANCE, HWND, const TCHAR *cmdLine, CmdLineParams *cmdLineParams); bool isDlgsMsg(MSG *msg) const; HACCEL getAccTable() const { return _notepad_plus_plus_core.getAccTable(); }; bool emergency(generic_string emergencySavedDir) { return _notepad_plus_plus_core.emergency(emergencySavedDir); }; bool isPrelaunch() const { return _isPrelaunch; }; void setIsPrelaunch(bool val) { _isPrelaunch = val; }; virtual void destroy(){ ::DestroyWindow(_hSelf); }; static const TCHAR * getClassName() { return _className; }; static HWND gNppHWND; //static handle to Notepad++ window, NULL if non-existant private: Notepad_plus _notepad_plus_plus_core; //窗口过程函数 static LRESULT CALLBACK Notepad_plus_Proc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam); LRESULT runProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam); static const TCHAR _className[32]; bool _isPrelaunch; bool _disablePluginsManager; std::string _userQuote; // keep the availability of this string for thread using };