Win32 SDK封閉例項——普通的Windows程式(翻譯) (轉)

worldblog發表於2007-12-12
Win32 SDK封閉例項——普通的Windows程式(翻譯) (轉)[@more@]語言/index.htm">

普通的


這個程式使用類封裝了Windows 。
  • Controller-- 視窗過程和之間的橋樑。
  • View-- Windows程式的輸出封裝。
  • Canvas-- 封裝了不同的裝置描述符和事件,你可以去使用它們。
  • Model-- 工作者,你的程式的大腦。從不處理視窗。
注意: 這是一個程式——它將執行在Windows95及WindowsNT下。 注意: _set_new_handler是特定的。如果你使用的是其它的,只要移去這行程式碼。依照當前的C++標準,new運算子無論如何都應該拋棄。 注意: 舊的編譯器在模板方面可能有問題。你可以直接Get/SetWindowLong來代替Win[Get/Set]Long模板。例項,呼叫以下的來代替

Controller * pCtrl = WinGetLong (hwnd);

你可以呼叫

Controller * pCtrl = reinterpret_cast (::GetWindowLong (hwnd, GWL_USERDATA));


讓我們從WinMain開始,我們建立一個視窗類及我們的程式的最頂層視窗。我在兩個類中封裝了這些動作:WinClassWinMaker。如果在那我們的程式已經執行了一個例項,WinClass可以告訴我們。當這樣的事發生時,我們的例子,將簡單的啟用程式先前的例項後結束。當你只想讓你的程式在同一時間執行一個例項時,你應該這樣做。

一旦頂層視窗被成功的建立,我們開始訊息迴圈。在這時我們透過呼叫TranslateMessage來處理。這是因為我們的程式的選單條目可以使用Alt+鍵的組合來訪問。

另外這個程式有趣的是我們不能使用很長的字串去命名我們的資源——我們使用數字標識。即使是API呼叫的字串,象視窗型別名或標題,我們都存貯在字串資源中,透過識別符號來訪問。你們的Windows開發環境中多數有一個資源編輯器讓你去建立圖示,選單,及字串資源,給它們分配適當的數字識別符號。這些識別符號的符號名存貯在一個引審了的頭中——我們程式去呼叫它re.h

常量,ID_MAIN是為例項的主程式引入的圖示 (在同一資源中有一大一小),主選單,及視窗型別句的字串。ID_CAPTION是視窗標題字串。


