目 錄
第二篇、創(chuàng)建Win32工程和主函數(shù)
第五篇、利用已注冊(cè)的窗口類來創(chuàng)建一個(gè)窗口
第九篇、窗口標(biāo)題欄上顯示自定義圖標(biāo)(手動(dòng)編輯代碼)
白云小飛
1 說在前面
由于VC6及MFC的特點(diǎn),我們?cè)S多人從標(biāo)準(zhǔn)C++學(xué)習(xí)到VC6MFC應(yīng)用程序的編程學(xué)習(xí)的過度會(huì)有一個(gè)很大的夸躍,從而感到非常的吃力。
究其原因之一:MFC類庫(kù)設(shè)計(jì)雖然精巧,但我們?cè)谑褂肕FC設(shè)計(jì)程序時(shí),會(huì)發(fā)現(xiàn)MFC到處是API函數(shù)的影子。MFC并沒有象Delphi的VCL類庫(kù),VB的控件庫(kù)一樣封裝得讓人幾乎完全不用知道還有Win32API函數(shù)及其操作原理,所以要想利用VC6的MFC編程,我認(rèn)為就一定要先學(xué)習(xí)如何直接用Win32API函數(shù)來編程。對(duì)API編程中的一些關(guān)鍵的概念和原理要有一定認(rèn)識(shí),這樣才會(huì)有一個(gè)比較平滑的過渡。以上就是我寫這個(gè)系列的初衷。
2 我假設(shè)你已有的知識(shí):
這里我假設(shè)你已經(jīng)掌握了如下的知識(shí),如果你在如下方面知識(shí)有點(diǎn)不太清楚,那要去補(bǔ)一補(bǔ)羅,否則你看到相關(guān)的內(nèi)容時(shí)會(huì)有麻煩的。
下面說是我對(duì)你知識(shí)的假設(shè):
2.1 Windows系統(tǒng)的文件、文件夾、路徑的概念
2.2 C語(yǔ)言的基本知識(shí)(基本以等級(jí)考試二級(jí)C語(yǔ)言為準(zhǔn),還要有所擴(kuò)充)
2.2.1 指針的概念。
2.2.2 函數(shù)指針概念。
2.2.3 各種自定義類型(最重要的是struct類型)的概念。
2.2.4 要知道函數(shù)的各種參數(shù)傳遞形式(值、地址、引用傳遞)。
2.2.5 typedef及其應(yīng)用。
2.2.6 #include及其應(yīng)用。
2.2.7 十進(jìn)制、二進(jìn)制、十六進(jìn)制。
2.2.8 按位與、或、非運(yùn)算的實(shí)質(zhì)。
2.2.9 宏定義概念、使用及意義。
(每個(gè)人總是學(xué)完了C或C++語(yǔ)法后才會(huì)開始用VC6進(jìn)行Windows編程學(xué)習(xí)的。但是你的基礎(chǔ)又是如何呢?這是一個(gè)關(guān)鍵。因此我對(duì)你的C知識(shí)做了具體的假設(shè)。)
2.3 會(huì)安裝VC6.0并安裝到一臺(tái)機(jī)上
2.4 VC6編譯界面的各組成部分及基本操作(至少會(huì)用VC6寫控制臺(tái)程序)。
2.5 VC6調(diào)試中至少要會(huì)設(shè)置斷點(diǎn)哦。
(呵呵!我的要求不過份吧!)
3 還必須預(yù)備的知識(shí):
以上知識(shí)是你看本系列的前提,不過我還要給你預(yù)備一下我們?cè)龠@個(gè)階段學(xué)習(xí)中會(huì)遇到的新東西。
3.1 你將會(huì)接觸到的Win32API函數(shù)庫(kù):
以前的DOS下或Windows的控制臺(tái)程序下,你要在顯示器上輸出文字,要用printf(),或cout的函數(shù)對(duì)象來完。但如果你要顯示一個(gè)圖形或圖象或?yàn)槟愕某绦蛟O(shè)計(jì)一個(gè)圖形化的操作界面等等的,那可就慘了,一切都要你自已完成。復(fù)雜得很了!(唉!誰(shuí)叫DOS是字符界面的操作系統(tǒng)呢?。?br> 現(xiàn)在好了,在Windows下編程你可就輕松得多了。因?yàn)閃indows操作系統(tǒng)都為我們準(zhǔn)備好了,它提供給我們多達(dá)數(shù)千個(gè)函數(shù)(??!我要昏倒了。這么多的函數(shù)要學(xué)。),我們通過這些函數(shù)來操作Windows系統(tǒng)提供給我們的各種功能。比如我要在桌面上創(chuàng)建并顯示一個(gè)窗口。就只要調(diào)用幾個(gè)相關(guān)的被稱為API的函數(shù),讓W(xué)indows來幫助我們完成這些事。我們是通過這些函數(shù)與Windows系統(tǒng)交互的,所以這些函數(shù)被稱作Win32應(yīng)用程序接口函數(shù),簡(jiǎn)稱Win32API函數(shù)。
請(qǐng)不用害怕喲!其實(shí),這么多的函數(shù)我們不必都馬上一一學(xué)過,只要掌握了不多的具有代表性的函數(shù)的使用方法,并知道大體API函數(shù)都提供了哪些功能就可以了。以后要用時(shí)再去查。
Window擁有現(xiàn)成的各種各樣的系統(tǒng)功能,供我們的程序調(diào)用。那么又是通過什么方式來調(diào)用這些系統(tǒng)功能呢?原來,Window還現(xiàn)成提供一個(gè)接口,好讓我們的程序來使用這些系統(tǒng)功能,這個(gè)結(jié)口就是Win32API函數(shù)了(注:API是應(yīng)用程序接口的英文縮寫)。Win32API函數(shù)是我們的應(yīng)用程序與Windows系統(tǒng)交互的唯一途徑。
我并不打算這時(shí)就介紹任何一個(gè)具體的API函數(shù)。你現(xiàn)在只要知道你又要接觸一個(gè)函數(shù)庫(kù)了——被稱為Win32API的函數(shù)庫(kù),如同你以前所學(xué)的C/C++函數(shù)庫(kù)。
哈哈,這真是太好了,我們不用再象DOS一樣,自已來完成程序界面的繪制了。我們現(xiàn)在又增加一個(gè)全新的函數(shù)庫(kù),只要調(diào)用幾個(gè)相關(guān)API函數(shù),剩下的一切由Windows來完成就可以啦?。ó?dāng)然還有很多其它功能。)
3.2 “新”的數(shù)據(jù)類型:
學(xué)完C、C++之后,我們就可以開始進(jìn)入VC6的Windows編程學(xué)習(xí)了。但是在接下來的學(xué)習(xí)中我們會(huì)發(fā)現(xiàn)在Windows編程中有許多“新”的數(shù)據(jù)類型。看下面:
BOOL、BYTE、INT、UINT、WORD、DWORD、FLOAT、CHAR、LPSTR、HINSTANCE、HWND、HMENU、HICON等等。
你看這些大寫的數(shù)據(jù)類型,你以前有見過嗎?還有很多哦!我們以后的學(xué)習(xí)過程中還會(huì)見到的。(呵呵!你可要有思想準(zhǔn)備了?。?br> 這真是讓我們初學(xué)者迷惑呀!難道VC6中對(duì)C/C++的基本數(shù)據(jù)類型又有重大的擴(kuò)充了嗎?
其實(shí)不用害怕,只是用新瓶裝舊酒而已了。在VC6的windef.h頭文件中已有這些定義:
typedef int BOOL;
typedef unsigned char BYTE;
typedef int INT;
typedef unsigned int UINT;
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef float FLOAT;
在winnt.h中有
typedef long LONG;
typedef char CHAR;
typedef CHAR *LPSTR, *PSTR;
你看其中(粗體字)CHAR只不過是char 的別名而已,也就是說它們是等價(jià)的。只要你包含了相關(guān)的頭文件,然后你就可以這樣申明一個(gè)變量:
INT i; //等同于int i;
CHAR a; //等同于char a;
LPSTR pa; //等同于char *pa;
明白了嗎?
我想你一定會(huì)問:為什么要這樣轉(zhuǎn)義呢?我們直接用int 、unsighed int、char等等不就行了嗎?我一句兩句也說不清,你只要知道,微軟這樣做一定是要道理的。
哦!還有這些HINSTANCE、HWND、HMENU、HICON我沒說呢!今后你還會(huì)見到許多這樣以H為開頭的數(shù)據(jù)類型,下面就讓我在下一節(jié)的“句柄”概念中說給你聽。
3.3 “句柄”概念
由windows系統(tǒng)創(chuàng)建出來的或加載的對(duì)象(如應(yīng)用程序進(jìn)程、線程、窗口、菜單、圖標(biāo)、光標(biāo)等等的對(duì)象),windows系統(tǒng)都會(huì)分配給它們一個(gè)唯一的標(biāo)識(shí)值,作為這些對(duì)象的標(biāo)志,稱之為句柄。我們程序中對(duì)這些對(duì)象的操作其實(shí)就是對(duì)其句柄的操作。請(qǐng)記住,句柄就是這些對(duì)象的“代號(hào)”了。
在編程序中,我們需要用相應(yīng)的句柄變量來保存這些句柄值,那么用什么類型的句柄變量呢?
就是我們前面提到過的HINSTANCE、HWND。
像其它變量一樣(如:int a;)申明句柄變量,如下:
HINSTANCE hst; //hst變量可以保存某個(gè)應(yīng)用程序?qū)嵗匆粋€(gè)進(jìn)程)的句柄。
HWND hwFirst; //hwFrist變量可以保存某個(gè)窗體句柄。
HMENU hMenu; //hMenu變量可以保存某個(gè)菜單句柄。
HICON hIcon; //hIcon變量可以保存某個(gè)圖標(biāo)句柄。
具體的使用讓我以后再慢慢與你道來啦。
那么這些類型的實(shí)質(zhì)又是什么?
目前,它們都只是一個(gè)int類型(小語(yǔ):我聽說微軟也許以后會(huì)改變它的類型)。不過不管怎樣,你現(xiàn)在只要把HINSTANCE、HWND、HMENU、HICON當(dāng)做是一個(gè)獨(dú)立的數(shù)據(jù)類型就可以了。
3.4 消息標(biāo)識(shí)
Windows系統(tǒng)是一個(gè)基于消息的系統(tǒng)。這樣的機(jī)制導(dǎo)致我們的程序與以往DOS下的程序流程會(huì)有很大的不同。(這可是很考我們的智慧嘍?。?br>
從軟件使用者角度看一個(gè)Win32窗口程序運(yùn)行的過程:
1) 我們運(yùn)行一個(gè)應(yīng)用程序,程序創(chuàng)建并顯示一個(gè)我們想要的程序窗口。
2) 當(dāng)我們對(duì)窗口進(jìn)行操作時(shí)(如單擊、雙擊、右擊、按下鍵盤、最大化、最小化、關(guān)閉窗口等等),程序會(huì)完成特定的操作,如:?jiǎn)螕糇畲蠡?、最小化按鈕時(shí),窗口會(huì)最大化、最小化操作;對(duì)窗口中菜單項(xiàng)的選取時(shí),會(huì)完成該菜單的相應(yīng)功能。
從程序員的角度看一個(gè)Win32窗口程序運(yùn)行的過程:
1) 我們運(yùn)行一個(gè)應(yīng)用程序,程序中我們通過Win32API函數(shù)創(chuàng)建并顯示一個(gè)我們想要的程序窗口。(由我們的程序來調(diào)用函數(shù)實(shí)現(xiàn))
2) 當(dāng)我們對(duì)窗口進(jìn)行操作時(shí)(如單擊、雙擊、右擊、按下鍵盤、最大化、最小化、關(guān)閉窗口等等),窗口會(huì)自動(dòng)產(chǎn)生一系列相應(yīng)的消息(這是由操作系統(tǒng)實(shí)現(xiàn)的)。
3) 具體地講:當(dāng)我們改變窗口大小時(shí),會(huì)產(chǎn)生WM_SIZE消息;單擊關(guān)閉按鈕關(guān)閉窗口時(shí),會(huì)產(chǎn)生WM_CLOSE消息;選取某一菜單項(xiàng)時(shí),會(huì)產(chǎn)生WM_COMMAND消息;按下鍵盤時(shí),會(huì)產(chǎn)生WM_CHAR、WM_KEYDOWN、WM_KEYUP消息;單擊鼠標(biāo)左鍵時(shí),會(huì)產(chǎn)生WM_LBUTTONUP、WM_LBUTTONDOWN消息等等。啊,很多很多,我也不必全部羅列出來了。(我說過了,這些都是由操作系統(tǒng)實(shí)現(xiàn)的)
4) windows系統(tǒng)會(huì)將這些消息排入我們窗口所在線程的消息隊(duì)列中(你會(huì)明白線程是什么嗎?)(也由Window操作系統(tǒng)實(shí)現(xiàn)),這樣我們的程序才有機(jī)會(huì)獲取并處理這些產(chǎn)生的消息。
5) 我們的程序可以通過Window操作系統(tǒng)提供的API函數(shù)來獲取這些消息及相關(guān)的信息。然后通過我們學(xué)過的條件判斷語(yǔ)句來判斷是什么消息及其相關(guān)的操作信息并可編寫相應(yīng)的程序代碼,從而實(shí)現(xiàn)對(duì)窗口操作的不同反應(yīng)。(由我們的程序來實(shí)現(xiàn))
看上述的過程描述,你可能要有點(diǎn)的抽象思維能力了。你現(xiàn)在只要有對(duì)程序流程有如上的大體認(rèn)知就可以了。慢慢地我會(huì)將上述流程變成確實(shí)的程序代碼噢!
(等等,還是有個(gè)問題:這些WM_CLOSE、WM_COMMAND、WM_CHAR、WM_KEYDOWN、WM_KEYUP、WM_LBUTTONUP、WM_LBUTTONDOWN等等的以WM_開頭的消息到底又是什么東西呢?)
看VC6的頭文件winuser.h中的片段:
……
#define WM_CLOSE 0x0010
……
#define WM_LBUTTONDOWN 0x0201
#define WM_LBUTTONUP 0x0202
#define WM_LBUTTONDBLCLK 0x0203
#define WM_RBUTTONDOWN 0x0204
……
#define WM_KEYDOWN 0x0100
#define WM_KEYUP 0x0101
#define WM_CHAR 0x0102
……
#define WM_INITDIALOG 0x0110
#define WM_COMMAND 0x0111
#define WM_SYSCOMMAND 0x0112
……
哦!這些WM_開頭的所謂的消息只不過是一系列16進(jìn)制整型數(shù)值的符號(hào)常量而已。每一個(gè)不同的整型數(shù)值代表著一個(gè)窗口某一操作的標(biāo)識(shí),因此我們將這些數(shù)值或者說以WM_開頭的符號(hào)常量稱之為消息了。
也就說,我們?cè)诖翱谥凶鞲鞣N不同的操作,Windows系統(tǒng)會(huì)產(chǎn)生各種相應(yīng)的數(shù)值。我們就是通過條件語(yǔ)句比較這些數(shù)值來判斷我們?cè)诖翱谥兴龅牟僮鞯摹?br>
3.5 資源標(biāo)識(shí)
(你看我沒完沒了地介紹一個(gè)個(gè)概念,覺得煩不煩?不用你說,我自已也有點(diǎn)煩了。唉!不過這些似乎是必要的,所以我不得不堅(jiān)持下去。不過,還好,剩下的不多了。)
那么VC6中資源是什么一種概念呢?
我們的程序中可能要用到各種圖標(biāo)(*.ico文件)、各種形狀的鼠標(biāo)(*.cur文件)、各種圖像(*.bmp/*.gif等等)、各種聲音(*.wav等)、各種菜單……,這些就是我們這里所說的資源了。
每一個(gè)要用到資源,我們都要給它分配一個(gè)編號(hào)或名稱,作為這個(gè)資源的標(biāo)識(shí)。之后我們的程序只是通過這個(gè)編號(hào)或名稱來訪問這些資源了。所以這些編號(hào)或名稱我們稱之為資源標(biāo)識(shí)。好了,現(xiàn)在你也只要有了一個(gè)大體的映象就可以了,具體的形式和應(yīng)用讓我慢慢再與你說了。
(各位可以提出你的疑問,白云小飛一定會(huì)盡力回復(fù)的。)
??!終于結(jié)束冗長(zhǎng)的概念解說了,看到這里,請(qǐng)先回顧一下我們前面講的東西。然后嘛——我們可以開工啦!。
白云小飛
1 在D:\創(chuàng)建一個(gè)空的工程(工程名為MyApp)
要編寫一個(gè)程序,我們就要首先用VC6應(yīng)用程序向?qū)?chuàng)建一個(gè)工程,下面我將給你創(chuàng)建一個(gè)空工程(也就是沒有任何源文件及代碼的工程)
1.1 操作:
=>文件->新建…->”工程”標(biāo)簽->位置:”D:\”(你可以設(shè)置你想要?jiǎng)?chuàng)建的位置)->工程名:MyApp(你可以自己指定其它名)->選擇”創(chuàng)建新的工作空間”->確定->一個(gè)空工程->完成
1.2 請(qǐng)查看指定位置下生成的文件:打開D:\MyApp
我們發(fā)現(xiàn),它在D:\下生成了一個(gè)MyApp文件夾。
并在MyApp文件夾下生成了幾個(gè)文件,如下:
MyApp.dsp
MyApp.dsw
MyApp.ncb
MyApp.opt
其中,MyApp.dsp是項(xiàng)目文件和MyApp.dsw是工作區(qū)文件,它們是不能刪除。
項(xiàng)目文件的作用:生成一個(gè)EXE或DLL程序的所有相關(guān)源文件、有頭文件的位置信息都記錄在MyApp.dsp項(xiàng)目文件中。
工作區(qū)文件的作用:如果一個(gè)復(fù)雜的軟件工程可能是由多個(gè)EXE和多個(gè)DLL程序組成,這樣每個(gè)項(xiàng)目文件的位置信息又記錄在MyApp.dsw工作區(qū)文件中。
當(dāng)然,我們的這個(gè)工程只有一個(gè)EXE程序,所以只有一個(gè)項(xiàng)目,這個(gè)項(xiàng)目文件MyApp.dsp也同樣要記錄在MyApp.dsw中了。
MyApp.ncb和MyApp.opt雖刪除后會(huì)自動(dòng)生成,但是還是請(qǐng)不要這樣做哦!以后我還會(huì)說它們的作用。
我們下次要編輯源程序時(shí),只要打開工作區(qū)文件MyApp.dsw就可。
2 在D:\MyApp下創(chuàng)建一個(gè)C++源文件,文件名為MyAppMain(當(dāng)然你也可以自己定義一個(gè)文件名),并同時(shí)加入到MyApp工程中
2.1 操作:
=>文件->新建…->”文件”標(biāo)簽->選”添加到工程”->選”MyApp”->文件名:MyAppMain->”位置”默認(rèn)->確定
2.2 查看指定生成的文件:
可以看到,在D:\MyApp文件夾下生成了MyAppMain.cpp源文件。
3 在MyAppMain.cpp文件中輸入一個(gè)主函數(shù)
3.1 代碼如下:
#include <windows.h>
#include<windowsx.h>
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
// 將會(huì)在這里輸入主函數(shù)的代碼
return 0;
}
3.2 包含必要的頭文件:
首先你只要包含下面兩個(gè)頭文件就可,因?yàn)樗鼈円呀?jīng)包含了絕大多數(shù)的MyApp應(yīng)用程序必要的頭文件。
#include <windows.h>
#include<windowsx.h>
3.3 主函數(shù)名:
函數(shù)頭定義的書寫格式很有趣:
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
寫成四行,其實(shí)沒什么,只不過寫在一行里太長(zhǎng)了,如下:
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow)
并且寫成上面的四行反而可讀性更強(qiáng),所以以后你會(huì)經(jīng)常看到這樣書寫的。
WinMain函數(shù)相當(dāng)于Dos下的main函數(shù),Windows應(yīng)用程序的主函數(shù)不用main而是用WinMain。一個(gè)程序必有并只能有一個(gè)WinMain函數(shù)。這個(gè)主函數(shù)可以寫在本工程中的任何一個(gè).cpp文件中。
3.4 參數(shù):
hinstance:類型是HINSTANCE,這種類型就是前面我們可是提到過的應(yīng)用程序句柄啦。hinstance的值就是你的當(dāng)前應(yīng)用程序進(jìn)程的句柄。
你的程序每次運(yùn)行,它的hinstance值是不會(huì)一樣的。不過我們并不關(guān)心hinstance的值是多少。我們只要知道hinstance里的值就是代表本應(yīng)用程序進(jìn)程的句柄值就可以了。
我們等一會(huì)兒就會(huì)用到它了,請(qǐng)看好哦!
hprevinstance:現(xiàn)在已經(jīng)不用了。我們可以完全不理它。
lpcmdline:這是一個(gè)命令行參數(shù)。與main(int argc,char **argv)中的命令行參數(shù)相似。由于與本題無關(guān),我們也不可完全不理它,對(duì)此不再進(jìn)一步討論下去。
至于它的數(shù)據(jù)類型LPSTR,我在前面已經(jīng)說明了,它其實(shí)就是char *類型。
ncmdshow:一個(gè)整型值。啟動(dòng)過程中被傳遞給應(yīng)用程序,帶有如何打開應(yīng)用程序主窗口的信息。這樣,程序的使用者有了一點(diǎn)點(diǎn)的控制應(yīng)用程序如何啟動(dòng)的能力了。作為一個(gè)程序員,如果想忽略它也可以,想使用它也行。哦,那我們這里也先忽略它了。
3.5 返回值:
是一個(gè)int值,當(dāng)我們的程序正常結(jié)束退出時(shí),一定要返回一個(gè)0值。所以我們的WinMain函數(shù)體內(nèi)最后有return 0;了
3.6 函數(shù)名前的WINAPI是什么?
最后還有一個(gè)要說明的——就是WINAPI。這是什么呢?
在VC6的Windef.h頭文件中是如下定義的:
#define WINAPI __stdcall
也就是WINAPI等于__stdcall了。
你知道嗎,凡是提供給Windows操作系統(tǒng)調(diào)用的函數(shù)都得是__stdcall調(diào)用的。WinMain主函數(shù)當(dāng)然是由Windows系統(tǒng)調(diào)用的,因此定義WinMain前要用__stdcall(即WINAPI)修飾。(你會(huì)明白函數(shù)調(diào)用方式的具體含義嗎?不知道也沒關(guān)系,現(xiàn)在只要記得WINAPI要放在WinMain前就行了。)
另外說明一點(diǎn),int 與WINAPI哪個(gè)在前哪個(gè)在后都是可以的。
好了,我們現(xiàn)在對(duì)主函數(shù)定義處的各個(gè)部分有了必要的了解。你可以休息一會(huì)兒,然后回顧一下我們前面操作的過程:
想想我們?cè)诒酒凶隽四男┦拢?br> 生成了哪些文件?
這些文件是什么作用的?
主函數(shù)定義的各個(gè)部分是什么作用?
如果你已經(jīng)對(duì)上面所做的感到比較清晰,那太好了,Come on!我們繼續(xù)吧!
白云小飛 1 請(qǐng)?jiān)賱?chuàng)建一個(gè)函數(shù)。 |
白云小飛
一 創(chuàng)建并顯示一個(gè)窗口的“遐想”。
首先,要顯示的窗口在哪里呢?
要想顯示你自己的窗口,顯然你得事先創(chuàng)建一個(gè)自己的窗口。當(dāng)你想要一個(gè)窗口時(shí),Window系統(tǒng)才會(huì)為你創(chuàng)建窗口。不要時(shí),Window再銷毀這個(gè)窗口。噢,這是多么相當(dāng)然的一種機(jī)制啊,你說是吧!也就是說我們得先創(chuàng)建一個(gè)窗口才能顯示。(否則哪里來的窗口給你顯示呢?)
其次,你想創(chuàng)建什么樣的窗口呢?
那么,創(chuàng)建什么樣的窗口呢?創(chuàng)建前,Window系統(tǒng)可不知道你要的是什么類型的窗口?。ū热鐦?biāo)題欄上顯示什么圖標(biāo),鼠標(biāo)形狀是什么,窗口背景顏色等等)。這些類型信息應(yīng)在你創(chuàng)建前事先告訴Window系統(tǒng)。可以采用這種方法:就是我們事先寫一份要?jiǎng)?chuàng)建窗口的類型申請(qǐng)表,提交(注冊(cè))給Window系統(tǒng)。然后在創(chuàng)建時(shí),可以讓W(xué)indows按這個(gè)申請(qǐng)表來創(chuàng)建你所要的窗口了。也就是說我們還應(yīng)該先提交一個(gè)申請(qǐng)表,申請(qǐng)成功后再根據(jù)這個(gè)表創(chuàng)建一個(gè)窗口。
依據(jù)上述的理由,我假想了以下幾個(gè)步驟要做:
第一. 你得先填寫一份你想創(chuàng)建的窗口類的“申請(qǐng)表”。
第二. 然后將這“申請(qǐng)表”通過一個(gè)API函數(shù)提交給Windows系統(tǒng)(即注冊(cè)到Windows系統(tǒng)中)。
第三. 如果提交(注冊(cè))成功,就說明Window系統(tǒng)通過了你的“申請(qǐng)表”,Windows系統(tǒng)中就有了一份你所申請(qǐng)的窗口類(注:這個(gè)注冊(cè)成功的已經(jīng)注冊(cè)在系統(tǒng)中的“申請(qǐng)表”我們稱之為窗口類)。這樣你就可以利用這個(gè)申請(qǐng)成功的窗口類,通過一個(gè)專門的API函數(shù)讓W(xué)indows系統(tǒng)創(chuàng)建一個(gè)或多個(gè)的同一窗口類的窗口。
第四. 創(chuàng)建成功后,我們有了窗口。但是,雖然窗口已存在在內(nèi)存中,并不一定就馬上顯示在屏幕上(這根據(jù)你的意愿了),所以之后的某時(shí)你可以用一個(gè)API函數(shù)來讓W(xué)indows系統(tǒng)顯示剛才創(chuàng)建的窗口。
Window系統(tǒng)就是這樣設(shè)計(jì)的噢?。ê呛牵@樣設(shè)計(jì)不算壞,我可以接受。)
以上就是創(chuàng)建一個(gè)窗口的大致過程。請(qǐng)記住,在Window系統(tǒng)下你的程序要顯示一個(gè)你想自定的窗口總是得經(jīng)歷如此步驟的。還要記住一點(diǎn),我們的代碼只是通過調(diào)用Window系統(tǒng)所提供的API函數(shù)來完成對(duì)窗口間接的管理。實(shí)際上窗口的管理操作都由Window系統(tǒng)直接完成的。
好,讓我們?cè)诒酒邢葋硗瓿傻谝?、二步驟吧!
二 第一步 填寫一份“申請(qǐng)表”
1 用什么來作為這種“申請(qǐng)表”呢?
我想,C語(yǔ)言中的struct結(jié)構(gòu)體類型的變量來充當(dāng)這個(gè)“申請(qǐng)表”是再合適不過的了。呵呵,真是這樣,VC6下早已為我們準(zhǔn)備好了這樣的“申請(qǐng)表”了。那就是WNDCLASSEX(我們稱之為窗口類結(jié)構(gòu)體)。
看看這個(gè)WNDCLASSEX結(jié)構(gòu)體的底細(xì)吧!
在windef.h中已經(jīng)有定義:(下面所列的與真實(shí)文件中會(huì)有點(diǎn)不同,但目前你只要理解我這份就可以了。)
typedef struct tagWNDCLASSEX {
UINT cbSize; //用來保存本結(jié)構(gòu)體的所占字節(jié)數(shù)
UINT style; //窗口類型風(fēng)格。
//比如,可設(shè)置“若移動(dòng)窗口寬度時(shí),則刷新整個(gè)窗口。
WNDPROC lpfnWndProc; //回調(diào)函數(shù)指針,用以指向前面那個(gè)回調(diào)函數(shù)。
int cbClsExtra; //略,我們可不必使用它,只要賦值為0就可
int cbWndExtra; //略,同上
HINSTANCE hInstance; //窗口所屬的應(yīng)用程序?qū)嵗浔?br> HICON hIcon; //大圖標(biāo),(這個(gè)圖標(biāo)會(huì)顯示在窗口的哪里呢?)
HICON hIconSm; //小圖標(biāo),(這個(gè)又會(huì)顯示在窗口的哪里呢?)
HCURSOR hCursor; //鼠標(biāo)句柄,用以指定鼠標(biāo)移入窗口時(shí)的樣式
HBRUSH hbrBackground; //用來刷背景顏色的畫刷句柄,
//窗口的顏色就會(huì)按這個(gè)顯示。
LPCSTR lpszMenuName; //用來指向菜單資源名稱字符串的指針,
//可讓你的窗口有一個(gè)菜單。
LPCSTR lpszClassName; //用來指向窗口類名字符串的指針
//作為這個(gè)窗口類的標(biāo)識(shí)
} WNDCLASSEX
哦!是一個(gè)有12個(gè)成員變量的struct結(jié)構(gòu)體的類型。(看來我們又要一個(gè)一個(gè)耐心地搞定它。)
2 創(chuàng)建一個(gè)“申請(qǐng)表”
現(xiàn)在就先讓我用這個(gè)WNDCLASSEX結(jié)構(gòu)體申明一個(gè)變量吧!(看粗體字部分)
HWND hWnd;
MSG msg;
WNDCLASSEX winclass; //變量名可由你自己定。
順便解釋一下:
這里我順便地同時(shí)申明了hWnd的窗口句柄變量,用以保存將要?jiǎng)?chuàng)建出來的窗口的句柄。
還申明了一個(gè)msg:它的類型是MSG,也是一個(gè)已預(yù)定義好的結(jié)構(gòu)體,用來保存窗口操作的各種消息信息。
好了好了,以后再說這兩個(gè)東東啦。
3 填寫“申請(qǐng)表”
接下來就到了填寫這個(gè)“申請(qǐng)表”的時(shí)候啦!也就是要對(duì)winclass這個(gè)結(jié)構(gòu)體變量的各成員賦值。
winclass.cbSize = sizeof (WNDCLASSEX);
這是將本結(jié)構(gòu)體的大?。ㄕ甲止?jié)數(shù))賦值給其成員變量cbSize,一定要這樣做哦!好處是:以后Windows系統(tǒng)只要訪問cbSize就可知道wndclass的大小了,就不必每次都要用sizeof(WNDCLASSEX)來獲取大小。(哦,不錯(cuò),這真是一個(gè)很值得學(xué)習(xí)的做法)
winclass.stype= CS_VREDRAW | CS_HREDRAW | CS_OWNDC | CS_DBLCLICKS;
我們這里將這四個(gè)值同時(shí)賦值給成員變量stype。
新問題:CS_VREDRAW、CS_HREDRAW、CS_OWNDC、CS_DBLCLICKS是什么??!
stype是一個(gè)UINT即unsigned int的類型(共32位二進(jìn)制位)。stype的可能值為如下的組合:
表二.3 窗口類的類型標(biāo)志
標(biāo)識(shí) 描述
CS_HREDRAW 若移動(dòng)或改變了窗口寬度,則刷新整個(gè)窗口
CS_VREDRAW 若移動(dòng)或改變了窗口高度,則刷新整個(gè)窗口
CS_OWNDC 為該類中的每個(gè)窗口分配一個(gè)單值的設(shè)備描述表
CS_DBLCLKS 當(dāng)用戶雙擊鼠標(biāo)時(shí)向窗口程序發(fā)送一個(gè)雙擊的信息,同時(shí),光標(biāo)位于屬 于該類的窗口中
CS_PARENTDC 略
CS_NOCLOSE 禁止系統(tǒng)菜單上的關(guān)閉命令
CS_SAVEBITS 略
關(guān)于更詳細(xì)的類型標(biāo)志描述,請(qǐng)自行參考相關(guān)書籍。
你可能看完后仍還不能完全明白這些窗口類的類型標(biāo)志的意思。沒有關(guān)系,它不妨礙我們對(duì)整個(gè)程序框架的理解(我們可是要善于把握輕重緩急噢?。?,現(xiàn)在你只要按我上面賦值就可以了。
還有一個(gè)問題:符號(hào)“|”是按位或的運(yùn)算符,即表示CS_VREDRAW、CS_HREDRAW、CS_OWNDC、CS_DBLCLICKS的值同時(shí)都賦值給stype
(只能解釋到這里了,如果還不太明白“|”的運(yùn)算,請(qǐng)自己去看關(guān)于C語(yǔ)言中“按位或”的相關(guān)知識(shí)了。)
winclass.lpfnWndProc=WinProc;
lpfnWndProc是一個(gè)函數(shù)指針(第一篇我已經(jīng)說過你要對(duì)函數(shù)指針有一定認(rèn)識(shí)。),它是WNDPROC函數(shù)類型,這個(gè)函數(shù)類型在winuser.h文件中已有定義。如下:
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
發(fā)現(xiàn)沒有,WNDPROC類型的格式與我在第三篇中寫的那個(gè)自定義窗口過程函數(shù)的格式是一樣的。現(xiàn)在我們將我們的這個(gè)函數(shù)WinPro的地址賦值給lpfnWndProc函數(shù)指針變量。
將前面的那個(gè)回調(diào)函數(shù)WinProc的首地址賦值給成員變量lpfnWndProc。這樣這個(gè)窗口類就與這個(gè)回調(diào)函數(shù)相關(guān)聯(lián)起來了。
這里,你可能有個(gè)疑問:為窗口類指定一個(gè)窗口過程函數(shù)有什么用處呢?這是Window系統(tǒng)的消息機(jī)制的關(guān)鍵。但我現(xiàn)在不想說太多,因?yàn)檎娴默F(xiàn)在無法說清楚的。只有等到我將整個(gè)程序框架建立起來后,我們?cè)賮砝斫馑伞#ò?!我知道這種說了卻又沒能完全說清楚的情況不應(yīng)該有,但真的是不得不為之?。。?br>wndclass.hInstance = hinstance;
第二篇中可是說過主函數(shù)參數(shù)中hinstance的值就是本應(yīng)用程序?qū)嵗浔?,我們現(xiàn)在將這個(gè)hinstance的句柄值賦值給wndclass.hInstance。這樣,由這個(gè)窗口類結(jié)構(gòu)創(chuàng)建的窗口就與本程序?qū)嵗嚓P(guān)聯(lián)了。
wndclass.cbClsExtra=0;
wndclass.cbWndExtra =0;
我們現(xiàn)在只是對(duì)cbClsExtra和cbWndExtra兩個(gè)成員簡(jiǎn)單賦值為0。你現(xiàn)在也不用管它們是做什么的??梢员WC絕大多數(shù)的程序只要這樣就行。
wndclass.hCursor =LoadCursor(NULL, IDC_ARROW);
hCursor是一個(gè)鼠標(biāo)光標(biāo)句柄變量,用來為這個(gè)窗口類指定一個(gè)鼠標(biāo)句柄值(也就是想讓這個(gè)窗口顯示一個(gè)什么樣的鼠標(biāo)形狀了)。LoadIcon(NULL, IDC_ARROW);這個(gè)函數(shù)是加載一個(gè)光標(biāo)給hCursor的。你現(xiàn)在只要造就輸入就可,其它的以后我再說了。
wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
wndclass.hIconSm = LoadIcon(NULL,IDI_APPLICATION);
hIcon和hIconSm都是圖標(biāo)句柄變量,前一個(gè)是用以指定大圖標(biāo),后一個(gè)是用以指定小圖標(biāo)。
hIconSm中設(shè)置的圖標(biāo)會(huì)顯示在你的窗口的標(biāo)題欄左邊。
hIcon中設(shè)置的圖標(biāo)是會(huì)顯示在哪兒啊?不好意思,我真的不知道。如果有人知道,請(qǐng)告之,本人非常感謝!
關(guān)于LoadIcon函數(shù),它是加載一個(gè)圖標(biāo)給hIcon及hIconSm的?,F(xiàn)在你只要造就輸入就可以了。
wndclass.lpszMenuName =NULL;
lpszMenuName是一個(gè)字符串指針,這是用來指向一個(gè)菜單資源名字符串。我們這個(gè)窗口暫時(shí)不要菜單,所以這里先指定NULL。
wndclass.hbrBackground =(HBRUSH)GetStockObject(WHITE_BRUSH);
hbrBackground是一個(gè)畫刷句柄變量(類型HBRUSH)(呵呵,又一個(gè)新句柄。)。窗口客戶區(qū)的背景顏色是用這個(gè)變量指定的畫刷來刷的??床幻靼?HBRUSH)GetStockObject(BLACK_BRUSH)這個(gè)函數(shù)沒有關(guān)系,這里它提供了一個(gè)白色畫刷給我們。你現(xiàn)在只要造就輸入就可以。
wndclass.lpszClassName ="WINCLASS1";
lpszClassName是一個(gè)字符串指針,它是為這個(gè)窗口類(申請(qǐng)表)指定個(gè)字符串,這個(gè)字符串作為這個(gè)窗口類的名稱。這里我們指定”WINCLASS1”字符串作為這個(gè)窗口類的名稱。(要注意一點(diǎn)的是,一個(gè)程序中如果申請(qǐng)(注冊(cè))了多個(gè)窗口類,那么每個(gè)窗口類的這個(gè)字段值是不能相同的。)
到此為止,我們終于(有點(diǎn)稀里糊涂地)結(jié)束了這個(gè)結(jié)構(gòu)體各成員變量的賦值了。以上成員變量賦值代碼匯總?cè)缦拢?br>
wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.cbClsExtra=0;
wndclass.cbWndExtra =0;
wndclass.lpfnWndProc = WinProc;
wndclass.style = CS_VREDRAW | CS_HREDRAW | CS_OWNDC | CS_DBLCLKS;
wndclass.hInstance = hinstance;
wndclass.hCursor =LoadCursor(NULL, IDC_ARROW);
wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
wndclass.hIconSm = LoadIcon(NULL,IDI_APPLICATION);
wndclass.lpszMenuName = NULL;
wndclass.hbrBackground =(HBRUSH) GetStockObject(BLACK_BRUSH);
wndclass.lpszClassName ="WINCLASS1";
好了,我們已經(jīng)填好了我們想要?jiǎng)?chuàng)建的窗口的“申請(qǐng)表”了,現(xiàn)在可以透一口氣了!
然后……再繼續(xù),因?yàn)槲覀兊娜蝿?wù)還沒用完成呢!
三 第二步 提交“申請(qǐng)表”給Window系統(tǒng)
到了提交“申請(qǐng)表”的時(shí)候了。我們現(xiàn)在要將wndclass中的各項(xiàng)數(shù)據(jù)信息提交(注冊(cè))到Windows系統(tǒng)中。有一個(gè)API函數(shù)是專門用來注冊(cè)窗口類信息的,原型如下:
ATOM WINAPI RegisterClassEx(CONST WNDCLASSEX *);
參數(shù)是WNDCLASSEX *類型的指針,只要將要注冊(cè)的WNDCLASSEX結(jié)構(gòu)體變量地址代入就行。
返回值是一個(gè)ATOM類型,其實(shí)它也是一個(gè)unsigned short類型的別名而已。如果注冊(cè)不成功則程序返回0值,否則表示注冊(cè)成功。(注:我們暫時(shí)不關(guān)心其它返回值的意義。)
所以我們注冊(cè)窗口類的代碼可以如下:
if (!RegisterClassEx(&winclass))
return 0; //不成功程序就直接結(jié)束了
四 最后總括代碼
本篇中所增加的全部代碼現(xiàn)總括如下(粗體字部分):
WINAPI int WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
HWND hWnd; //窗口句柄變量
MSG msg; //消息結(jié)構(gòu)體變量
WNDCLASSEX wndclass; //窗口類結(jié)構(gòu)體變量
//以下是對(duì)wndclass各成員賦值
wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.cbClsExtra=0;
wndclass.cbWndExtra =0;
wndclass.lpfnWndProc = WinProc;
wndclass.style = CS_VREDRAW | CS_HREDRAW | CS_OWNDC | CS_DBLCLKS;
wndclass.hInstance = hinstance;
wndclass.hCursor =LoadCursor(NULL, IDC_ARROW);
wndclass.hIcon =LoadIcon(NULL,IDI_APPLICATION);
wndclass.hIconSm = LoadIcon(NULL,IDI_APPLICATION) ;
wndclass.lpszMenuName = NULL;
wndclass.hbrBackground =(HBRUSH) GetStockObject(BLACK_BRUSH);
wndclass.lpszClassName =WND_CLS_NAME;
//下面是利用wndcalss結(jié)構(gòu)體的信息注冊(cè)一個(gè)窗口類
if (!RegisterClassEx(&wndclass))
return 0;
//……
return 0;
}
白云小飛
一 用CreateWindowEx函數(shù)來創(chuàng)建窗口
1 參數(shù)及返回值說明:
上篇中我們完成了向Windows系統(tǒng)進(jìn)行窗口的“申請(qǐng)”工作(即注冊(cè)一個(gè)窗口類)。本篇就是要用這個(gè)窗口類來創(chuàng)建一個(gè)窗口。
下面這個(gè)API函數(shù)就是專門用來創(chuàng)建一個(gè)窗口的:
HWND WINAPI CreateWindowEx(
DWORD dwExStyle,
LPCSTR lpClassName,
LPCSTR lpWindowName,
DWORD dwStyle,
int X, int Y,
int nWidth, int nHeight,
HWND hWndParent ,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam);
這是一個(gè)擁有一大串參數(shù)的函數(shù)。(唉!我又要一個(gè)個(gè)地介紹了。)
DWORD dwExStyle:這是用來指定擴(kuò)展樣式標(biāo)志,絕大多數(shù)情況,我們只要指定為NULL,所以我不想多說。
LPCSTR lpClassName:我們要用前篇中注冊(cè)的窗口類來完成創(chuàng)建窗口,所以lpCassName所指字符串值要與前篇中注冊(cè)時(shí)所用的窗口類名值相同(即wndclass.lpszClassName的值)。本例中就是"WINCLASS1"字符串值。
LPCSTR lpWindowName:此指針?biāo)傅淖址畷?huì)顯示在標(biāo)題欄上(即標(biāo)題欄文字)。
DWORD dwStyle:這是用來指定窗口外觀類型的。以下是它可能的值(部分):
表 dwStyle可以設(shè)置的值
WS_POPUP 彈出式窗口
WS_OVERLAPPED 帶有標(biāo)題欄和邊界的重疊式窗口。
WS_OVERLAPPEDWINDOW 具有WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU、WS_THICK、FRAME、WS_MINIMIZEBOX、WS_MAXIMIZEOBX樣式的重疊式窗口
WS_VISIBLE 創(chuàng)建之后就立即顯示窗口
…… (還有很多其它值呢!請(qǐng)自行參看其它參考書)
還記得上篇中注冊(cè)窗口類時(shí)窗口類結(jié)構(gòu)體成員中有一項(xiàng):
wndclass.style = CS_VREDRAW | CS_HREDRAW | CS_OWNDC | CS_DBLCLKS;
你可不要搞糊涂啦!wndclass.style所指的是針對(duì)窗口內(nèi)在特性的類型,而CreateWindowEx參數(shù)中的dwStyle窗口類型是窗口外觀的類型。
int X, int Y,:這是指定相對(duì)于桌面的窗口左上角位置(坐標(biāo))。
int nWidth, int nHeight,:指定窗口的寬度與高度。
(上面這四個(gè)參數(shù)應(yīng)該好理解吧!)
HWND hWndParent :父窗口句柄,不明白它的作用吧?現(xiàn)在你只要賦值為NULL就可。
HMENU hMenu:你還記得嗎?我們?cè)谇捌拇翱陬惤Y(jié)構(gòu)體變量賦值中有這么一句: wndclass.lpszMenuName =NULL; 它是可以為窗口指定一個(gè)菜單的。
CreateWindowEx函數(shù)的這個(gè)hMenu菜單句柄(這又是一個(gè)句柄噢!)也可以為窗口指定一個(gè)菜單,它將代替前面的設(shè)置。不過我們現(xiàn)在暫時(shí)不要菜單,所以也賦值為NULL。
HINSTANCE hInstance:要與wndclass.hInstance值相同,本應(yīng)用程序的句柄代入這里。
LPVOID lpParam:高級(jí)特征,現(xiàn)在我們只要設(shè)置為NULL就行。
(噢,好不容易介紹完這些參數(shù)了。)
返回值:如果窗口成功創(chuàng)建出來,則返回這個(gè)窗口的句柄,如果創(chuàng)建不成功,則返回0值。
2 具體實(shí)現(xiàn)代碼:
下面我就給出具體的創(chuàng)建窗口代碼:
hWnd=CreateWindowEx(NULL,”WINCLASS1”,
"這是我的第一個(gè)窗口",
WS_OVERLAPPEDWINDOW ,
0, 0,
400,400,
NULL,
NULL,
hinstance,
NULL );
if (!hWnd) //這里應(yīng)該判斷是否創(chuàng)建成功
return 0;
3 調(diào)試及結(jié)果:
程序?qū)懙竭@里似乎窗口就可以創(chuàng)建出來了。那就讓我們調(diào)試調(diào)試?OK,Let’s go!請(qǐng)?jiān)?br> if (!hWnd)
return 0; //設(shè)斷點(diǎn)
中的return 0處設(shè)置一個(gè)斷點(diǎn)??纯茨愕某绦驎?huì)不會(huì)執(zhí)行到這個(gè)return 0;當(dāng)創(chuàng)建不成功,則hWnd==0,程序會(huì)執(zhí)行到這個(gè)return 0。反之,程序成功地創(chuàng)建了這個(gè)窗口。
(哦!我并不知道你是否會(huì)用VC6來設(shè)置斷點(diǎn)并進(jìn)行調(diào)試。
只要光標(biāo)放在return 0 處按F9就可以設(shè)置此處的斷點(diǎn)。如果你再按F9一次,則會(huì)取消這個(gè)斷點(diǎn)。
設(shè)置完斷點(diǎn)后,按F5編譯運(yùn)行這個(gè)程序試試。)
矣?不太對(duì)勁啊,調(diào)試中我們發(fā)現(xiàn)hWnd值為0,即說明窗口并沒有創(chuàng)建成功。
(在這里請(qǐng)?jiān)侔碏5,可以從斷點(diǎn)處繼續(xù)運(yùn)行,程序結(jié)束了。)
原因是?……
二 CreateWindowEx的一個(gè)條件:調(diào)用缺省窗口過程DefWindowProc函數(shù)
1 在WinProc中增加調(diào)用DefWindowProc函數(shù)
原因是:我們還要在這個(gè)窗口的自定義窗口過程WinProc中增加如下代碼(黑體字部分):
LRESULT CALLBACK WinProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
//……
return DefWindowProc(hwnd, msg, wparam, lparam);
}
真讓我疑惑??!
CreateWindowEx函數(shù)創(chuàng)建一個(gè)窗口與這個(gè)DefWindowProc函數(shù)什么關(guān)系。
又為什么要把這個(gè)函數(shù)寫在WinProc回調(diào)函數(shù)里呢?
首先,看DefWindowProc在Winuser.h中的原型定義:
LRESULT CALLBACK DefWindowProc(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam);
你有沒有看出,它的函數(shù)格式與我們自定義WinProc回調(diào)函數(shù)的格式一完全一樣的?只不過DefWindowProc是Window系統(tǒng)提供給我們的一個(gè)API函數(shù)。
那么在整個(gè)調(diào)用CreateWindowEx函數(shù)中到底發(fā)生了什么事???讓我來告訴你吧!
2 原因何在?
從我們的自定義窗口過程WinProc函數(shù)說起:
當(dāng)應(yīng)用程序運(yùn)行時(shí),用戶對(duì)該程序窗口的操作,都會(huì)自動(dòng)產(chǎn)生一系列的消息。比如創(chuàng)建一個(gè)窗口會(huì)產(chǎn)生WM_CREATE消息;移動(dòng)窗口會(huì)產(chǎn)生WM_MOVE消息;按下鍵盤時(shí)會(huì)產(chǎn)生WM_KEYDOWN消息;關(guān)閉窗口時(shí)會(huì)產(chǎn)生WM_CLOSE消息等等。也就是說,在執(zhí)行CreateWindowEx函數(shù)期間會(huì)產(chǎn)生若干個(gè)消息(先別管都是些什么消息)。
Window系統(tǒng)會(huì)將這些由一系列的消息添加到該進(jìn)程的消息隊(duì)例中(噢,你可要有一點(diǎn)想象力喲?。?。在CreateWindowEx函數(shù)中同時(shí)會(huì)調(diào)用我們寫的WinProc函數(shù)若干次,并把消息的各項(xiàng)信息通過WinPro函數(shù)參數(shù)傳遞進(jìn)來。(我再說一遍:你得有點(diǎn)想象力?。?br> 說白了,就是在執(zhí)行CreateWindowEx創(chuàng)建窗口過程中會(huì)引發(fā)對(duì)WinProc函數(shù)的多次調(diào)用。
創(chuàng)建窗口過程中要用到缺省窗口過程函數(shù)DefWindowProc:
前面說過,在執(zhí)行CreateWindowEx創(chuàng)建窗口過程中會(huì)引發(fā)對(duì)WinProc函數(shù)的多次調(diào)用。嘿嘿,這可不是可有可無的調(diào)用??!在這里,我們要讓缺省窗口過程DefWindowProc來完成一些默認(rèn)的消息處理操作。你不必知道它做了什么事,只要把這一切消息都“扔”給它就行啦!只有讓 DefWindowProc函數(shù)完成必要的消息處理,CreateWindowEx函數(shù)才能全程地完成窗口的創(chuàng)建(否則,嘿嘿!窗口的創(chuàng)建必將失敗。)。所以我們添加了調(diào)用DefWindowProc的代碼。(DefWindowProc的返回值返回的是對(duì)消息處理的結(jié)果,我們?cè)賹⑺鳛閃inProc的返回值。)
3 本篇增加的全部代碼
最后,本篇中所增加的全部代碼總括如下(黑體部分):
LRESULT CALLBACK WinProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
return DefWindowProc(hwnd, msg, wparam, lparam);
}
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
HWND hWnd;
MSG msg;
WNDCLASSEX wndclass;
//…… 這里省略了前篇所述的注冊(cè)窗口類的過程
hWnd=CreateWindowEx(NULL,WND_CLS_NAME,
"這是我的第一個(gè)窗口",
WS_OVERLAPPEDWINDOW|WS_VISIBLE ,
CW_USEDEFAULT, 0,
400,400,
NULL,
NULL,
hinstance,
NULL );
if (!hWnd)
return 0;
//……
return 0;
}
4 具體分析
現(xiàn)在現(xiàn)具體地分析一下調(diào)用CreateWindowEx函數(shù)的過程:
1. 首先從調(diào)用CreateWindowEx開始,程序進(jìn)入了CreateWindowEx函數(shù)體內(nèi)的代碼(就是Window系統(tǒng)的代碼)。
2. 在此期間,CreateWindowEx會(huì)產(chǎn)生若干個(gè)消息(我們不用管是什么消息)。
3. 每當(dāng)一個(gè)消息產(chǎn)生后,CreateWindowEx會(huì)自動(dòng)調(diào)用我們自己寫的WinProc函數(shù),將消息信息傳遞進(jìn)來。
4. 這些消息我們自己不作處理而是直接在WinProc調(diào)用缺省窗口過程DefWindowProc來作缺省處理(你不用去管DefWindowProc做了什么),并返回處理結(jié)果。
5. 每次WinProc結(jié)束后回到CreateWindowEx代碼處,CreateWindowEx會(huì)根據(jù)返回值果進(jìn)行判斷,完成最終的窗口創(chuàng)建。
怪不得嘛!沒有調(diào)用DefWindowProc這個(gè)函數(shù),CreateWindowEx無法完成全部的窗口創(chuàng)建過程,所以最終創(chuàng)建窗口失敗了。
5 再調(diào)試
很好!現(xiàn)在再按前面講的調(diào)試步驟試試看。
if (!hWnd)
return 0; //這里設(shè)斷點(diǎn)
程序不再執(zhí)行這個(gè)return 0;了。太好啦!這說明窗口創(chuàng)建成功了。當(dāng)然并沒把窗口顯示出來,所以你只看到程序停都不停一下就馬上就結(jié)束了。
關(guān)于窗口的顯示我就留到下一篇了。
白云小飛
哈!到了顯示窗口的時(shí)候啦!
看,下面這個(gè)函數(shù)就是用來顯示窗口的:
BOOL ShowWindow( HWND hWnd, int nCmdShow);
一 ShowWindow函數(shù)的參數(shù)及返回值
hWnd就是你要顯示的窗口的句柄:
nCmdShow是窗口的顯示方式,其可能的值如下:
SW_HIDE 隱藏應(yīng)用程序窗口
SW_SHOWNORMAL 激活并顯示窗口,如果窗口被最大化或最不化,系統(tǒng)恢復(fù)窗口到原始大小和位置(與SW_RESTORE)
SW_RESTORE 同SW_SHOWNORMAL
SW_NORMAL
SW_SHOWMINIMIZED 激活并最小化窗口
SW_SHOWMAXIMIZED 激活并最大化窗口
SW_SHOW 激活窗口,并按其當(dāng)前大小和位置顯示
SW_MAXINIZE 最大化應(yīng)用程序窗口
SW_MINIMIZE 最不化應(yīng)用程序窗口
SW_SHOWNOACTIVATE 按最近大小和位置顯示窗口,但不改變激活特性
SW_SHOWMINNOACTIVE 最小化窗口,但不改變其激活特性
SW_SHOWNA 按當(dāng)前大小各位置顯示窗口,但不改變其激活特性
返回值:成功則返回TRUE,不成功則返回FALSE。
二 先來兩個(gè)例子
例一:現(xiàn)假設(shè)已經(jīng)創(chuàng)建了一個(gè)窗口,并且該窗口句柄已保存在hWnd變量中。我希望將窗口最大化并使該窗口為當(dāng)前窗口(即激活該窗口)。請(qǐng)寫出ShowWindow函數(shù)的具體實(shí)現(xiàn)代碼。(注:可不必處理它的返回值)
解:ShowWindow(hWnd, SW_SHOWMAXIMIZED);
例二:我希望隱藏一個(gè)原來是顯示著的窗口。該窗口的句柄在hWnd變量中。應(yīng)如何寫ShowWindow函數(shù)呢?
解:ShowWindow(hWnd, SW_HIDE);
三 本系列程序中的實(shí)現(xiàn)
(快一點(diǎn)嘍,我想馬上動(dòng)手啦?。?br> 好,我們現(xiàn)在繼續(xù)完善我們的這個(gè)Win32SDK程序吧!
具體代碼如下(注意粗體字部分):
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
HWND hWnd;
MSG msg;
WNDCLASSEX wndclass;
//…… 這里省略了前面所述的注冊(cè)窗口類的過程
//
hWnd=CreateWindowEx(NULL,WND_CLS_NAME,
"這是我的第一個(gè)窗口",
WS_OVERLAPPEDWINDOW|WS_VISIBLE ,
CW_USEDEFAULT, 0,
400,400,
NULL,
NULL,
hinstance,
NULL );
if (!hWnd)
return 0;
ShowWindow(hWnd, ncmdshow); //本篇只添加這一句
return 0; //這里設(shè)置一個(gè)斷點(diǎn),調(diào)試看看
}
一點(diǎn)說明:這里的ncmdshow就是WinMain主函數(shù)中的傳入?yún)?shù)ncmdshow。當(dāng)我們通過雙擊*.exe文件來執(zhí)行程序時(shí),ncmdshow里的值就會(huì)是SW_SHOWNORMAL。
四 調(diào)試看看
太棒啦!我終于可以親眼所見我的窗口了!
好,讓我們來調(diào)試一下吧!看看程序運(yùn)行后會(huì)發(fā)生什么。(如果不這樣調(diào)試而只是運(yùn)行它,那么還來不及等你看清窗口,程序就會(huì)馬上結(jié)束的。這不用我說明原因吧?。?br> 請(qǐng)?jiān)谧詈笠粋€(gè)return 0處設(shè)置一個(gè)斷點(diǎn)(光標(biāo)放在return 0處,按F9)。然后F5運(yùn)行程序。
程序暫停在最后一個(gè)return 0處。
然后最小化桌面上所有其它無關(guān)窗口(包括VC6窗口)(這樣才能看到這個(gè)程序的窗口噢?。?。
認(rèn)真研究,我發(fā)現(xiàn)目前的代碼有以下幾個(gè)問題:
1. 窗口雖然顯示,但窗口不能自動(dòng)被激活(即成為當(dāng)前窗口)。只有最小化桌面上其它應(yīng)用程序的窗口后,才能看到我們的這個(gè)窗口。(注意:在ShowWindow(hWnd, ncmdshow);函數(shù)中ncmdshow值我說過是SW_SHOWNORMAL值,應(yīng)該會(huì)將窗口激活才對(duì)??!這可是個(gè)大問題。)
2. 窗口無法進(jìn)行調(diào)整大小,移動(dòng)位置等的操作。
不過我并不想在這里解決這個(gè)問題(為什么?),那是因?yàn)檫@是一個(gè)大問題,它將引出Window程序的一個(gè)重要機(jī)制——消息處理機(jī)制。嘿嘿,到了關(guān)鍵一擊的時(shí)候了!我請(qǐng)你務(wù)必帶著這兩個(gè)問題看下篇吧!
白云小飛
一 重提上篇的問題:
還記得在上一篇的最后,經(jīng)過調(diào)試后我們發(fā)現(xiàn)的問題嗎?
1. 窗口不能自動(dòng)被激活(即成為當(dāng)前窗口)。只有最小化其它應(yīng)用程序的窗口后,才能看到我們的這個(gè)窗口。(注意:在ShowWindow(hWnd, ncmdshow);函數(shù)中ncmdshow值我說過是SW_SHOWNORMAL值,應(yīng)該會(huì)將窗口激活才對(duì)?。。?br> 2. 無法進(jìn)行調(diào)整窗口大小,移動(dòng)窗口位置等等的所有對(duì)窗口的操作。
你認(rèn)為是什么原因呢?
二 且聽我說:
1. ShowWindow(hWnd, SW_SHOWNORMAL)函數(shù)作用雖然是顯示窗口并激活窗口。它確實(shí)完成了窗口的顯示,但是,它并沒有去激活窗口,只是發(fā)了一系列與激活窗口有關(guān)的消息,這才是ShowWindow(…)函數(shù)所做的事。(那又由誰(shuí)來激活呢?)
2. 真正激活窗口的任務(wù)是由DefWindowProc( )完成的。哦,也就是說我們得讓DefWindowProc( )收到ShowWindow( )產(chǎn)生的消息并根據(jù)消息來執(zhí)行相應(yīng)任務(wù)。
3. 但ShowWindow( )產(chǎn)生的消息只是加入到該線程的消息隊(duì)列中,并沒有自動(dòng)地去調(diào)用WinProc( )的回調(diào)函數(shù)對(duì)消息處理。因此,DefWindowProc( )也就沒被調(diào)用,自然地就沒法激活窗口了。(請(qǐng)邊看代碼邊分析我的這段話吧!呵呵,你得有點(diǎn)想像力了!)
4. 哦,這不由地讓我想起前面的創(chuàng)建窗口函數(shù)CreateWindowEx( ) 。這兩個(gè)函數(shù)在執(zhí)行中都會(huì)產(chǎn)生若干的消息。但CreateWindowEx會(huì)自動(dòng)地調(diào)用了WinProc( ) 函數(shù),而ShowWindow( )沒有這樣。
5. 同理,我們?cè)诖翱谥械母鞣N操作(調(diào)整大小、移動(dòng)窗口等等),雖然會(huì)產(chǎn)生各種消息,但操作系統(tǒng)也只是把它們排入我們線程的消息隊(duì)列中,并不自動(dòng)去調(diào)用 Proc,所以同樣的理由,DefWindowProc也沒有去處理我們的消息了,窗口也就沒法完成如調(diào)整大小,移動(dòng)等等的操作了(你要知道:這些操作的實(shí)現(xiàn)都是由DefWindowProc( )完成的)。(還有,由于消息因?yàn)闆]有得到處理,就會(huì)一直留在消息隊(duì)列中,這樣,你的線程消息隊(duì)列中的消息將會(huì)越積越多噢?。?br> 看來,上述的兩個(gè)問題都源于同一個(gè)原因:那就是對(duì)窗口操作產(chǎn)生的消息只是排入消息隊(duì)列中,操作系統(tǒng)并沒有自動(dòng)地調(diào)用WinProc回調(diào)函數(shù)來處理我們的窗口消息。
哦!那我們只有自力更生了。
我猜你一定迫切想知道如何解決吧!
三 介紹三個(gè)函數(shù)給你認(rèn)識(shí):
我們到了一個(gè)Window窗口程序框架最關(guān)鍵的部分了——消息處理機(jī)制。
消息隊(duì)列中的本窗口大量消息并不會(huì)被自動(dòng)取出,也沒有自動(dòng)地調(diào)用WinProc函數(shù)對(duì)消息加以處理,但是,Window系統(tǒng)提供了三個(gè)API函數(shù)給我們,讓我們自己去完成這件事??窗?!
GetMessage( …);
TranslateMessage(…);
DispatchMessage(…);
下面就讓我分別對(duì)這三個(gè)函數(shù)解釋解釋。
1 GetMessage( …)
原型如下:
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd ,
UINT wMsgFilterMin,
UINT wMsgFilterMax);
功能:這個(gè)API函數(shù)用來從消息隊(duì)列中“摘取”一個(gè)消息信息放到lpMsg所指的變量里。(注:如果所取窗口的消息隊(duì)列中沒有消息,則程序會(huì)暫停在GetMessage(…) 函數(shù)里,不會(huì)返回。)
參數(shù)及返回值:
LPMSG lpMsg:是傳出參數(shù)。消息結(jié)構(gòu)MSG的指針。如果該函數(shù)執(zhí)行成功,則從消息隊(duì)列中“摘”取的一個(gè)消息信息會(huì)放入lpMsg所指的MSG結(jié)構(gòu)變量中。
在Winuser.h中有定義如下:
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
其中的成員變量message里才是我所說的WM_SIZE、WM_COMMAND、WM_QUIT等等消息標(biāo)識(shí)。
hwnd中是這個(gè)消息所在的窗口句柄。
好了,其它成員變量我就暫時(shí)省略不說。
HWND hWnd:傳入?yún)?shù)。你要獲取你程序中哪個(gè)窗口的消息,那就把相應(yīng)的窗口句柄代入其中。
UINT wMsgFilterMin,UINT wMsgFilterMax:這兩個(gè)參數(shù)我就不介紹。你只要用0值代入就可了。
返回值:如果取的是WM_QUIT消息,則返回值為0,如果取的是其它消息,返回值為非0值。WM_QUIT消息是退出程序的消息。當(dāng)我們想讓程序退出時(shí),我們就可以發(fā)一個(gè)WM_QUIT消息,讓GetMessage返回0值。
2 TranslateMessage(…)
原型:BOOL TranslateMessage( CONST MSG *lpMsg);
功能:對(duì)GetMessage取得的MSG消息結(jié)構(gòu)中的信息進(jìn)行必要的預(yù)處理(你可不用管它做了什么。)。
參數(shù):
CONST MSG *lpMsg:它是用來對(duì)你取的消息結(jié)構(gòu)MSG變量進(jìn)行必要的預(yù)處理。GetMessage函數(shù)取得的消息,要經(jīng)過TranslateMessage處理一下,然后才可以傳給DispatchMessage函數(shù),因此,TranslateMessage必放在GetMessage與DispatchMessage之間。
返回值:它雖然有一個(gè)返回值,我們總是忽略它,所以我也就不說了。
3 DispatchMessage(…)
原型: LONG DispatchMessage( CONST MSG *lpMsg);
功能:用來完成調(diào)用WinPro回調(diào)函數(shù)并把由GetMessage取得的消息結(jié)構(gòu)MSG變量中的信息傳遞給WinPro回調(diào)函數(shù):(原型如下)
參數(shù)及返回值:
MSG *lpMsg :傳入?yún)?shù),MSG消息結(jié)構(gòu)體類型指針,指向你已經(jīng)取出的消息結(jié)構(gòu)體變量。
返回值:同上,我們總是忽略它。
四 Come on,行動(dòng)起來吧!
看我添加的代碼(粗體部分):
LRESULT CALLBACK WinProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
//這里可以添加你的消息處理代碼
return DefWindowProc(hwnd, msg, wparam, lparam);
}
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
HWND hWnd;
MSG msg;
WNDCLASSEX wndclass;
//…… 這里省略了前面所述的注冊(cè)窗口類的過程
//
hWnd=CreateWindowEx(NULL,WND_CLS_NAME,
"這是我的第一個(gè)窗口",
WS_OVERLAPPEDWINDOW|WS_VISIBLE ,
CW_USEDEFAULT, 0,
400,400,
NULL,
NULL,
hinstance,
NULL );
if (!hWnd)
return 0;
ShowWindow(hWnd, ncmdshow);
while(GetMessage(&msg, NULL, 0, 0)) //獲取一個(gè)消息,成功后會(huì)放在msg中。
{
TranslateMessage(&msg); //消息進(jìn)行必要的處理轉(zhuǎn)換。
DispatchMessage(&msg); //調(diào)用WinProc,將msg的各項(xiàng)信息傳遞給WinProc
}
return 0;
}
看清楚了:這是一個(gè)消息循環(huán)。只有當(dāng)GetMessage(…)收到一個(gè)WM_QUIT時(shí)返回0值,程序才會(huì)退出。
五 程序流程分析(粗體部分)
1. 當(dāng)用戶進(jìn)行如調(diào)整窗口大小,移動(dòng)窗口位置等等的操作時(shí)。會(huì)產(chǎn)生不同的消息。(當(dāng)然包括了ShowWindow( )函數(shù)產(chǎn)生的若干個(gè)消息。
2. Window系統(tǒng)會(huì)將這些消息排入本線程的消息隊(duì)列中。(在操作系統(tǒng)中完成)
3. 只要該窗口消息隊(duì)列中有消息,我們的GetMessage(&msg,NULL,0,0)就會(huì)從消息隊(duì)列中摘取一個(gè)消息的信息,填入msg結(jié)構(gòu)體變量中,如果不是WM_QUIT則返回非零值,就執(zhí)行循環(huán)體。(注意:如果消息隊(duì)列中沒有任何消息可取時(shí),則程序會(huì)停在GetMessage函數(shù)里,直到消息隊(duì)列中有了消息,GetMessage函數(shù)才會(huì)取一個(gè)消息信息并返回。)
4. 用TranslateMessage(&msg)對(duì)msg中的數(shù)據(jù)進(jìn)行預(yù)處理(你先不必知道它具體做了什么,但不要忘記這個(gè)函數(shù)。)。
5. 然后是調(diào)用DespatchMessage(&msg),這個(gè)函數(shù)里會(huì)調(diào)用WinProc,并將msg中的數(shù)據(jù)通過WinProc的參數(shù)傳遞給WinProc;
6. 程序轉(zhuǎn)入執(zhí)行WinProc回調(diào)函數(shù)體內(nèi)的代碼。
7. 看代碼處,WinProc此時(shí)只有一句 return DefWindowProc(hwnd, msg, wparam, lparam);這里,我們只是將WinProc傳入的參數(shù)原樣地傳給了API函數(shù)DefWindowProc。所有的消息都讓DefWindowProc進(jìn)行缺省默認(rèn)的處理。(你不用理會(huì)DefWindowProc都做了些什么。)
8. DefWindowProc完成一個(gè)消息處理后,返回消息處理的結(jié)果。
9. 我們的WinProc也原樣地將DefWindowProc返回值返回。
10. WinProc執(zhí)行完成后,程序又返回到DispatchMessage(&msg)函數(shù)體內(nèi)。(因?yàn)槭窃贒ispathMessage( )中調(diào)用WinProc的。)
11. 退出DispatchMessage(&msg);函數(shù)后程序又執(zhí)行下一個(gè)循環(huán)。即
while(GetMessage(&msg, NULL, 0, 0))
又取下一個(gè)消息,進(jìn)行下一個(gè)消息的處理。
12. 直到GetMessage “摘取”到了退出程序的消息WM_QUIT,返回零值,退出循環(huán),結(jié)束程序。
(哦,整個(gè)流程是通過我們的程序與Window系統(tǒng)相互協(xié)作來完成的。你可要多加理解羅?。?br>
六 調(diào)試這個(gè)程序
不設(shè)任何斷點(diǎn),按F5運(yùn)行程序,
看來一切正常??梢砸苿?dòng)窗口;可以調(diào)整窗口大??;可以最大化最小化;總之可以進(jìn)行窗口的基本的操作了。(這些動(dòng)作都是由DefWindowProc來完成的噢?。?br> 哈!我們可是漸入佳境啦!這就是Window系統(tǒng)消息處理機(jī)制帶給我們的成果。
不過……只是……有一個(gè)問題。
你有沒有注意到,點(diǎn)擊窗口右上角的關(guān)閉按鈕時(shí),窗口是關(guān)閉了,但程序并沒有退出(看來點(diǎn)擊關(guān)閉按鈕時(shí)并沒有產(chǎn)生WM_QUIT的消息。)。
你只能點(diǎn)擊VC菜單的:Debug->Stop Debugging來退出程序了。
(欲知此為如何,請(qǐng)聽下回分解!)
白云小飛
還記得上篇中最后說到一個(gè)問題嗎?當(dāng)我們關(guān)閉程序窗口時(shí),窗口確實(shí)是關(guān)閉了,可是程序并沒有退出啊。為什么呢???
一. 理解程序的退出條件:
首先,我們要先明白程序退出的條件,看上篇中的這段代碼:
while(GetMessage(&msg, NULL, 0, 0)) //獲取一個(gè)消息,成功后會(huì)放在msg中。
{
TranslateMessage(&msg); //消息進(jìn)行必要的預(yù)處理轉(zhuǎn)換。
DispatchMessage(&msg); //調(diào)用WinProc回調(diào)函數(shù),將msg傳遞給WinProc函數(shù)
}
如果程序一直在這個(gè)消息循環(huán)中,程序就沒能退出。只有當(dāng)GetMessage收到一個(gè)WM_QUIT的消息,則返回值才會(huì)為零,退出循環(huán),程序得以結(jié)束。(這個(gè)道理應(yīng)該好理解吧?)
二. 點(diǎn)關(guān)閉按鈕時(shí),發(fā)生了什么
當(dāng)我們點(diǎn)窗口右上角的關(guān)閉按鈕時(shí),到底發(fā)生了什么事呢?(請(qǐng)邊看源代碼,邊體會(huì)下面的分析噢?。?br> 第一. 它并沒有(或最終沒有導(dǎo)致)發(fā)出WM_QUIT的消息。因此GetMessage函數(shù)不會(huì)收到WM_QUIT消息,就沒法跳出循環(huán)了。(那么又產(chǎn)生了什么消息呢?)
第二. 點(diǎn)關(guān)閉按鈕時(shí),產(chǎn)生WM_CLOSE的消息。GetMessage會(huì)收到WM_CLOSE消息的MSG結(jié)構(gòu)信息。
第三. 按前篇所述的消息處理流程可知:DespatchMessage會(huì)調(diào)用WinProc回調(diào)函數(shù),并把WM_CLOSE消息的相關(guān)信息傳遞給WinProc函數(shù)參數(shù)中。
第四. 現(xiàn)在我們的WinProc里只有一句:
LRESULT CALLBACK WinProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
//這里可以添加你的消息處理代碼
return DefWindowProc(hwnd, msg, wparam, lparam);
}
它將WM_CLOSE繼續(xù)傳遞給缺省窗口過程函數(shù)DefWindowProc。
第五. 在DefWindowProc函數(shù)里,判斷是WM_CLOSE消息后,就會(huì)對(duì)參數(shù)hwnd所代表的窗口進(jìn)行銷毀。(看吧,銷毀窗口的事也是由DefWindowProc來完成了。)
第六. 成功銷毀窗口后,DefWindowProc里接著還會(huì)發(fā)一個(gè)WM_DESTROY的消息到消息隊(duì)列中(表示說窗口已經(jīng)被銷毀了)。然后DefWindowProc函數(shù)才結(jié)束。
第七. 回到我們的消息循環(huán)的GetMessage函數(shù)。這個(gè)函數(shù)又會(huì)獲得WM_DESTROY消息的信息,開始了下一個(gè)消息處理過程。
第八. 這個(gè)WM_DESTROY可在WinProc函數(shù)中由我們處理。但在WinProc函數(shù)體的代碼中我們沒有自己去處理它。仍然是讓DefWindowProc去處理。
第九. 然而,DefWindowProc只是簡(jiǎn)單地把它給“扔掉”了。
第十. 整個(gè)點(diǎn)窗口右上角的關(guān)閉按鈕作所的所有動(dòng)作就這樣完成了。
你看,上述中,上述程序始終沒有產(chǎn)生WM_QUIT的消息,所以窗口確實(shí)是銷毀了,但程序并沒有退出這個(gè)消息處理循環(huán)。
哦,怪不得我們的程序沒法結(jié)束了。(那該怎么辦呢?)
三. 如何使程序結(jié)束。
退出程序的三點(diǎn)說明:
1. 我們希望是通過單擊這個(gè)窗口右上角的關(guān)閉按鈕時(shí)來退出程序。
2. 應(yīng)該在窗口成功銷毀后,才讓程序退出。
3. 只要讓程序產(chǎn)生一個(gè)WM_QUIT消息,就可以退出循環(huán)而結(jié)束程序。
終上所述,程序應(yīng)在收到WM_DESTROY消息后才能退出程序。因?yàn)椋祝蚠DESTROY消息表示窗口已經(jīng)銷毀。
那么我們又如何才能產(chǎn)生一個(gè)WM_QUIT的消息呢?用下面這個(gè)--API函數(shù):
PostQuitMessage(0);
參數(shù)代入0值就可。它將產(chǎn)生一個(gè)WM_QUIT消息。WM_QUIT消息最終會(huì)被GetMessage函數(shù)“摘取”到并返回0值。從而退出循環(huán),結(jié)束程序??次覍?shí)現(xiàn)代碼:
LRESULT CALLBACK WinProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
switch (msg) //msg中保存的就是正要處理的消息
{
case WM_DESTROY: //這是我們自行處理的第一個(gè)消息
{
PostQuitMessage(0); //發(fā)出一個(gè)WM_QUIT消息
return 0; //然后直接返回。
}break;
default:break;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
HWND hWnd;
MSG msg;
WNDCLASSEX wndclass;
//…… 這里省略了前面所述的注冊(cè)窗口類的過程
//
hWnd=CreateWindowEx(NULL,WND_CLS_NAME,
"這是我的第一個(gè)窗口",
WS_OVERLAPPEDWINDOW|WS_VISIBLE ,
CW_USEDEFAULT, 0,
400,400,
NULL,
NULL,
hinstance,
NULL );
if (!hWnd)
return 0;
ShowWindow(hWnd, ncmdshow);
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
WinProc函數(shù)參數(shù)中的UINT msg就是程序傳遞進(jìn)來的消息標(biāo)識(shí)。我們只要判斷msg是否為WM_DESTROY消息,如果是就發(fā)一個(gè)WM_QUIT消息。
補(bǔ)充說明一點(diǎn):
WinProc函數(shù)參數(shù)中有一個(gè)msg變量,而WinMain函數(shù)中也定義了一個(gè)msg。不要把它們給混了??!它們可是不同的變量啊!WinMain中定義的msg類型是MSG,在Winuser.h中已定義如下:
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
你看,它是一個(gè)結(jié)構(gòu)體。而WinProc參數(shù)中的msg是一個(gè)UINT類型,它其實(shí)只是WinProc函數(shù)里的msg結(jié)構(gòu)成員message的值。
你看看DispatchMessage(&msg);是如何傳遞這個(gè)msg給WinProc的:
它在調(diào)用WinProc時(shí),會(huì)——
將msg.hwnd值傳遞給WinProc參數(shù)中的HWND hwnd;
將msg.message值傳遞給WinProc參數(shù)中的UINT msg;
將msg.wParam傳遞給WinProc參數(shù)中的WPARAM wParam;
將msg.lParam值傳遞給WinProc參數(shù)中的LPARAM lParam。
整個(gè)程序?qū)崿F(xiàn)退出的流程還是讓你自行去分析啦!應(yīng)該不會(huì)太難吧???
至此,終于完成了這個(gè)SDK程序的基本框架了?。ü嬷档梦覀?nèi)ヅe杯慶祝啦!不過,我的任務(wù)還沒完??!還有許多要解決的問題等著我噢?。?/span>
白云小飛
本篇想通過手動(dòng)直接編輯代碼的方式(而不是可視化的方式)來操作使用自定義的圖標(biāo)。(以在窗口標(biāo)題欄上顯示自定義圖標(biāo)為例)
通過本篇,你將知道如何使用圖標(biāo)資源及實(shí)質(zhì),并有助于你理解在可視化方式編輯使用圖標(biāo)資源過程中的代碼實(shí)質(zhì)。
另外,可以觸類旁通,明白VC中的Window應(yīng)用程序?qū)Ω鞣N類似資源(如光標(biāo)資源、位圖資源、聲音資源等)操作的一般機(jī)制。
一 開始我們的思考:程序所要用到的各類圖像、聲音等資源應(yīng)放在什么地方為好?
一個(gè)程序可能需要使用各種圖像、聲音。那么,你認(rèn)為應(yīng)把這些東西放在什么地方呢?
一種很顯然的做法:就是讓這些圖形、圖像、聲音仍以文件形式(如.bmp .jpg .ico .cur .wav等等擴(kuò)展名的文件,)保存在磁盤中,不包含在應(yīng)用程序(.exe)中。當(dāng)我的.exe程序執(zhí)行時(shí)需要這些文件時(shí)再把它們從磁盤中加載到內(nèi)存里并對(duì)它們操作。
如果你是想寫一個(gè)看圖軟件或播放音樂之類的軟件,這當(dāng)然是一種最佳的做法啦。但是,有時(shí)候你的程序可能要顯示一個(gè)代表你程序的圖像等等的情況。由于前述的方法中,圖形、圖像、聲音是以獨(dú)立的文件形式,所以軟件的使用者是很容易就改動(dòng)及刪除它們的,唉!我可不希望這樣啊!
那么,還有什么辦法呢?
還有一種可以想到的做法:就是讓這些圖形、圖像、聲音等數(shù)據(jù)在編譯時(shí)就把它們編譯進(jìn).exe文件中。
顯然,由于程序要用到的各種圖像、聲音等的數(shù)據(jù)已經(jīng)包含在.exe里面了,這樣,軟件使用者只要擁有這個(gè).exe文件就可以。同時(shí)使用者要想改動(dòng)程序所用的這些圖形、圖像、聲音也就不是那么容易。
啊!exe程序文件里居然也能存儲(chǔ)各種類型圖像、聲音!嗯,這可是Windows的一個(gè)設(shè)計(jì)目標(biāo):可執(zhí)行程序里不僅只含有程序代碼,還可以存儲(chǔ)各種圖像、聲音等的數(shù)據(jù)呢!也可能第一次讓你有了這個(gè)念頭!但不管怎么說,程序是可以這樣設(shè)計(jì)的!
好了,那么又如何創(chuàng)建一個(gè)包含有圖標(biāo)資源并使用這個(gè)圖標(biāo)的.exe文件呢?
二 圖像、聲音資源操作的一般步驟。
1 顯然,你得先創(chuàng)建一個(gè)自己想要的圖標(biāo)(提示:這里我以圖標(biāo)為例)
但具體應(yīng)如何做呢?
這個(gè)問題好解決也好理解:我們只要用一個(gè)能編輯圖標(biāo)的圖形圖像軟件就可以啦。制做完圖標(biāo)后,把它們以.ico文件的形式(.ico 是圖標(biāo)文件的擴(kuò)展名)保存在磁盤的某個(gè)文件夾中。
當(dāng)然啦,VC本身也提供了一個(gè)圖標(biāo)編輯器。
2 然后你要給這個(gè)圖標(biāo)定義一個(gè)字符串ID或者整數(shù)ID
為什么要給它定義一個(gè)字符串ID或整數(shù)ID呢?
那是因?yàn)檫@個(gè)圖標(biāo)是要被編譯鏈接進(jìn)可執(zhí)行程序中(即.exe文件,當(dāng)然也可能是.dll文件),這樣程序就不能通過文件名來訪問該圖標(biāo)了。
所以,我們就得給這個(gè)圖標(biāo)定義一個(gè)字符串ID或整數(shù)ID了,用以代表這個(gè)圖標(biāo)。(你可以選擇使用字符串ID,也可以使用整數(shù)ID。)
這個(gè)定義是寫在一個(gè)叫資源腳本文件(擴(kuò)展名為 .rc)里的。
當(dāng)然,還要記住把.rc文件加入工程。
3 使用圖標(biāo)第一步:通過ID號(hào)加載圖標(biāo)
請(qǐng)先想想前面兩部做了什么?前面兩步產(chǎn)生了一個(gè)圖標(biāo)并定義了一個(gè)圖標(biāo)ID。這樣我們的程序就可以使用這個(gè)圖標(biāo)了:程序首先通過以下這個(gè)API函數(shù):
HICON LoadIcon ( HINSTANCE hInstance, LPCSTR lpIconName);
來加載圖標(biāo)。
暫略第一個(gè)參數(shù),先來說一下第二個(gè)參數(shù)LPCSTR lpIconName。該參數(shù)是代表要加載圖標(biāo)的字符串ID。
加載成功后,返回系統(tǒng)分配給該圖標(biāo)的一個(gè)句柄值(類型是HICON)。
(該函數(shù)具體的使用,后面還會(huì)有介紹。)
4 加載后,程序都是通過圖標(biāo)句柄來操作該圖標(biāo)
有了圖標(biāo)句柄值,就可以通過這個(gè)值來操作相應(yīng)的圖標(biāo)了。(還記得“句柄”這個(gè)概念及作用嗎?Window系統(tǒng)總是通過句柄來操作已加載到內(nèi)存的某個(gè)對(duì)象。)
5 最后,我們的程序都編好后,只要把資源與程序代碼一起編譯到.exe文件中
VC會(huì)自動(dòng)用一個(gè)專門的資源編譯器會(huì)把.rc文件及相關(guān)的資源文件(*.ico、*.bmp、*.wav等)編譯生成一個(gè)擴(kuò)展名為.res的二進(jìn)制中間文件。然后再用連接程序與程序代碼的二進(jìn)制中間文件一同連接成可執(zhí)行程序了。
這看上去比較復(fù)雜。不用害怕啊!其實(shí)你只要按原來的方式編譯連接就行了。
到此,最好重新瀏覽一下上述1~5的步驟,并多加體會(huì)這個(gè)操作流程。
現(xiàn)在頭腦有了概念沒有啊?有,那就開始動(dòng)手吧?。ㄗ⒁猓罕酒形沂怯檬謩?dòng)編輯代碼的方式來完成的。)
三 具體實(shí)現(xiàn)范例:本例為圖標(biāo)定義一個(gè)字符串ID(注意:不是整數(shù)ID)
任務(wù):在窗口標(biāo)題欄上顯示一個(gè)自定義的圖標(biāo)。
(提醒:務(wù)必請(qǐng)先對(duì)前一篇所完成工程做一個(gè)備份。因?yàn)橐院笪疫€要從上一篇的工程狀態(tài)開始新的內(nèi)容呢?。?br>
1 首先我們要有一個(gè)自定義圖標(biāo)(文件名以myicon.ico為例)
在我們的工程文件夾(即D:\MyApp)下創(chuàng)建一個(gè)myResLib文件夾(用以集中存放各種資源文件)。
然后,你可用一個(gè).ico編輯器創(chuàng)建一個(gè)圖標(biāo)文件myicon.ico,把它放在D:\MyApp\myResLib文件夾下。
不過,也可能你并不懂得使用任何一款.ico編輯器,那也沒關(guān)系,隨便找一個(gè).ico(16*16或32*32的)文件(這不應(yīng)成為問題吧?)。把它復(fù)制到D:\MyApp\myResLib文件夾下,并改名為myicon.ico。
好了,現(xiàn)在我們有一個(gè)圖標(biāo)文件,請(qǐng)你記住它的路徑和名稱。
2 用記事本程序創(chuàng)建一個(gè)資源腳本文件(文件名為myRes.rc),并在這個(gè)文件中為myicon.ico定義一個(gè)字符串ID(本例為:IDI_MYICON)作為這個(gè)資源的名稱。
之所以用記事本來創(chuàng)建而不用VC本身來創(chuàng)建,是因?yàn)槲也幌M孷C生成一些無關(guān)碼,以便于解說和理解。
另外一點(diǎn)要提醒:如果你的工程里已經(jīng)包含有一個(gè).rc的資源腳本文件,那么在下面的操作會(huì)出現(xiàn)一些不同的情況。但如果從第一篇就按我所述的來操作,本工程是沒有.rc文件的。
操作:
=>開始->程序->附件->記事本
=>在記事本中輸入如下一行:
IDI_MYICON ICON DISCARDABLE " myResLib\ myicon.ico"
看,在ICON DISCARDABLE的左邊寫上ID名,右邊寫上圖標(biāo)所在的相對(duì)路徑字符串。這樣也就將myResLib\myicon.ico圖標(biāo)定義ID號(hào)為IDI_MYICON,并且這樣定義的ID就是字符串ID。(等一下你就會(huì)知道如何使用字符串ID了?。?br> =>點(diǎn)擊“記事本”程序菜單“文件”->另存為->在“保存在”框中選D: ->雙擊打開MyApp文件夾->在“保存類型”框中選“所有文件(*.*)->在“文件名”框中輸入:myRes.rc->點(diǎn)“保存”(操作完成)
現(xiàn)在我們已經(jīng)為myicon.ico定義了一個(gè)字符串ID:IDI_MYICON 。接下來,要干什么呢?哦,你要知道,這樣方式創(chuàng)建腳本文件是不會(huì)自動(dòng)加到我們的工程中,所以你要記得自己把myRes.rc加到你的工程中。
3 將myRec.rc加入到工程中。
=>在工作區(qū)(Workspace)視圖中選FileView選項(xiàng)卡->在其中右擊Source Files -> 單擊“添加文件到目錄… ->雙擊“myRes.rc”
本操作的目的:將資源腳本文件myRes.rc加入到該工程中。下面我們就可以通過代碼來訪問這個(gè)圖標(biāo)了。
4 要使用圖標(biāo)資源,得先用LoadIcon函數(shù)加載圖標(biāo)資源。
LoadIcon原型:
HICON LoadIcon ( HINSTANCE hInstance, LPCSTR lpIconName);
參數(shù):
HINSTANCE hInstance:要加載圖標(biāo)是存在那個(gè)應(yīng)用程序里,就代入這個(gè)應(yīng)用程序?qū)嵗木浔?br> LPCSTAR lpIconName:是你要加載的圖標(biāo)的字符串ID,就是我們?cè)诘?步中定義的。
返回值:加載成功后會(huì)返回一個(gè)圖標(biāo)句柄值,其類型是HICON。加載后,我們就可以通過這個(gè)句柄值來操作對(duì)應(yīng)圖標(biāo)了。
下面就是原來在窗口類結(jié)構(gòu)體中設(shè)置窗口標(biāo)題欄圖標(biāo)的代碼(應(yīng)該還記得下面一行代碼在哪里吧?。?br>wndclass.hIconSm = LoadIcon(NULL, MAKEINTRESOURCE( IDI_APPLICATION ) ) ;
你先別理上面LoadIcon中的參數(shù)使用,現(xiàn)在我們把它改成如下:
wndclass.hIconSm = LoadIcon(hinstance,” IDI_MYICON”) ;
其中,hinstance就是本應(yīng)用程序?qū)嵗木浔!盜DI_MYICON”就是我們要加載的圖標(biāo)。
現(xiàn)在,我們把WinMain主函數(shù)里的代碼修改如下:
int APIENTRY WinMain(HINSTANCE hinstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
//……
wndclass.hInstance = hinstance;
wndclass.hCursor =LoadCursor(NULL, IDC_ARROW);
wndclass.hIcon =NULL;
//wndclass.hIconSm = LoadIcon(NULL, MAKEINTRESOURCE( IDI_APPLICATION ) ) ;
//注釋了上句
wndclass.hIconSm = LoadIcon(hinstance,"IDI_ICON1") ; //添加了此句
wndclass.lpszMenuName =NULL;
wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH);
wndclass.lpszClassName =WND_CLS_NAME;
if (!RegisterClassEx(&wndclass))
return 0;
//……
}
5 最后Build我們的程序
直接F5就行了。
看到了沒,程序的窗口已經(jīng)變成我們想要的圖標(biāo)了。
四 繼續(xù)上例:將圖標(biāo)資源由使用字符串ID改為使用整數(shù)ID
前面我反復(fù)地說到字符串ID和整數(shù)ID。你首先要明白,所有資源的ID號(hào)可以定義為字符串的,也可以定義為整數(shù)型的。我在前例中使用的是一個(gè)字符串ID的例子,現(xiàn)在我又要改為使用整數(shù)ID。
1 創(chuàng)建一個(gè)名為Resource.h頭文件,內(nèi)容如下:
#define IDI_MYICON 100
將IDI_MYICON 定義為一個(gè)整數(shù)型符號(hào)常量。這個(gè)數(shù)值應(yīng)是以1以上一個(gè)數(shù)值。
(注意:頭文件最后要有一空行,也是是說 #define IDI_MYICON 100 后要按一個(gè)回車鍵。)
2 修改myRes.rc文件。
//myRes.rc
#include “Resource.h” //包含頭文件resource.h
IDI_MYICON ICON DISCARDABLE " myResLib\ myicon.ico"
經(jīng)過上述兩處的添加后,IDI_MYICON就不在是字符串ID了,而是整數(shù)ID。因?yàn)镽esource.h已經(jīng)將IDI_MYICON定義為一個(gè)整數(shù)的符號(hào)常量。
3 在WinMain( )函數(shù)所在的源文件中添加包含Resources.h頭文件,并修改LoadIcon()函數(shù)。
// MyAppMain.cpp :主函數(shù)及回調(diào)函數(shù)
#include
#include
#include "resource.h" //包含resource.h
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
//……
wndclass.hInstance = hinstance;
wndclass.hCursor =LoadCursor(NULL, IDC_ARROW);
wndclass.hIcon =NULL;
//wndclass.hIconSm = LoadIcon(NULL, MAKEINTRESOURCE( IDI_APPLICATION ) ) ;
//上句注釋了
wndclass.hIconSm = LoadIcon(hinstance, (LPCTSTR)IDI_ICON1) ;
//第二個(gè)參數(shù)由” IDI_ICON1”字符串改成(LPCTSTR) IDI_ICON1
wndclass.lpszMenuName =NULL;
wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH);
wndclass.lpszClassName =WND_CLS_NAME;
}
分析:
凡是要使用整數(shù)ID資源的文件都要包含Resource.h 頭文件,這是因?yàn)檎麛?shù)ID是由Resource.h定義的。(這個(gè)好理解!)
修改LoadIcon()調(diào)用。因?yàn)長(zhǎng)oadIcon的第二個(gè)參數(shù)接受的是字符串ID(就是LPCTSTR指針),所以,我們得把整數(shù)ID轉(zhuǎn)化成LPCTSTR。(LPCTSTR) IDI_MYICON目的就是將IDI_MYICON強(qiáng)制轉(zhuǎn)化成LPCTSTR。
我們可是辛辛苦苦地把字符串ID改成整數(shù)ID,現(xiàn)在調(diào)用LoadIcon()時(shí)又要將IDI_MYICON 強(qiáng)制類型轉(zhuǎn)化成LPCTSTR類型。嘻嘻,真有意思,好似我們?cè)谙拐垓v似的,到頭來又要回到了原來的狀態(tài)。但不管怎么說,這也是一種方式噢!
好了,現(xiàn)在你可以編譯運(yùn)行試試了。哈哈,也是相同的作用喲!
聯(lián)系客服