Windows應用程序的面向對象認識
面向對象作為一種方法學
要求將程序中的數據和操作(代碼)歸結到某些對象名下
將數據看作對象的屬性
要改變這些屬性
必須通過操作來進行
進行面向對象的程序設計最好使用面向對象的語言
如C++
SamllTalk等
面向對象的語言的語言所起的作用
就是給程序員們提供一些進行面向對象的程序設計時必需的約束
使數據和操作的銜接有一種顯式的描述
並進行一些技術性的事務管理
但是
如果我們能理解面向對象程序設計的原理和方法
即使不使用面向對象的語言
也能實現面向對象的程序設計
Windows本身並不是一個面向對象的程序設計環境
但Windows的某些部分還是明顯地受到面向對象的軟件的概念的影響
從某種程度上說
在進行Windows程序設計時
程序員是在進行面向對象的程序設計
理解Windows的面向對象的思想和應用程序設計的面向對象方法對設計結構合理的應用程序會有很大的幫助
前面已給出了對象的定義
每個對象包含有數據和代碼
代碼描述了對象可執行的一系列預定義的動作
而數據是對象私有的
它們由相關的可執行代碼存取
預定義的動作和私有數據的結合稱為封裝
在C中
我們使用一個函數來封裝一個對象的私有數據和動作
使用switch語句來定義預定義的動作
這些動作只存取為該函數本身所知道的數據
Windows和Windows應用程序是怎樣發送消息的呢?在Windows及其應用程序中
消息被表示為一個數據結構
並能在對象之間傳遞
發送消息等價於執行其參數表示消息數據的函數調用
參數之一是一個標識該消息的預定義的消息標識符
當一個對象接受到一條消息時
消息標識符決定該對象執行何種動作
消息傳遞是以函數調用的形式來實現的
這種調用可以發生在程序的任何地方
Windows程序員必須清楚用消息引發動作的技術
不同的對象能以不同的動作響應同樣的消息
這樣
一個特定的消息可代表一個通用事件
例如
按鍵操作
移動鼠標或繪制用戶區等
而任何一個特定的消息可以在不同的對象中引發不同的動作
例如
不同的窗口對象以不同的動作處理同樣的WM_KEYDOWN
WM_MOUSEMOVE或WM_PAINT消息
一個消息可以有一個對象發送到另一個對象
或由Windows發送到某個對象
例如
WM_KEY_DOWN之類的消息是由Windows產生的
有些消息在對象的窗口函數對其處理完畢後就消失了
而有些消息在處理時有產生新的消息
一個對象通過向其它對象或自己發送一條或多條消息來處理一條消息
這樣
Windows應用程序的控制流程不象MS
DOS應用程序那樣易於跟蹤
程序的調試也比MS
DOS應用程序困難
除了個別消息以外
對象接受消息的順序是不可預知的
但對象處理每條消息所采取的動作是顯式定義在窗口函數中的
對象並不顯式地定義所有可能消息的動作
對於不顯示處理的消息
都交由DefWindowProc進行缺省處理
消息傳遞的途徑很簡單
從一個對象傳遞到另一對象
但由於DefWindowProc對有些消息提供了缺省處理
因此
程序員在設計程序時必須考慮在一個窗口函數中捕獲某條消息時是否還應交給DefWindowProc函數作進一步的處理
DefWindowProc能處理所有的消息
但對大部消息只是簡單地廢棄之
不作具有實際意義的處理
在窗口函數捕獲這些廢棄消息是安全的
若要捕獲其它消息
則必須了解DefWindowProc是怎樣處理這條消息的
並在窗口函數的處理代碼中能提供類似的處理(或將該消息交由DefWindowProc作進一步的處理)
現在我們討論窗口函數對對象的私有數據的處理問題
窗口類也說明了對象的私有數據
當調用CreateWindow創建一個窗口對象時
Windows為創建的窗口對象分配私有數據存儲區
其中存儲有窗口的實例句柄
父窗口句柄
窗口函數的地址和其它Windows用於管理窗口對象的數據
對這些私有數據的的操作只能使用GetWindowWord/GetWindowLong等函數
對於程序中說明的變量
如何在窗口函數中將它們與相關的對象銜接在一起就比較復雜
因為窗口函數為該類的所有對象共享
該類的所有對象在接收到消息時都執行相同的代碼
在過去
Windows推薦使用的程序設計語言是C
由於C語言不具備將一個對象的私有數據和操作這些私有數據的代碼銜接在一起的語言成份(面向對象的語言的事務性工作之一就是為程序完成這個工作)
這個工作只能由程序員來作
程序員心中必須清楚程序中所說明或分配的變量私有於哪個對象
並采用合適的數據結構來表示它們
以便程序在使用它們時
能根據不同的對象將它們區別開來
有幾種方法可用於區分對象的私有數據
程序員編制額外的代碼來判斷一個對象應使用哪些數據
使用窗口附加字節
使用屬性表
當使用第一種方法時
程序實際是使用對象句柄作索引來檢索與該對象相關的私有數據
Windows也使用這種方法使用句柄來檢索一張表
這個表中存儲著該句柄所標識的對象的私有數據
Windows的許多函數需要一個對象的句柄作為第一參數
其原因就是為區分對象的私有數據
以便使用相同的函數處理不同的對象(的數據)
後兩種方法與第一種方法本質是一樣的(我們會將在後面的章節對其進行介紹)
只是Windows提供了一些相關的函數來簡化程序的工作
由於C沒有繼承這種語言成分
因為
也就不能形成對象的等級結構
繼承是面向對象語言的另一個重要成分
繼承使得程序中的對象形成一個分層次的對象結構
低層次的對象可以將它不處理的消息發送到高層對象上進行缺省處理
由於在C中不能(或說很難)建立對象的這種等級結構
但為了簡化應用程序的設計
又必須要求支持消息的缺省處理(否則應用程序要定義一個窗口對象可能接收到的所有消息的處理代碼)
因此只能使用DefWindowProc提供消息的缺省處理
這就要求對一個窗口對象所有消息的處理定義在一個函數中
就帶來了定義窗口函數的返回值和參數類型時使用了一種較難為人理解的方法
因為不同的消息可以帶有不同類型和個數的參數
並且返回數據的類型也不相同
Windows的設計者采用了一個折中的方法
為消息規定一個十六位的參數和一個
位的參數
將返回類型指定為LRESULT
這種類型的長度能容下C中所有預定義類型的數據
由於不同類的窗口對象定義有自己的窗口函數
但C語言不具備根據接受消息的對象自動決定調用該對象的窗口函數的能力(在面向對象的語言中
這種能力被稱為多態性)
因此
向不同的窗口對象發送消息時使用函數SendMessage對窗口函數作間接調用
由Windows根據該函數調用中所使用的對象標識符來調用該對象的窗口函數
在程序設計中由於窗口函數的限制
需經常進行各種各樣的數據類型轉換
例如
SendMessage(hwnd
WM_USER
(WPARAM)
MAKELPARAM(
));
在這個例子中
為了組建一個LPARAM類型的數據
使用了宏MAKEPARAM
它將兩個十六位的數據組裝成一個
位的數據(低位字為MAKEPARAM的第一個參數
高位字為第二個參數)
當需要從一個LPARAM類型的數據中分離出低位字和高位字時
使用宏LOWORD和HIWORD
例如
處理上個例子中所發送的WM_USER消息的窗口函數的代碼可能為
WORD wStart = LOWORD(lParam);
WORD wStart = LOWORD(lParam);
宏MAKELRESULT與MAKELPARAM類似
它被用於裝配LRESULT類型的數據
宏MAKELONG用於裝配LONG類型的數據
當需要從LRESULT或LONG類型的數據中分離出高位字和低位字時
使用宏HIWORD和LOWORD
基於上面的介紹
我們在設計Windows應用程序時
要明確程序中存在哪些對象
對象之間是如何通過消息傳遞程序控制的
哪些數據是對所有對象公有的
哪些數據是私有於某一個對象的
公有數據和對象的私有數據必須是存儲在靜態生存期的變量中(局部生存期的變量在窗口函數返回後就消失了
不能在下次調用該函數時保存上次的值
換句話說
存儲對象的數據的變量的生存期不應小於對象的生存期)
由於Windows應用程序各個模塊之間主要是通過消息傳遞控制
因此
Windows應用程序的邏輯結構就不同於MS
DOS應用程序的邏輯結構
如圖
所示
從圖
可以看出
Windows應用程序的各個模塊通過消息傳遞被聯系在一起
因此
如果正確地組織程序
程序的模塊性和結構較MS
DOS應用程序要好
圖 DOS應用程序與Windows應用程序邏輯結構的比較示意說明
Windows程序的組織
將節介紹的程序按照C/C++語言的要求組織起來就得到一個完整的Windows程序一個Windows程序必須有一個名為WinMain的主函數
// c 代碼片段
#include <windowsh>
LRESULT CALLBACK WndProc(HWND UINT WPARAM LPARAM);
int PASCAL WinMain(
HINSTANCE hInstance // 應用程序的實例句柄
HINSTANCE hPrevInstance // 該應用程序前一個實例的句柄
LPSTR lpszCmdLine // 命令行參數串
int nCmdShow) // 程序在初始化時如何顯示窗口
{
char szAppName[] = Window;
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
if (!hPrevInstance) {
// 該實例是程序的第一個實例注冊窗口類
wndclassstyle = CS_VREDRAW | CS_HREDRAW;
wndclasslpfnWndProc = WndProc;
wndclasscbClsExtra = ;
wndclasscbWndExtra = ;
wndclasshInstance = hInstance;
wndclasshIcon = LoadIcon(hInstance IDI_APPLICATION);
wndclasshCursor = LoadCursor(NULL IDC_ARROW);
wndclasshbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wndclasslpszMenuName = NULL;
wndclasslpszClassName = szAppName;
if (!RegisterClass(&wndclass))
// 如果注冊失敗
return FALSE;
}
// 對每個實例創建一個窗口對象
hwnd = CreateWindow(
szAppName
Sample Program
WS_OVERLAPPEDWINDOW
CW_USEDEFAULT CW_USEDEFAULT
CW_USEDEFAULT CW_USEDEFAULT
NULL
NULL
hInstance
NULL);
ShowWindow(hwnd nCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg NULL )) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msgwParam;
}
WinMain函數是Windows應用程序開始執行時的入口點它的返回類型為intWinMain函數的作用十分類似於MSDOS中的C應用程序的main函數
WinMain帶有四個參數參數hInstance和hPrevInstance是程序的實例句柄在Windows環境下可以運行同一個程序的多個拷貝每一個拷貝都是該應用程序的一個句柄每個實例使用一個實例句柄進行標識hInstance是標識當前程序的實例的句柄它的值不會為NULL如果在此之前Windows中已經運行了該程序的另一個實例則這個實例的句柄由參數hPrevInstace給出如果在運行該程序時Windows環境中不存在該程序的另一個實例則hPrevInstance為NULL
我們曾經說過對同一個類不能向Windows注冊一次以上在這個程序中通過判別hPrevInstance的值是否為NULL來決定是否應向Windows注冊窗口類這樣的程序邏輯保證了只在該程序的第一個實例中注冊窗口類
參數lpszCmdLine中包含有運行程序時傳遞給程序的命令行參數例如若以這樣的命令運行該程序Sampleexe Programming Windows則lpszCmdLine將指向字符串Programming Windows
最後一個參數nCmdShow是一個int類型的整數用以說明在程序被裝如內存時Windows以何種方式顯示這個程序的窗口根據運行程序的方式不同該參數被設置為SW_SHOWNORMAL或SW_SHOWMINNOACTIVESW的含義是Show Window(顯示窗口)這兩個參數的含義在後面介紹
在程序Sample
CPP中
有幾個函數我們未曾介紹
表
給出了這些函數的說明
表 ShowWindow 函數
用 途
顯示或改變給定的窗口
原 型
BOOL ShowWindow(
HWND hWnd
指定一個窗口對象
int nCmdShow
指定窗口的顯示方式
);
返回值
返回該窗口更新前的窗口狀態
對先前可見的窗口
其值為非零
對先前隱藏的窗口
其值為零
顯示方式(nCmdShow)可以是下列常量之一
類型
說明
SW_HIDE
隱藏該窗口(並是另一個窗口激活)
SW_MINIMIZE
使窗口變成圖標(並激活窗口管理表的頂層窗口)
SW_SHOW
激活一個窗口
並根據其當前的尺寸和位置顯示該窗口
SW_SHOWMAXIMIZED
激活並以全屏方式顯示一個窗口
SW_SHOWMINIMIZED
激活並以圖標方式顯示一個窗口
SW_SHOWMINNOACTIVE
以圖標方式顯示一個窗口
當前活動的窗口仍保持活動
SW_SHOWNA
以當前狀態顯示一個窗口
當前活動的窗口仍保持活動
SW_SHOWNOACTIVE
以最近的大小和位置顯示一個窗口
當前活動的窗口仍保持活動
SW_SHOWNORMAL
激活並顯示一個窗口
若其為圖標或全屏方式顯示
則恢復為它的原始大小和位置
SW_RESTORE
同SH_SHOWNORMAL
表 UpdateWindow 函數
用 途
若應用程序的消息隊列中存在WM_PAINT消息(繪制用戶區消息)
則該函數使Windows立即調用窗口函數
向其傳遞WM_PAINT
否則該函數不作為任何動作
原 型
VOID UpdateWindow(
HWND hWnd
標識被刷新的窗口的句柄
);
返回值
無
表 GetMessage 函數
用 途
從應用程序中的消息隊列中檢索一條消息
原 型
BOOL GetMessage(
LPMSG lpMsg
指向MSG類型的變量的遠指針
它包含有從應用程序消息隊列中檢索到的一條消息的數據
HWND hWnd
指定為哪個窗口檢索消息
如果hWnd為NULL
則檢索調用該函數的應用程序的所有的消息(不檢索屬於其它應用程序的消息)
UINT wMin
UINT wMax
以下兩個基本參數指定檢索在wMin和wMax范圍內的消息
如果這兩個參數都為零
該函數檢索所有的可用的消息
);
返回值
在檢索出WM_QUIT消息時
返回零值
在其它情況下返回非零值
表 DispatchMessage 函數
用 途
將消息發送到指定的窗口對象上(窗口函數被調用)
原 型
LRESULT DispatchMessage(
LPCMSG lpMsg
指向MSG類型變量的遠指針
該變量中存儲有來自應用程序消息隊列中的消息
);
返回值
若有一個WM_CHAR消息被放到應用程序的消息隊列中
返回非零
否則返回零
該函數不改變lpMsg所指向的變量中存儲的消息數據
Windows的主函數都是首先以初始化(注冊類
創建對象等)這一步開始
而且緊跟著就是消息循環運行這一步
這些步驟對所有的Windows應用程序都大同小異
Windows應用程序主要的不同點在窗口函數的定義上
由於一個應用程序所解決的任務不同
它的窗口函數對消息的處理方式也就不相同
因而每個應用程序需要定義不同的窗口函數
LRESULT CALLBACK WndProc (HWND hwnd
UINT message
WPARAM wParam
LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(
):
return
;
}
return DefWindowProc(hwnd
message
wParam
lParam);
}
這個窗口函數僅處理一條WM_DESTROY消息
這條消息是在用戶關閉了屏幕上的窗口時
Windows發送給窗口對象的
該函數對這條消息的處理只是簡單地調用Windows函數PostQuitMessage
表
給出了函數PostQuitMessage的說明
當主函數的消息循環中的GetMessage函數檢索出WM_QUIT消息時
函數GetMessage返回零
這樣
消息循環終止
程序也隨之被終止
存儲消息數據的變量msg的wParam域的值是在調用函數PostQuitMessage時所提供的實參的值
如果程序正常結束
調用PostQuitMessage函數時使用零作為該函數的參數
如果需要表示程序由於出現了異常或錯誤而必須終止時
使用非零值(一般使用
)作為該函數的參數
在調用PostQuitMessage使用的參數值被主函數用語句
return msg
wParam;
返回給Windows
供Windows或其它應用程序使用
因此
我們也稱PostQuitMessage使用的參數為程序的退出碼
表 PostQuitMessage 函數
用 途
通知Windows
應用程序希望中止
它一般用於響應WM_DESTROY消息
該函數將消息WM_QUIT消息放入應用程序的消息隊列中
原 型
PostQuitMessage(
int nExitCode
指定應用程序的退出代碼
它用作WM_QUIT消息的wParam參數
);
返回值
無
小結 本章首先介紹了圖形用戶界面的優點和面向對象的程序設計方法
從某種意義上說
Windows是面向對象的
它主要建立在把窗口作為一個對象的概念上
窗口之間通過消息進行消息傳遞
Windows支持直接操作技術
直接操作是對屏幕對象的操作
數據和函數的封裝允許該對象自己響應它們接收到的消息
在用戶界面上發生的任何事件被作為消息發送給窗口對象
程序員在設計程序時
只須關心一個對象要接受哪些消息和怎樣處理這些消息
消息傳遞工作由Windows負責
因而
使用Windows操作環境可以極大地方便程序開發用戶界面的工作
並使程序的結構合理
模塊化程序高
更重要的是
支持直接操作技術的Windows支持用戶進行有創造性的界面設計
From:http://tw.wingwit.com/Article/Common/201311/5234.html