int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hPrevInst, char * cmdParam, int cmdShow) { _set_new_handler (& NewHandler); // 使用異常幫助你的程式 // 防止異常事件 try { // 建立頂層視窗類 TopWinClass topWinClass (ID_MAIN, hInst, MainWndProc); // 正在執行這個程式的例項嗎? HWND hwndOther = topWinClass.GetRunningWindow (); if (hwndOther != 0) { ::SetForegroundWindow (hwndOther); if (::IsIconic (hwndOther)) ::ShowWindow (hwndOther, SW_RESTORE); return 0; } topWinClass.Register (); // 建立頂層視窗 ResString caption (hInst, ID_CAPTION); TopWinMaker topWin (topWinClass, caption); topWin.Create (); topWin.Show (cmdShow); // 主訊息迴圈 MSG msg; int status; while ((status = ::GetMessage (&msg, 0, 0, 0)) != 0) { if (status == -1) return -1; ::TranslateMessage (&msg); ::DispatchMessage (&msg); } return msg.wParam; } catch ( WinException e ) { char buf [50]; wsprintf (buf, "%s, Error %d", e.GetMessage (), e.GetError ()); ::MessageBox (0, buf, "Exception", MB_ICONEXCLAMATION | MB_OK); } catch (...) { ::MessageBox (0, "Unknown", "Exception", MB_ICONEXCLAMATION | MB_OK); } return 0; }



讓我們一起看一下WinClass類。它封裝了被WNDCLASSEX呼叫的視窗定義結構,為它的所有欄位提供了合理的預設值。它來源於一個WinSimpleClass模板類,你可以使用去封裝一些固定的視窗型別(象按鈕,列表視)

我倘若有一個方法的例子可以不考慮預設。例如,SetBgSylor可以改變預設的視窗的客戶區的背景色。方法SetResIcons從資源中裝入適當的圖示,並把它們附在視窗類上。這些圖示將在當時顯示在主視窗和Windows的工作列中。

TopWinClass來由於WinClass的方法的使用。它同樣分配選單到頂層視窗類中。


class WinSimpleClass { public: WinSimpleClass (char const * name, HINSTANCE hInst) : _name (name), _hInstance (hInst) {} WinSimpleClass (int resId, HINSTANCE hInst); char const * GetName () const { return _name.c_str (); } HINSTANCE GetInstance () const { return _hInstance; } HWND GetRunningWindow (); protected: HINSTANCE _hInstance; std::string _name; }; WinSimpleClass::WinSimpleClass (int resId, HINSTANCE hInst) : _hInstance (hInst) { ResString resStr (hInst, resId); _name = resStr; } HWND WinSimpleClass::GetRunningWindow () { HWND hwnd = ::FindWindow (GetName (), 0); if (::IsWindow (hwnd)) { HWND hwndPopup = ::GetLastActivePopup (hwnd); if (::IsWindow (hwndPopup)) hwnd = hwndPopup; } else hwnd = 0; return hwnd; }



class WinClass: public WinSimpleClass { public: WinClass (char const * className, HINSTANCE hInst, WNDPROC wndProc); WinClass (int resId, HINSTANCE hInst, WNDPROC wndProc); void SetBgSysColor (int sysColor) { _class.hbrBackground = reinterpret_cast (sysColor + 1); } void SetResIcons (int resId); void Register (); protected: void SetDefaults (); WNDCLASSEX _class; }; WinClass::WinClass (char const * className, HINSTANCE hInst, WNDPROC wndProc) : WinSimpleClass (className, hInst) { _class.lpfnWndProc = wndProc; SetDefaults (); } WinClass::WinClass (int resId, HINSTANCE hInst, WNDPROC wndProc) : WinSimpleClass (resId, hInst) { _class.lpfnWndProc = wndProc; SetDefaults (); } void WinClass::SetDefaults () { // 提供合理的預設值 _class.cbSize = sizeof (WNDCLASSEX); _class.style = 0; _class.lpszClassName = GetName (); _class.hInstance = GetInstance (); _class.hIcon = 0; _class.hIconSm = 0; _class.lpszMenuName = 0; _class.cbClsExtra = 0; _class.cbWndExtra = 0; _class.hbrBackground = reinterpret_cast (COLOR_WINDOW + 1); _class.hCursor = ::LoadCursor (0, IDC_ARROW); } void WinClass::SetResIcons (int resId) { _class.hIcon = ::LoadIcon (_class.hInstance, MAKEINTRESOURCE (resId)); // 可以使用LoadImage從同樣的資源中裝入小圖示 _class.hIconSm = reinterpret_cast ( ::LoadImage ( _class.hInstance, MAKEINTRESOURCE (resId), IMAGE_ICON, ::GetSystemMetrics (SM_CXSMICON), ::GetSystemMetrics (SM_CYSMICON), LR_SHARED)); } void WinClass::Register () { if (::RegisterClassEx (&_class) == 0) throw WinException ("Internal error: RegisterClassEx failed."); }



class TopWinClass: public WinClass { public: TopWinClass (int resId, HINSTANCE hInst, WNDPROC wndProc); }; TopWinClass::TopWinClass (int resId, HINSTANCE hInst, WNDPROC wndProc) : WinClass (resId, hInst, wndProc) { SetResIcons (resId); _class.lpszMenuName = MAKEINTRESOURCE (resId); }



一旦視窗類在被註冊,你可以建立任意個你想要的這個類的視窗。他們將,當然,他們同享這個類註冊的過程。稍後我們將可以瞭解在過程內視窗的不同例項的區別。

WinMaker類的工作非常像WinClass。它的構造提供了切合實際的預設值,可以透過呼叫細節方法去覆蓋。一旦任何事都設定了,你呼叫Create方法去建立一個視窗,及呼叫Show方法去顯示它。注意,在呼叫Create的瞬間,你的視窗過程被WM_CREATE訊息呼叫。

頂層視窗使用TopWinMaker類建立,提供了適當的風格和標題。


class WinMaker { public: WinMaker (WinClass & winClass); operator HWND () { return _hwnd; } void AddCaption (char const * caption) { _windowName = caption; } void AddSysMenu () { _style |= WS_SYSMENU; } void AddVScrollBar () { _style |= WS_VSCROLL; } void AddHScrollBar () { _style |= WS_HSCROLL; } void Create (); void Show (int nCmdShow = SW_SHOWNORMAL); protected: WinClass & _class; HWND _hwnd; D _exStyle; // 擴充套件視窗風格 char const * _windowName; // 指向視窗名字的指標p DWORD _style; // 視窗風格 int _x; // 視窗的水平位置 int _y; // 視窗的升起位置 int _width; // 視窗寬 int _height; // 視窗高 HWND _hWndParent; // 父視窗或所有者的控制程式碼 HMENU _hMenu; // 選單的控制程式碼,或子視窗的識別符號 void * _data; // 指向視窗創造資料 }; WinMaker::WinMaker (WinClass & winClass) : _hwnd (0), _class (winClass), _exStyle (0), // 擴充套件視窗風格 _windowName (0), // 指向視窗的名字 _style (WS_OVERLAPPED), // 視窗風格 _x (CW_USEDEFAULT), // 視窗的水平位置 _y (0), // 視窗的升起位置 _width (CW_USEDEFAULT), // 視窗寬 _height (0), // 視窗高 _hWndParent (0), // 父視窗或所用者的控制程式碼 _hMenu (0), // 指向選單,或子視窗的識別符號 _data (0) // 指向視窗創造資料 { } void WinMaker::Create () { _hwnd = ::CreateWindowEx ( _exStyle, _class.GetName (), _windowName, _style, _x, _y, _width, _height, _hWndParent, _hMenu, _class.GetInstance (), _data); if (_hwnd == 0) throw WinException ("Internal error: Window Creation Failed."); } void WinMaker::Show (int nCmdShow) { ::ShowWindow (_hwnd, nCmdShow); ::UpdateWindow (_hwnd); } // 製造頂層重疊帶標題的視窗 TopWinMaker::TopWinMaker ((WinClass & winClass, char const * caption) : WinMaker (winClass) { _style = WS_OVERLAPPEDWINDOW | WS_VISIBLE; _windowName = caption; }



在我們開始下一步前,這有一個小的類。WinException丟擲任何時刻的Windows API的失敗。它需取回的Windows錯誤程式碼。(順便,使用FormatMessage很容易的轉換錯誤程式碼到字串。)

ResString類是對你的應用程式的字串資源中貯存的字串的簡單的封裝。


// exception類:貯存訊息和錯誤程式碼 class WinException { public: WinException (char* msg) : _err (::GetLastError()), _msg(msg) {} DWORD GetError() const { return _err; } char const * GetMessage () const { return _msg; } private: DWORD _err; char * _msg; }; // :丟擲異常 int NewHandler (size_t size) { throw WinException ( "Out of memory" ); return 0; } class ResString { enum { MAX_RESSTRING = 255 }; public: ResString (HINSTANCE hInst, int resId); operator char const * () { return _buf; } private: char _buf [MAX_RESSTRING + 1]; }; ResString::ResString (HINSTANCE hInst, int resId) { if (!::LoadString (hInst, resId, _buf, MAX_RESSTRING + 1)) throw WinException ("Load String failed"); }



Controller是一個特別的視窗例項的強健的系統。它建立了視窗,貯存了它,並在最後銷燬了它。在它的控制內你可以放置一些對特定視窗例項的描述資訊。在通常,器有一個視(透過在視窗的表面繪製進行互動)並且它有權使用model。它相當於你的應用程式的大腦(在這,MVC或Model-View-Controller透過Smalltalk程式師被呼叫)。

如果,這常常發生,你的應用程式只有一個最頂層的視窗,你可以在它的控制中立即插入model。這單一化的資源管理,這是以控制元件器與model的緊密聯絡的。在大的專案中應該避免這樣的聯接——首選在控制器裡使用一個指向model的指標。

更多的controller方法為了它們操作的利益需要一個指向視窗的控制程式碼。這個控制程式碼是每一個視窗訊息透過的控制程式碼,但它只是一次性的簡單的貯存它到controller物件裡,隨時都可以使用它。記住——在視窗例項(因為視窗的控制程式碼)和controller物件之間有一對一的通訊。


class Controller { public: Controller(HWND hwnd, CREATESTRUCT * pCreate); ~Controller (); void Size (int x, int y); void Paint (); void Command (int cmd); private: HWND _hwnd; Model _model; View _view; };



視窗過程是一個視窗應用程式的主器。你不能在你的程式視窗中呼叫它!每當某件有意義的事件發生,Windows傳送條訊息給你的程式。這條訊息被你的視窗過程傳遞。你可以處理它,或傳遞它到預設的視窗過程。

視窗過程被給定訊息指定的視窗呼叫的。這個控制程式碼是獨一無二的一個協調視窗例項的內部視窗資料。它發生時,我們可以訪問這些資料結構,及使用它去貯存一些例項指定的資料。這是典型的的訪問結構的途徑。順便,這個結構的GWL_USERDATA成員是所有視窗允許給出的。包含訊息框,對話方塊及平滑按鈕。


template inline T WinGetLong (HWND hwnd, int which = GWL_USERDATA) { return reinterpret_cast (::GetWindowLong (hwnd, which)); } template inline void WinSetLong (HWND hwnd, T value, int which = GWL_USERDATA) { ::SetWindowLong (hwnd, which, reinterpret_cast (value)); }



每當Windows呼叫我們的視窗過程時,我們應該首先找回它的控制物件。記住,在這可能幾個視窗共享相同的視窗過程,我們應該為每個視窗分別控制。當我們被呼叫後怎麼樣知道去使用哪個控制?我們透過找出視窗的控制程式碼。在這個控制程式碼裡我們貯存了指向這個特定視窗的控制器,使用Win[Set/Get]Long技巧。

視窗過程首先被WM_CREATE訊息呼叫。在那時我們建立了控制器物件並初始化了帶有視窗控制程式碼的它,且透過呼叫CREATESTRUCT指定了資料結構。一旦我們有了控制器,我們貯存指向在當前hwnd的標籤下相應的內部視窗的資料結構。在下次視窗過程被呼叫,帶有一個不同與WM_CREATE的訊息,我們只要簡單的取回指向我們控制器的指標,使用hwnd

這個支援是容易的。視窗過程解釋訊息引數及呼叫控制器的適當模組。


LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { Controller * pCtrl = WinGetLong (hwnd); switch (message) { case WM_CREATE: // 不得不在此捕捉異外錯誤! try { pCtrl = new Controller (hwnd, reinterpret_cast (lParam)); WinSetLong (hwnd, pCtrl); } catch (WinException e) { ::MessageBox (hwnd, e.GetMessage(), "Initialization", MB_ICONEXCLAMATION | MB_OK); return -1; } catch (...) { ::MessageBox (hwnd, "Unknown Error", "Initialization", MB_ICONEXCLAMATION | MB_OK); return -1; } return 0; case WM_SIZE: pCtrl->Size (LOWORD(lParam), HIWORD(lParam)); return 0; case WM_PAINT: pCtrl->Paint (); return 0; case WM_COMMAND: pCtrl->Command (LOWORD (wParam)); return 0; case WM_DESTROY: WinSetLong (hwnd, 0); delete pCtrl; return 0; } return ::DefWindowProc (hwnd, message, wParam, lParam); }



在這個簡單的例子中了少許的控制器方法。建構函式為稍後的使用不得不記住視窗的控制程式碼,解構函式不得不傳送離開訊息,Size方法傳遞它的引數到View,等等,我們將討論關於描繪視窗。目前,注意到控制器準備與View去一起描繪。

Controller::Controller (HWND hwnd, CREATESTRUCT * pCreate) :_hwnd (hwnd), _model ("Generic") { } Controller::~Controller () { ::PostQuitMessage(0); } void Controller::Size (int cx, int cy) { _view.SetSize (cx, cy); } void Controller::Paint () { // prepare the canvas and let View do the rest PaintCanvas canvas (_hwnd); _view.Paint (canvas, _model); // Notice: The destructor of PaintCanvas called automatically! }



當選擇了選單條目之一時,產生WM_COMMAND訊息視窗過程被呼叫。適當的控制方法當足於命命令id分配命令。當你利用你的資源編輯器建立了你的選單時,你為每個選單條目選取了這些標識id。它們被貯存在適當的標頭檔案裡(可能在resource.h裡) ,不得不被包含到控制器的原始檔中。

我們的選單僅僅包含三個條目,它們的標識id為IDM_EXITIDM_HELPIDM_ABOUT。響應IDM_ABOUT時對話方塊被顯示,它也是利用資源編輯器建立的,給定的標識id為IDD_ABOUT。它的對話方塊過程是AboutDlgProc

最後,為了顯示一個對話方塊我們需指定應用程式例項的控制程式碼。標準的途徑是利用應用程式的hwnd訪問內部視窗資料結構取回它。


// 選單命令處理 void Controller::Command (int cmd) { switch (cmd) { case IDM_EXIT: ::SendMessage (_hwnd, WM_CLOSE, 0, 0L); break; case IDM_HELP: ::MessageBox (_hwnd, "Go figure!", "Generic", MB_ICONINFORMATION | MB_OK); break; case IDM_ABOUT: { // 透過HWND取得例項控制程式碼 HINSTANCE hInst = WinGetLong (_hwnd, GWL_HINSTANCE); ::DialogBox (hInst, MAKEINTRESOURCE (IDD_ABOUT), _hwnd, AboutDlgProc); } break; } }



視物件通常貯存客戶範圍的尺寸。它們隨著控制器過程的WM_SIZE訊息。首先WM_SIZE訊息是在視窗創造期間和WM_PAINT訊息前傳送的,因此我們可以在描繪呼叫時安全的呈現已知客戶範圍的尺寸。

圖形輸出到視窗是透過適當的Canvas物件的方法完成的。倘若,我們列印的文字是原型是從客戶範圍的邊緣繪製一個10個象素的垂直行。


class View { public: void SetSize (int cxNew, int cyNew) { _cx = cxNew; _cy = cyNew; } void Paint (Canvas & canvas, Model & model); protected: int _cx; int _cy; }; void View::Paint (Canvas & canvas, Model & model) { canvas.Text (12, 1, model.GetText(), model.GetLen()); canvas.Line (10, 0, 10, _cy); }



canvas物件封裝了Windows所謂的裝置描述符。我們的Canvas非常的簡單,我們只要知道怎樣列印文字和繪製行,但你的Canvas可以有很多的方法去做創造性的事情。我們將在下一個指南里告訴關於Canvas的更多。

class Canvas { public: operator HDC () { return _hdc; } void Line ( int x1, int y1, int x2, int y2 ) { ::MoveToEx (_hdc, x1, y1, 0); ::Lo (_hdc, x2, y2); } void Text (int x, int y, char const * buf, int cBuf) { ::TextOut ( _hdc, x, y, buf, cBuf ); } void Char (int x, int y, char c) { ::TextOut (_hdc, x, y, & c, 1); } protected: // 保護建構函式:你不能建造 // 一個Canvas物件,但你可 // 以從它的來源構造物件。 Canvas (HDC hdc): _hdc (hdc) {} HDC _hdc; };



你建造的canvas用來響應WM_PAINT訊息到特定種類。它們透過呼叫BeginPaint獲得裝置描述符並透過呼叫EndPaint來釋放。PAINTSTRUCT包含關於哪部分使用者範圍應該被寫的附加資訊,上當我們忽視這種詳細資料,但如果你嚴肅的關注,你應該學習關於它的更多。

// canvas的具體的例項。 // 在WM_PAINT訊息後建立這個物件。 class PaintCanvas: public Canvas { public: // Constructor obtains the DC PaintCanvas (HWND hwnd) : Canvas (::BeginPaint (hwnd, & _paint)), _hwnd (hwnd) {} // 解構函式釋放DC ~PaintCanvas () { ::EndPaint(_hwnd, & _paint); } protected: PAINTSTRUCT _paint; HWND _hwnd; };



CopyRight by VCROAD ©2000-2001 All Right Reserve

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-992125/,如需轉載,請註明出處,否則將追究法律責任。

相關文章