標籤:des style blog http color os
引言
近期在看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 版本的代碼講解如何封裝視窗訊息處理常式。
寫累了,待續