《白手起家Win32SDK應用程序》(完整版+目錄)
《白手起家Win32SDK應用程序》
目 錄
第一篇、預備知識
白云小飛
1 說在前面
由于VC6及MFC的特點,我們許多人從標準C++學習到VC6MFC應用程序的編程學習的過度會有一個很大的夸躍,從而感到非常的吃力。
究其原因之一:MFC類庫設計雖然精巧,但我們在使用MFC設計程序時,會發現MFC到處是API函數的影子。MFC并沒有象Delphi的VCL類庫,VB的控件庫一樣封裝得讓人幾乎完全不用知道還有Win32API函數及其操作原理,所以要想利用VC6的MFC編程,我認為就一定要先學習如何直接用Win32API函數來編程。對API編程中的一些關鍵的概念和原理要有一定認識,這樣才會有一個比較平滑的過渡。以上就是我寫這個系列的初衷。
2 我假設你已有的知識:
這里我假設你已經掌握了如下的知識,如果你在如下方面知識有點不太清楚,那要去補一補羅,否則你看到相關的內容時會有麻煩的。
下面說是我對你知識的假設:
2.1 Windows系統的文件、文件夾、路徑的概念
2.2 C語言的基本知識(基本以等級考試二級C語言為準,還要有所擴充)
2.2.1 指針的概念。
2.2.2 函數指針概念。
2.2.3 各種自定義類型(最重要的是struct類型)的概念。
2.2.4 要知道函數的各種參數傳遞形式(值、地址、引用傳遞)。
2.2.5 typedef及其應用。
2.2.6 #include及其應用。
2.2.7 十進制、二進制、十六進制。
2.2.8 按位與、或、非運算的實質。
2.2.9 宏定義概念、使用及意義。
(每個人總是學完了C或C++語法后才會開始用VC6進行Windows編程學習的。但是你的基礎又是如何呢?這是一個關鍵。因此我對你的C知識做了具體的假設。)
2.3 會安裝VC6.0并安裝到一臺機上
2.4 VC6編譯界面的各組成部分及基本操作(至少會用VC6寫控制臺程序)。
2.5 VC6調試中至少要會設置斷點哦。
(呵呵!我的要求不過份吧!)
3 還必須預備的知識:
以上知識是你看本系列的前提,不過我還要給你預備一下我們再這個階段學習中會遇到的新東西。
3.1 你將會接觸到的Win32API函數庫:
以前的DOS下或Windows的控制臺程序下,你要在顯示器上輸出文字,要用printf(),或cout的函數對象來完。但如果你要顯示一個圖形或圖象或為你的程序設計一個圖形化的操作界面等等的,那可就慘了,一切都要你自已完成。復雜得很了!(唉!誰叫DOS是字符界面的操作系統呢!)
現在好了,在Windows下編程你可就輕松得多了。因為Windows操作系統都為我們準備好了,它提供給我們多達數千個函數(啊!我要昏倒了。這么多的函數要學。),我們通過這些函數來操作Windows系統提供給我們的各種功能。比如我要在桌面上創建并顯示一個窗口。就只要調用幾個相關的被稱為API的函數,讓Windows來幫助我們完成這些事。我們是通過這些函數與Windows系統交互的,所以這些函數被稱作Win32應用程序接口函數,簡稱Win32API函數。
請不用害怕喲!其實,這么多的函數我們不必都馬上一一學過,只要掌握了不多的具有代表性的函數的使用方法,并知道大體API函數都提供了哪些功能就可以了。以后要用時再去查。
Window擁有現成的各種各樣的系統功能,供我們的程序調用。那么又是通過什么方式來調用這些系統功能呢?原來,Window還現成提供一個接口,好讓我們的程序來使用這些系統功能,這個結口就是Win32API函數了(注:API是應用程序接口的英文縮寫)。Win32API函數是我們的應用程序與Windows系統交互的唯一途徑。
我并不打算這時就介紹任何一個具體的API函數。你現在只要知道你又要接觸一個函數庫了——被稱為Win32API的函數庫,如同你以前所學的C/C++函數庫。
哈哈,這真是太好了,我們不用再象DOS一樣,自已來完成程序界面的繪制了。我們現在又增加一個全新的函數庫,只要調用幾個相關API函數,剩下的一切由Windows來完成就可以啦!(當然還有很多其它功能。)
3.2 “新”的數據類型:
學完C、C++之后,我們就可以開始進入VC6的Windows編程學習了。但是在接下來的學習中我們會發現在Windows編程中有許多“新”的數據類型。看下面:
BOOL、BYTE、INT、UINT、WORD、DWORD、FLOAT、CHAR、LPSTR、HINSTANCE、HWND、HMENU、HICON等等。
你看這些大寫的數據類型,你以前有見過嗎?還有很多哦!我們以后的學習過程中還會見到的。(呵呵!你可要有思想準備了!)
這真是讓我們初學者迷惑呀!難道VC6中對C/C++的基本數據類型又有重大的擴充了嗎?
其實不用害怕,只是用新瓶裝舊酒而已了。在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 的別名而已,也就是說它們是等價的。只要你包含了相關的頭文件,然后你就可以這樣申明一個變量:
INT i; //等同于int i;
CHAR a; //等同于char a;
LPSTR pa; //等同于char *pa;
明白了嗎?
我想你一定會問:為什么要這樣轉義呢?我們直接用int 、unsighed int、char等等不就行了嗎?我一句兩句也說不清,你只要知道,微軟這樣做一定是要道理的。
哦!還有這些HINSTANCE、HWND、HMENU、HICON我沒說呢!今后你還會見到許多這樣以H為開頭的數據類型,下面就讓我在下一節的“句柄”概念中說給你聽。
3.3 “句柄”概念
由windows系統創建出來的或加載的對象(如應用程序進程、線程、窗口、菜單、圖標、光標等等的對象),windows系統都會分配給它們一個唯一的標識值,作為這些對象的標志,稱之為句柄。我們程序中對這些對象的操作其實就是對其句柄的操作。請記住,句柄就是這些對象的“代號”了。
在編程序中,我們需要用相應的句柄變量來保存這些句柄值,那么用什么類型的句柄變量呢?
就是我們前面提到過的HINSTANCE、HWND。
像其它變量一樣(如:int a;)申明句柄變量,如下:
HINSTANCE hst; //hst變量可以保存某個應用程序實例(即一個進程)的句柄。
HWND hwFirst; //hwFrist變量可以保存某個窗體句柄。
HMENU hMenu; //hMenu變量可以保存某個菜單句柄。
HICON hIcon; //hIcon變量可以保存某個圖標句柄。
具體的使用讓我以后再慢慢與你道來啦。
那么這些類型的實質又是什么?
目前,它們都只是一個int類型(小語:我聽說微軟也許以后會改變它的類型)。不過不管怎樣,你現在只要把HINSTANCE、HWND、HMENU、HICON當做是一個獨立的數據類型就可以了。
3.4 消息標識
Windows系統是一個基于消息的系統。這樣的機制導致我們的程序與以往DOS下的程序流程會有很大的不同。(這可是很考我們的智慧嘍!)
從軟件使用者角度看一個Win32窗口程序運行的過程:
1) 我們運行一個應用程序,程序創建并顯示一個我們想要的程序窗口。
2) 當我們對窗口進行操作時(如單擊、雙擊、右擊、按下鍵盤、最大化、最小化、關閉窗口等等),程序會完成特定的操作,如:單擊最大化、最小化按鈕時,窗口會最大化、最小化操作;對窗口中菜單項的選取時,會完成該菜單的相應功能。
從程序員的角度看一個Win32窗口程序運行的過程:
1) 我們運行一個應用程序,程序中我們通過Win32API函數創建并顯示一個我們想要的程序窗口。(由我們的程序來調用函數實現)
2) 當我們對窗口進行操作時(如單擊、雙擊、右擊、按下鍵盤、最大化、最小化、關閉窗口等等),窗口會自動產生一系列相應的消息(這是由操作系統實現的)。
3) 具體地講:當我們改變窗口大小時,會產生WM_SIZE消息;單擊關閉按鈕關閉窗口時,會產生WM_CLOSE消息;選取某一菜單項時,會產生WM_COMMAND消息;按下鍵盤時,會產生WM_CHAR、WM_KEYDOWN、WM_KEYUP消息;單擊鼠標左鍵時,會產生WM_LBUTTONUP、WM_LBUTTONDOWN消息等等。啊,很多很多,我也不必全部羅列出來了。(我說過了,這些都是由操作系統實現的)
4) windows系統會將這些消息排入我們窗口所在線程的消息隊列中(你會明白線程是什么嗎?)(也由Window操作系統實現),這樣我們的程序才有機會獲取并處理這些產生的消息。
5) 我們的程序可以通過Window操作系統提供的API函數來獲取這些消息及相關的信息。然后通過我們學過的條件判斷語句來判斷是什么消息及其相關的操作信息并可編寫相應的程序代碼,從而實現對窗口操作的不同反應。(由我們的程序來實現)
看上述的過程描述,你可能要有點的抽象思維能力了。你現在只要有對程序流程有如上的大體認知就可以了。慢慢地我會將上述流程變成確實的程序代碼噢!
(等等,還是有個問題:這些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進制整型數值的符號常量而已。每一個不同的整型數值代表著一個窗口某一操作的標識,因此我們將這些數值或者說以WM_開頭的符號常量稱之為消息了。
也就說,我們在窗口中作各種不同的操作,Windows系統會產生各種相應的數值。我們就是通過條件語句比較這些數值來判斷我們在窗口中所做的操作的。
3.5 資源標識
(你看我沒完沒了地介紹一個個概念,覺得煩不煩?不用你說,我自已也有點煩了。唉!不過這些似乎是必要的,所以我不得不堅持下去。不過,還好,剩下的不多了。)
那么VC6中資源是什么一種概念呢?
我們的程序中可能要用到各種圖標(*.ico文件)、各種形狀的鼠標(*.cur文件)、各種圖像(*.bmp/*.gif等等)、各種聲音(*.wav等)、各種菜單……,這些就是我們這里所說的資源了。
每一個要用到資源,我們都要給它分配一個編號或名稱,作為這個資源的標識。之后我們的程序只是通過這個編號或名稱來訪問這些資源了。所以這些編號或名稱我們稱之為資源標識。好了,現在你也只要有了一個大體的映象就可以了,具體的形式和應用讓我慢慢再與你說了。
(各位可以提出你的疑問,白云小飛一定會盡力回復的。)
啊!終于結束冗長的概念解說了,看到這里,請先回顧一下我們前面講的東西。然后嘛——我們可以開工啦!。
第二篇、創建Win32工程和主函數
白云小飛
1 在D:\創建一個空的工程(工程名為MyApp)
要編寫一個程序,我們就要首先用VC6應用程序向導創建一個工程,下面我將給你創建一個空工程(也就是沒有任何源文件及代碼的工程)
1.1 操作:
=>文件->新建…->”工程”標簽->位置:”D:\”(你可以設置你想要創建的位置)->工程名:MyApp(你可以自己指定其它名)->選擇”創建新的工作空間”->確定->一個空工程->完成
1.2 請查看指定位置下生成的文件:打開D:\MyApp
我們發現,它在D:\下生成了一個MyApp文件夾。
并在MyApp文件夾下生成了幾個文件,如下:
MyApp.dsp
MyApp.dsw
MyApp.ncb
MyApp.opt
其中,MyApp.dsp是項目文件和MyApp.dsw是工作區文件,它們是不能刪除。
項目文件的作用:生成一個EXE或DLL程序的所有相關源文件、有頭文件的位置信息都記錄在MyApp.dsp項目文件中。
工作區文件的作用:如果一個復雜的軟件工程可能是由多個EXE和多個DLL程序組成,這樣每個項目文件的位置信息又記錄在MyApp.dsw工作區文件中。
當然,我們的這個工程只有一個EXE程序,所以只有一個項目,這個項目文件MyApp.dsp也同樣要記錄在MyApp.dsw中了。
MyApp.ncb和MyApp.opt雖刪除后會自動生成,但是還是請不要這樣做哦!以后我還會說它們的作用。
我們下次要編輯源程序時,只要打開工作區文件MyApp.dsw就可。
2 在D:\MyApp下創建一個C++源文件,文件名為MyAppMain(當然你也可以自己定義一個文件名),并同時加入到MyApp工程中
2.1 操作:
=>文件->新建…->”文件”標簽->選”添加到工程”->選”MyApp”->文件名:MyAppMain->”位置”默認->確定
2.2 查看指定生成的文件:
可以看到,在D:\MyApp文件夾下生成了MyAppMain.cpp源文件。
3 在MyAppMain.cpp文件中輸入一個主函數
3.1 代碼如下:
#include <windows.h>
#include<windowsx.h>
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
// 將會在這里輸入主函數的代碼
return 0;
}
3.2 包含必要的頭文件:
首先你只要包含下面兩個頭文件就可,因為它們已經包含了絕大多數的MyApp應用程序必要的頭文件。
#include <windows.h>
#include<windowsx.h>
3.3 主函數名:
函數頭定義的書寫格式很有趣:
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
寫成四行,其實沒什么,只不過寫在一行里太長了,如下:
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow)
并且寫成上面的四行反而可讀性更強,所以以后你會經常看到這樣書寫的。
WinMain函數相當于Dos下的main函數,Windows應用程序的主函數不用main而是用WinMain。一個程序必有并只能有一個WinMain函數。這個主函數可以寫在本工程中的任何一個.cpp文件中。
3.4 參數:
hinstance:類型是HINSTANCE,這種類型就是前面我們可是提到過的應用程序句柄啦。hinstance的值就是你的當前應用程序進程的句柄。
你的程序每次運行,它的hinstance值是不會一樣的。不過我們并不關心hinstance的值是多少。我們只要知道hinstance里的值就是代表本應用程序進程的句柄值就可以了。
我們等一會兒就會用到它了,請看好哦!
hprevinstance:現在已經不用了。我們可以完全不理它。
lpcmdline:這是一個命令行參數。與main(int argc,char **argv)中的命令行參數相似。由于與本題無關,我們也不可完全不理它,對此不再進一步討論下去。
至于它的數據類型LPSTR,我在前面已經說明了,它其實就是char *類型。
ncmdshow:一個整型值。啟動過程中被傳遞給應用程序,帶有如何打開應用程序主窗口的信息。這樣,程序的使用者有了一點點的控制應用程序如何啟動的能力了。作為一個程序員,如果想忽略它也可以,想使用它也行。哦,那我們這里也先忽略它了。
3.5 返回值:
是一個int值,當我們的程序正常結束退出時,一定要返回一個0值。所以我們的WinMain函數體內最后有return 0;了
3.6 函數名前的WINAPI是什么?
最后還有一個要說明的——就是WINAPI。這是什么呢?
在VC6的Windef.h頭文件中是如下定義的:
#define WINAPI __stdcall
也就是WINAPI等于__stdcall了。
你知道嗎,凡是提供給Windows操作系統調用的函數都得是__stdcall調用的。WinMain主函數當然是由Windows系統調用的,因此定義WinMain前要用__stdcall(即WINAPI)修飾。(你會明白函數調用方式的具體含義嗎?不知道也沒關系,現在只要記得WINAPI要放在WinMain前就行了。)
另外說明一點,int 與WINAPI哪個在前哪個在后都是可以的。
好了,我們現在對主函數定義處的各個部分有了必要的了解。你可以休息一會兒,然后回顧一下我們前面操作的過程:
想想我們在本篇中做了哪些事?
生成了哪些文件?
這些文件是什么作用的?
主函數定義的各個部分是什么作用?
如果你已經對上面所做的感到比較清晰,那太好了,Come on!我們繼續吧!
第三篇、增加一個回調函數
白云小飛
1 請再創建一個函數。
LRESULT CALLBACK WinProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
//這里可以輸入響應消息的代碼
return 0;
}
此函數人稱回調函數也稱自定義窗口過程函數。但其實它與我們一般函數無異,你千萬不要把這個函數想得太復雜了啊!
記住,函數名WinProc及各參數變量名是可以由你來指定,但是參數類型、個數、順序、及返回值類型是不能改變的。
那么,這個函數是干什么的啊?
它是一個用來與某窗口關聯的函數。當我們在窗口上的每一個操作,都會調用這個函數。這在個函數里,我們可以寫上代碼來完成我們在窗口上操作的相應功能。
先來看參數部分:
UINT msg :UINT類型不是我們在前面也見過嗎?就是unsigned int類型了。每次程序運行時用戶在窗口上操作所產生的消息都會通過msg傳遞進來。msg里保存的值就是我們對窗口操作時產生的消息值(即WM_CLOSE、WM_CHAR、WM_COMMAND、……的值)。
HWIND hwnd :哦!這是我在第一篇中講過的一個窗口句柄類型的變量。我們可以通過傳入的hwnd的值知道msg中的消息對應的是哪一個窗口。
WPARAM wparam , LPARAM lparam :wparam和lparam這兩個參數變量的類型分別是WPARAM和LPARAM。呵呵,我不是說過,你還會見到許多“新”的數據類型的嗎?你可要有心理準備噢。
在windef.h中有定義:
typedef UINT WPARAM; //看,WPARAM其實就是unsigned int。
typedef LONG LPARAM; //看,LPARAM其實就是long類型
typedef LONG LRESULT; //LRESULT與LPARAM一樣也是長整型long
返回值:類型為LRESULT,看windef.h中的定義可以知道是一個長整型long現在你大體知道這些就行了。至于更進一步的認識,讓我們先放到一邊去吧!
CALLBACK:這又是什么呢?
在windef.h中的定義如下:
#define CALLBACK __stdcall
#define WINAPI __stdcall
哦!它與WINAPI是一樣的,也是__stdcall了,這個WinProc函數是提供給操作系統調用的。我說過操作系統調用的函數都得在前加__stdcall。
(那為什么不直接用__stdcall呢?)
呵呵,這至少可以增加代碼的可讀性及易改性。
我猜也許你還會繼續下一個疑問:那我又如何具體地使用這個函數呢?慢慢來,現在你只要輸入到你的.cpp文件中就可以了。
到此為止,我們的MyAppMain.cpp文件代碼如下:
//Begin MyAppMain.cpp
#include <windows.h>
#include<windowsx.h>
LRESULT CALLBACK WinProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
//這里可以輸入響應消息的代碼
return 0;
}
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
// 將會在這里輸入主函數的代碼
return 0;
}
//End MyAppMain.cpp
目前為止,這個程序還是一個什么事都沒做的Win32應用程序。當然它是不會顯示任何窗口的,不過我會一點一點地完善它的。
最后請再認真瀏覽一下上述代碼吧!重點回顧各參數的類型和作用。
第四篇、注冊一個窗口類
白云小飛
一 創建并顯示一個窗口的“遐想”。
? 首先,要顯示的窗口在哪里呢?
要想顯示你自己的窗口,顯然你得事先創建一個自己的窗口。當你想要一個窗口時,Window系統才會為你創建窗口。不要時,Window再銷毀這個窗口。噢,這是多么相當然的一種機制啊,你說是吧!也就是說我們得先創建一個窗口才能顯示。(否則哪里來的窗口給你顯示呢?)
? 其次,你想創建什么樣的窗口呢?
那么,創建什么樣的窗口呢?創建前,Window系統可不知道你要的是什么類型的窗口啊(比如標題欄上顯示什么圖標,鼠標形狀是什么,窗口背景顏色等等)。這些類型信息應在你創建前事先告訴Window系統。可以采用這種方法:就是我們事先寫一份要創建窗口的類型申請表,提交(注冊)給Window系統。然后在創建時,可以讓Windows按這個申請表來創建你所要的窗口了。也就是說我們還應該先提交一個申請表,申請成功后再根據這個表創建一個窗口。
依據上述的理由,我假想了以下幾個步驟要做:
第一. 你得先填寫一份你想創建的窗口類的“申請表”。
第二. 然后將這“申請表”通過一個API函數提交給Windows系統(即注冊到Windows系統中)。
第三. 如果提交(注冊)成功,就說明Window系統通過了你的“申請表”,Windows系統中就有了一份你所申請的窗口類(注:這個注冊成功的已經注冊在系統中的“申請表”我們稱之為窗口類)。這樣你就可以利用這個申請成功的窗口類,通過一個專門的API函數讓Windows系統創建一個或多個的同一窗口類的窗口。
第四. 創建成功后,我們有了窗口。但是,雖然窗口已存在在內存中,并不一定就馬上顯示在屏幕上(這根據你的意愿了),所以之后的某時你可以用一個API函數來讓Windows系統顯示剛才創建的窗口。
Window系統就是這樣設計的噢!(呵呵,這樣設計不算壞,我可以接受。)
以上就是創建一個窗口的大致過程。請記住,在Window系統下你的程序要顯示一個你想自定的窗口總是得經歷如此步驟的。還要記住一點,我們的代碼只是通過調用Window系統所提供的API函數來完成對窗口間接的管理。實際上窗口的管理操作都由Window系統直接完成的。
好,讓我們在本篇中先來完成第一、二步驟吧!
二 第一步 填寫一份“申請表”
1 用什么來作為這種“申請表”呢?
我想,C語言中的struct結構體類型的變量來充當這個“申請表”是再合適不過的了。呵呵,真是這樣,VC6下早已為我們準備好了這樣的“申請表”了。那就是WNDCLASSEX(我們稱之為窗口類結構體)。
看看這個WNDCLASSEX結構體的底細吧!
在windef.h中已經有定義:(下面所列的與真實文件中會有點不同,但目前你只要理解我這份就可以了。)
typedef struct tagWNDCLASSEX {
UINT cbSize; //用來保存本結構體的所占字節數
UINT style; //窗口類型風格。
//比如,可設置“若移動窗口寬度時,則刷新整個窗口。
WNDPROC lpfnWndProc; //回調函數指針,用以指向前面那個回調函數。
int cbClsExtra; //略,我們可不必使用它,只要賦值為0就可
int cbWndExtra; //略,同上
HINSTANCE hInstance; //窗口所屬的應用程序實例句柄。
HICON hIcon; //大圖標,(這個圖標會顯示在窗口的哪里呢?)
HICON hIconSm; //小圖標,(這個又會顯示在窗口的哪里呢?)
HCURSOR hCursor; //鼠標句柄,用以指定鼠標移入窗口時的樣式
HBRUSH hbrBackground; //用來刷背景顏色的畫刷句柄,
//窗口的顏色就會按這個顯示。
LPCSTR lpszMenuName; //用來指向菜單資源名稱字符串的指針,
//可讓你的窗口有一個菜單。
LPCSTR lpszClassName; //用來指向窗口類名字符串的指針
//作為這個窗口類的標識
} WNDCLASSEX
哦!是一個有12個成員變量的struct結構體的類型。(看來我們又要一個一個耐心地搞定它。)
2 創建一個“申請表”
現在就先讓我用這個WNDCLASSEX結構體申明一個變量吧!(看粗體字部分)
HWND hWnd;
MSG msg;
WNDCLASSEX winclass; //變量名可由你自己定。
順便解釋一下:
這里我順便地同時申明了hWnd的窗口句柄變量,用以保存將要創建出來的窗口的句柄。
還申明了一個msg:它的類型是MSG,也是一個已預定義好的結構體,用來保存窗口操作的各種消息信息。
好了好了,以后再說這兩個東東啦。
3 填寫“申請表”
接下來就到了填寫這個“申請表”的時候啦!也就是要對winclass這個結構體變量的各成員賦值。
winclass.cbSize = sizeof (WNDCLASSEX);
這是將本結構體的大小(占字節數)賦值給其成員變量cbSize,一定要這樣做哦!好處是:以后Windows系統只要訪問cbSize就可知道wndclass的大小了,就不必每次都要用sizeof(WNDCLASSEX)來獲取大小。(哦,不錯,這真是一個很值得學習的做法)
winclass.stype= CS_VREDRAW | CS_HREDRAW | CS_OWNDC | CS_DBLCLICKS;
我們這里將這四個值同時賦值給成員變量stype。
新問題:CS_VREDRAW、CS_HREDRAW、CS_OWNDC、CS_DBLCLICKS是什么啊!
stype是一個UINT即unsigned int的類型(共32位二進制位)。stype的可能值為如下的組合:
表二.3 窗口類的類型標志
標識 描述
CS_HREDRAW 若移動或改變了窗口寬度,則刷新整個窗口
CS_VREDRAW 若移動或改變了窗口高度,則刷新整個窗口
CS_OWNDC 為該類中的每個窗口分配一個單值的設備描述表
CS_DBLCLKS 當用戶雙擊鼠標時向窗口程序發送一個雙擊的信息,同時,光標位于屬 于該類的窗口中
CS_PARENTDC 略
CS_NOCLOSE 禁止系統菜單上的關閉命令
CS_SAVEBITS 略
關于更詳細的類型標志描述,請自行參考相關書籍。
你可能看完后仍還不能完全明白這些窗口類的類型標志的意思。沒有關系,它不妨礙我們對整個程序框架的理解(我們可是要善于把握輕重緩急噢!),現在你只要按我上面賦值就可以了。
還有一個問題:符號“|”是按位或的運算符,即表示CS_VREDRAW、CS_HREDRAW、CS_OWNDC、CS_DBLCLICKS的值同時都賦值給stype
(只能解釋到這里了,如果還不太明白“|”的運算,請自己去看關于C語言中“按位或”的相關知識了。)
winclass.lpfnWndProc=WinProc;
lpfnWndProc是一個函數指針(第一篇我已經說過你要對函數指針有一定認識。),它是WNDPROC函數類型,這個函數類型在winuser.h文件中已有定義。如下:
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
發現沒有,WNDPROC類型的格式與我在第三篇中寫的那個自定義窗口過程函數的格式是一樣的。現在我們將我們的這個函數WinPro的地址賦值給lpfnWndProc函數指針變量。
將前面的那個回調函數WinProc的首地址賦值給成員變量lpfnWndProc。這樣這個窗口類就與這個回調函數相關聯起來了。
這里,你可能有個疑問:為窗口類指定一個窗口過程函數有什么用處呢?這是Window系統的消息機制的關鍵。但我現在不想說太多,因為真的現在無法說清楚的。只有等到我將整個程序框架建立起來后,我們再來理解它吧。(唉!我知道這種說了卻又沒能完全說清楚的情況不應該有,但真的是不得不為之啊!)
wndclass.hInstance = hinstance;
第二篇中可是說過主函數參數中hinstance的值就是本應用程序實例句柄值,我們現在將這個hinstance的句柄值賦值給wndclass.hInstance。這樣,由這個窗口類結構創建的窗口就與本程序實例相關聯了。
wndclass.cbClsExtra=0;
wndclass.cbWndExtra =0;
我們現在只是對cbClsExtra和cbWndExtra兩個成員簡單賦值為0。你現在也不用管它們是做什么的。可以保證絕大多數的程序只要這樣就行。
wndclass.hCursor =LoadCursor(NULL, IDC_ARROW);
hCursor是一個鼠標光標句柄變量,用來為這個窗口類指定一個鼠標句柄值(也就是想讓這個窗口顯示一個什么樣的鼠標形狀了)。LoadIcon(NULL, IDC_ARROW);這個函數是加載一個光標給hCursor的。你現在只要造就輸入就可,其它的以后我再說了。
wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
wndclass.hIconSm = LoadIcon(NULL,IDI_APPLICATION);
hIcon和hIconSm都是圖標句柄變量,前一個是用以指定大圖標,后一個是用以指定小圖標。
hIconSm中設置的圖標會顯示在你的窗口的標題欄左邊。
hIcon中設置的圖標是會顯示在哪兒啊?不好意思,我真的不知道。如果有人知道,請告之,本人非常感謝!
關于LoadIcon函數,它是加載一個圖標給hIcon及hIconSm的。現在你只要造就輸入就可以了。
wndclass.lpszMenuName =NULL;
lpszMenuName是一個字符串指針,這是用來指向一個菜單資源名字符串。我們這個窗口暫時不要菜單,所以這里先指定NULL。
wndclass.hbrBackground =(HBRUSH)GetStockObject(WHITE_BRUSH);
hbrBackground是一個畫刷句柄變量(類型HBRUSH)(呵呵,又一個新句柄。)。窗口客戶區的背景顏色是用這個變量指定的畫刷來刷的。看不明白(HBRUSH)GetStockObject(BLACK_BRUSH)這個函數沒有關系,這里它提供了一個白色畫刷給我們。你現在只要造就輸入就可以。
wndclass.lpszClassName ="WINCLASS1";
lpszClassName是一個字符串指針,它是為這個窗口類(申請表)指定個字符串,這個字符串作為這個窗口類的名稱。這里我們指定”WINCLASS1”字符串作為這個窗口類的名稱。(要注意一點的是,一個程序中如果申請(注冊)了多個窗口類,那么每個窗口類的這個字段值是不能相同的。)
到此為止,我們終于(有點稀里糊涂地)結束了這個結構體各成員變量的賦值了。以上成員變量賦值代碼匯總如下:
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";
好了,我們已經填好了我們想要創建的窗口的“申請表”了,現在可以透一口氣了!
然后……再繼續,因為我們的任務還沒用完成呢!
三 第二步 提交“申請表”給Window系統
到了提交“申請表”的時候了。我們現在要將wndclass中的各項數據信息提交(注冊)到Windows系統中。有一個API函數是專門用來注冊窗口類信息的,原型如下:
ATOM WINAPI RegisterClassEx(CONST WNDCLASSEX *);
參數是WNDCLASSEX *類型的指針,只要將要注冊的WNDCLASSEX結構體變量地址代入就行。
返回值是一個ATOM類型,其實它也是一個unsigned short類型的別名而已。如果注冊不成功則程序返回0值,否則表示注冊成功。(注:我們暫時不關心其它返回值的意義。)
所以我們注冊窗口類的代碼可以如下:
if (!RegisterClassEx(&winclass))
return 0; //不成功程序就直接結束了
四 最后總括代碼
本篇中所增加的全部代碼現總括如下(粗體字部分):
WINAPI int WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
HWND hWnd; //窗口句柄變量
MSG msg; //消息結構體變量
WNDCLASSEX wndclass; //窗口類結構體變量
//以下是對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結構體的信息注冊一個窗口類
if (!RegisterClassEx(&wndclass))
return 0;
//……
return 0;
}
第五篇、利用已注冊的窗口類來創建一個窗口
白云小飛
一 用CreateWindowEx函數來創建窗口
1 參數及返回值說明:
上篇中我們完成了向Windows系統進行窗口的“申請”工作(即注冊一個窗口類)。本篇就是要用這個窗口類來創建一個窗口。
下面這個API函數就是專門用來創建一個窗口的:
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);
這是一個擁有一大串參數的函數。(唉!我又要一個個地介紹了。)
DWORD dwExStyle:這是用來指定擴展樣式標志,絕大多數情況,我們只要指定為NULL,所以我不想多說。
LPCSTR lpClassName:我們要用前篇中注冊的窗口類來完成創建窗口,所以lpCassName所指字符串值要與前篇中注冊時所用的窗口類名值相同(即wndclass.lpszClassName的值)。本例中就是"WINCLASS1"字符串值。
LPCSTR lpWindowName:此指針所指的字符串會顯示在標題欄上(即標題欄文字)。
DWORD dwStyle:這是用來指定窗口外觀類型的。以下是它可能的值(部分):
表 dwStyle可以設置的值
WS_POPUP 彈出式窗口
WS_OVERLAPPED 帶有標題欄和邊界的重疊式窗口。
WS_OVERLAPPEDWINDOW 具有WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU、WS_THICK、FRAME、WS_MINIMIZEBOX、WS_MAXIMIZEOBX樣式的重疊式窗口
WS_VISIBLE 創建之后就立即顯示窗口
…… (還有很多其它值呢!請自行參看其它參考書)
還記得上篇中注冊窗口類時窗口類結構體成員中有一項:
wndclass.style = CS_VREDRAW | CS_HREDRAW | CS_OWNDC | CS_DBLCLKS;
你可不要搞糊涂啦!wndclass.style所指的是針對窗口內在特性的類型,而CreateWindowEx參數中的dwStyle窗口類型是窗口外觀的類型。
int X, int Y,:這是指定相對于桌面的窗口左上角位置(坐標)。
int nWidth, int nHeight,:指定窗口的寬度與高度。
(上面這四個參數應該好理解吧!)
HWND hWndParent :父窗口句柄,不明白它的作用吧?現在你只要賦值為NULL就可。
HMENU hMenu:你還記得嗎?我們在前篇的窗口類結構體變量賦值中有這么一句: wndclass.lpszMenuName =NULL; 它是可以為窗口指定一個菜單的。
CreateWindowEx函數的這個hMenu菜單句柄(這又是一個句柄噢!)也可以為窗口指定一個菜單,它將代替前面的設置。不過我們現在暫時不要菜單,所以也賦值為NULL。
HINSTANCE hInstance:要與wndclass.hInstance值相同,本應用程序的句柄代入這里。
LPVOID lpParam:高級特征,現在我們只要設置為NULL就行。
(噢,好不容易介紹完這些參數了。)
返回值:如果窗口成功創建出來,則返回這個窗口的句柄,如果創建不成功,則返回0值。
2 具體實現代碼:
下面我就給出具體的創建窗口代碼:
hWnd=CreateWindowEx(NULL,”WINCLASS1”,
"這是我的第一個窗口",
WS_OVERLAPPEDWINDOW ,
0, 0,
400,400,
NULL,
NULL,
hinstance,
NULL );
if (!hWnd) //這里應該判斷是否創建成功
return 0;
3 調試及結果:
程序寫到這里似乎窗口就可以創建出來了。那就讓我們調試調試?OK,Let’s go!請在
if (!hWnd)
return 0; //設斷點
中的return 0處設置一個斷點。看看你的程序會不會執行到這個return 0;當創建不成功,則hWnd==0,程序會執行到這個return 0。反之,程序成功地創建了這個窗口。
(哦!我并不知道你是否會用VC6來設置斷點并進行調試。
只要光標放在return 0 處按F9就可以設置此處的斷點。如果你再按F9一次,則會取消這個斷點。
設置完斷點后,按F5編譯運行這個程序試試。)
矣?不太對勁啊,調試中我們發現hWnd值為0,即說明窗口并沒有創建成功。
(在這里請再按F5,可以從斷點處繼續運行,程序結束了。)
原因是?……
二 CreateWindowEx的一個條件:調用缺省窗口過程DefWindowProc函數
1 在WinProc中增加調用DefWindowProc函數
原因是:我們還要在這個窗口的自定義窗口過程WinProc中增加如下代碼(黑體字部分):
LRESULT CALLBACK WinProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
//……
return DefWindowProc(hwnd, msg, wparam, lparam);
}
真讓我疑惑啊!
CreateWindowEx函數創建一個窗口與這個DefWindowProc函數什么關系。
又為什么要把這個函數寫在WinProc回調函數里呢?
首先,看DefWindowProc在Winuser.h中的原型定義:
LRESULT CALLBACK DefWindowProc(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam);
你有沒有看出,它的函數格式與我們自定義WinProc回調函數的格式一完全一樣的?只不過DefWindowProc是Window系統提供給我們的一個API函數。
那么在整個調用CreateWindowEx函數中到底發生了什么事啊?讓我來告訴你吧!
2 原因何在?
從我們的自定義窗口過程WinProc函數說起:
當應用程序運行時,用戶對該程序窗口的操作,都會自動產生一系列的消息。比如創建一個窗口會產生WM_CREATE消息;移動窗口會產生WM_MOVE消息;按下鍵盤時會產生WM_KEYDOWN消息;關閉窗口時會產生WM_CLOSE消息等等。也就是說,在執行CreateWindowEx函數期間會產生若干個消息(先別管都是些什么消息)。
Window系統會將這些由一系列的消息添加到該進程的消息隊例中(噢,你可要有一點想象力喲!)。在CreateWindowEx函數中同時會調用我們寫的WinProc函數若干次,并把消息的各項信息通過WinPro函數參數傳遞進來。(我再說一遍:你得有點想象力啊)
說白了,就是在執行CreateWindowEx創建窗口過程中會引發對WinProc函數的多次調用。
創建窗口過程中要用到缺省窗口過程函數DefWindowProc:
前面說過,在執行CreateWindowEx創建窗口過程中會引發對WinProc函數的多次調用。嘿嘿,這可不是可有可無的調用啊!在這里,我們要讓缺省窗口過程DefWindowProc來完成一些默認的消息處理操作。你不必知道它做了什么事,只要把這一切消息都“扔”給它就行啦!只有讓 DefWindowProc函數完成必要的消息處理,CreateWindowEx函數才能全程地完成窗口的創建(否則,嘿嘿!窗口的創建必將失敗。)。所以我們添加了調用DefWindowProc的代碼。(DefWindowProc的返回值返回的是對消息處理的結果,我們再將它作為WinProc的返回值。)
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;
//…… 這里省略了前篇所述的注冊窗口類的過程
hWnd=CreateWindowEx(NULL,WND_CLS_NAME,
"這是我的第一個窗口",
WS_OVERLAPPEDWINDOW|WS_VISIBLE ,
CW_USEDEFAULT, 0,
400,400,
NULL,
NULL,
hinstance,
NULL );
if (!hWnd)
return 0;
//……
return 0;
}
4 具體分析
現在現具體地分析一下調用CreateWindowEx函數的過程:
1. 首先從調用CreateWindowEx開始,程序進入了CreateWindowEx函數體內的代碼(就是Window系統的代碼)。
2. 在此期間,CreateWindowEx會產生若干個消息(我們不用管是什么消息)。
3. 每當一個消息產生后,CreateWindowEx會自動調用我們自己寫的WinProc函數,將消息信息傳遞進來。
4. 這些消息我們自己不作處理而是直接在WinProc調用缺省窗口過程DefWindowProc來作缺省處理(你不用去管DefWindowProc做了什么),并返回處理結果。
5. 每次WinProc結束后回到CreateWindowEx代碼處,CreateWindowEx會根據返回值果進行判斷,完成最終的窗口創建。
怪不得嘛!沒有調用DefWindowProc這個函數,CreateWindowEx無法完成全部的窗口創建過程,所以最終創建窗口失敗了。
5 再調試
很好!現在再按前面講的調試步驟試試看。
if (!hWnd)
return 0; //這里設斷點
程序不再執行這個return 0;了。太好啦!這說明窗口創建成功了。當然并沒把窗口顯示出來,所以你只看到程序停都不停一下就馬上就結束了。
關于窗口的顯示我就留到下一篇了。
第六篇、顯示你創建的窗口
白云小飛
哈!到了顯示窗口的時候啦!
看,下面這個函數就是用來顯示窗口的:
BOOL ShowWindow( HWND hWnd, int nCmdShow);
一 ShowWindow函數的參數及返回值
hWnd就是你要顯示的窗口的句柄:
nCmdShow是窗口的顯示方式,其可能的值如下:
SW_HIDE 隱藏應用程序窗口
SW_SHOWNORMAL 激活并顯示窗口,如果窗口被最大化或最不化,系統恢復窗口到原始大小和位置(與SW_RESTORE)
SW_RESTORE 同SW_SHOWNORMAL
SW_NORMAL
SW_SHOWMINIMIZED 激活并最小化窗口
SW_SHOWMAXIMIZED 激活并最大化窗口
SW_SHOW 激活窗口,并按其當前大小和位置顯示
SW_MAXINIZE 最大化應用程序窗口
SW_MINIMIZE 最不化應用程序窗口
SW_SHOWNOACTIVATE 按最近大小和位置顯示窗口,但不改變激活特性
SW_SHOWMINNOACTIVE 最小化窗口,但不改變其激活特性
SW_SHOWNA 按當前大小各位置顯示窗口,但不改變其激活特性
返回值:成功則返回TRUE,不成功則返回FALSE。
二 先來兩個例子
例一:現假設已經創建了一個窗口,并且該窗口句柄已保存在hWnd變量中。我希望將窗口最大化并使該窗口為當前窗口(即激活該窗口)。請寫出ShowWindow函數的具體實現代碼。(注:可不必處理它的返回值)
解:ShowWindow(hWnd, SW_SHOWMAXIMIZED);
例二:我希望隱藏一個原來是顯示著的窗口。該窗口的句柄在hWnd變量中。應如何寫ShowWindow函數呢?
解:ShowWindow(hWnd, SW_HIDE);
三 本系列程序中的實現
(快一點嘍,我想馬上動手啦!)
好,我們現在繼續完善我們的這個Win32SDK程序吧!
具體代碼如下(注意粗體字部分):
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
HWND hWnd;
MSG msg;
WNDCLASSEX wndclass;
//…… 這里省略了前面所述的注冊窗口類的過程
//
hWnd=CreateWindowEx(NULL,WND_CLS_NAME,
"這是我的第一個窗口",
WS_OVERLAPPEDWINDOW|WS_VISIBLE ,
CW_USEDEFAULT, 0,
400,400,
NULL,
NULL,
hinstance,
NULL );
if (!hWnd)
return 0;
ShowWindow(hWnd, ncmdshow); //本篇只添加這一句
return 0; //這里設置一個斷點,調試看看
}
一點說明:這里的ncmdshow就是WinMain主函數中的傳入參數ncmdshow。當我們通過雙擊*.exe文件來執行程序時,ncmdshow里的值就會是SW_SHOWNORMAL。
四 調試看看
太棒啦!我終于可以親眼所見我的窗口了!
好,讓我們來調試一下吧!看看程序運行后會發生什么。(如果不這樣調試而只是運行它,那么還來不及等你看清窗口,程序就會馬上結束的。這不用我說明原因吧!)
請在最后一個return 0處設置一個斷點(光標放在return 0處,按F9)。然后F5運行程序。
程序暫停在最后一個return 0處。
然后最小化桌面上所有其它無關窗口(包括VC6窗口)(這樣才能看到這個程序的窗口噢!)。
認真研究,我發現目前的代碼有以下幾個問題:
1. 窗口雖然顯示,但窗口不能自動被激活(即成為當前窗口)。只有最小化桌面上其它應用程序的窗口后,才能看到我們的這個窗口。(注意:在ShowWindow(hWnd, ncmdshow);函數中ncmdshow值我說過是SW_SHOWNORMAL值,應該會將窗口激活才對啊!這可是個大問題。)
2. 窗口無法進行調整大小,移動位置等的操作。
不過我并不想在這里解決這個問題(為什么?),那是因為這是一個大問題,它將引出Window程序的一個重要機制——消息處理機制。嘿嘿,到了關鍵一擊的時候了!我請你務必帶著這兩個問題看下篇吧!
第七篇、獲取消息及對消息缺省處理
白云小飛
一 重提上篇的問題:
還記得在上一篇的最后,經過調試后我們發現的問題嗎?
1. 窗口不能自動被激活(即成為當前窗口)。只有最小化其它應用程序的窗口后,才能看到我們的這個窗口。(注意:在ShowWindow(hWnd, ncmdshow);函數中ncmdshow值我說過是SW_SHOWNORMAL值,應該會將窗口激活才對啊!)
2. 無法進行調整窗口大小,移動窗口位置等等的所有對窗口的操作。
你認為是什么原因呢?
二 且聽我說:
1. ShowWindow(hWnd, SW_SHOWNORMAL)函數作用雖然是顯示窗口并激活窗口。它確實完成了窗口的顯示,但是,它并沒有去激活窗口,只是發了一系列與激活窗口有關的消息,這才是ShowWindow(…)函數所做的事。(那又由誰來激活呢?)
2. 真正激活窗口的任務是由DefWindowProc( )完成的。哦,也就是說我們得讓DefWindowProc( )收到ShowWindow( )產生的消息并根據消息來執行相應任務。
3. 但ShowWindow( )產生的消息只是加入到該線程的消息隊列中,并沒有自動地去調用WinProc( )的回調函數對消息處理。因此,DefWindowProc( )也就沒被調用,自然地就沒法激活窗口了。(請邊看代碼邊分析我的這段話吧!呵呵,你得有點想像力了!)
4. 哦,這不由地讓我想起前面的創建窗口函數CreateWindowEx( ) 。這兩個函數在執行中都會產生若干的消息。但CreateWindowEx會自動地調用了WinProc( ) 函數,而ShowWindow( )沒有這樣。
5. 同理,我們在窗口中的各種操作(調整大小、移動窗口等等),雖然會產生各種消息,但操作系統也只是把它們排入我們線程的消息隊列中,并不自動去調用 Proc,所以同樣的理由,DefWindowProc也沒有去處理我們的消息了,窗口也就沒法完成如調整大小,移動等等的操作了(你要知道:這些操作的實現都是由DefWindowProc( )完成的)。(還有,由于消息因為沒有得到處理,就會一直留在消息隊列中,這樣,你的線程消息隊列中的消息將會越積越多噢!)
看來,上述的兩個問題都源于同一個原因:那就是對窗口操作產生的消息只是排入消息隊列中,操作系統并沒有自動地調用WinProc回調函數來處理我們的窗口消息。
哦!那我們只有自力更生了。
我猜你一定迫切想知道如何解決吧!
三 介紹三個函數給你認識:
我們到了一個Window窗口程序框架最關鍵的部分了——消息處理機制。
消息隊列中的本窗口大量消息并不會被自動取出,也沒有自動地調用WinProc函數對消息加以處理,但是,Window系統提供了三個API函數給我們,讓我們自己去完成這件事。看吧!
GetMessage( …);
TranslateMessage(…);
DispatchMessage(…);
下面就讓我分別對這三個函數解釋解釋。
1 GetMessage( …)
原型如下:
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd ,
UINT wMsgFilterMin,
UINT wMsgFilterMax);
功能:這個API函數用來從消息隊列中“摘取”一個消息信息放到lpMsg所指的變量里。(注:如果所取窗口的消息隊列中沒有消息,則程序會暫停在GetMessage(…) 函數里,不會返回。)
參數及返回值:
LPMSG lpMsg:是傳出參數。消息結構MSG的指針。如果該函數執行成功,則從消息隊列中“摘”取的一個消息信息會放入lpMsg所指的MSG結構變量中。
在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等等消息標識。
hwnd中是這個消息所在的窗口句柄。
好了,其它成員變量我就暫時省略不說。
HWND hWnd:傳入參數。你要獲取你程序中哪個窗口的消息,那就把相應的窗口句柄代入其中。
UINT wMsgFilterMin,UINT wMsgFilterMax:這兩個參數我就不介紹。你只要用0值代入就可了。
返回值:如果取的是WM_QUIT消息,則返回值為0,如果取的是其它消息,返回值為非0值。WM_QUIT消息是退出程序的消息。當我們想讓程序退出時,我們就可以發一個WM_QUIT消息,讓GetMessage返回0值。
2 TranslateMessage(…)
原型:BOOL TranslateMessage( CONST MSG *lpMsg);
功能:對GetMessage取得的MSG消息結構中的信息進行必要的預處理(你可不用管它做了什么。)。
參數:
CONST MSG *lpMsg:它是用來對你取的消息結構MSG變量進行必要的預處理。GetMessage函數取得的消息,要經過TranslateMessage處理一下,然后才可以傳給DispatchMessage函數,因此,TranslateMessage必放在GetMessage與DispatchMessage之間。
返回值:它雖然有一個返回值,我們總是忽略它,所以我也就不說了。
3 DispatchMessage(…)
原型: LONG DispatchMessage( CONST MSG *lpMsg);
功能:用來完成調用WinPro回調函數并把由GetMessage取得的消息結構MSG變量中的信息傳遞給WinPro回調函數:(原型如下)
參數及返回值:
MSG *lpMsg :傳入參數,MSG消息結構體類型指針,指向你已經取出的消息結構體變量。
返回值:同上,我們總是忽略它。
四 Come on,行動起來吧!
看我添加的代碼(粗體部分):
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;
//…… 這里省略了前面所述的注冊窗口類的過程
//
hWnd=CreateWindowEx(NULL,WND_CLS_NAME,
"這是我的第一個窗口",
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)) //獲取一個消息,成功后會放在msg中。
{
TranslateMessage(&msg); //消息進行必要的處理轉換。
DispatchMessage(&msg); //調用WinProc,將msg的各項信息傳遞給WinProc
}
return 0;
}
看清楚了:這是一個消息循環。只有當GetMessage(…)收到一個WM_QUIT時返回0值,程序才會退出。
五 程序流程分析(粗體部分)
1. 當用戶進行如調整窗口大小,移動窗口位置等等的操作時。會產生不同的消息。(當然包括了ShowWindow( )函數產生的若干個消息。
2. Window系統會將這些消息排入本線程的消息隊列中。(在操作系統中完成)
3. 只要該窗口消息隊列中有消息,我們的GetMessage(&msg,NULL,0,0)就會從消息隊列中摘取一個消息的信息,填入msg結構體變量中,如果不是WM_QUIT則返回非零值,就執行循環體。(注意:如果消息隊列中沒有任何消息可取時,則程序會停在GetMessage函數里,直到消息隊列中有了消息,GetMessage函數才會取一個消息信息并返回。)
4. 用TranslateMessage(&msg)對msg中的數據進行預處理(你先不必知道它具體做了什么,但不要忘記這個函數。)。
5. 然后是調用DespatchMessage(&msg),這個函數里會調用WinProc,并將msg中的數據通過WinProc的參數傳遞給WinProc;
6. 程序轉入執行WinProc回調函數體內的代碼。
7. 看代碼處,WinProc此時只有一句 return DefWindowProc(hwnd, msg, wparam, lparam);這里,我們只是將WinProc傳入的參數原樣地傳給了API函數DefWindowProc。所有的消息都讓DefWindowProc進行缺省默認的處理。(你不用理會DefWindowProc都做了些什么。)
8. DefWindowProc完成一個消息處理后,返回消息處理的結果。
9. 我們的WinProc也原樣地將DefWindowProc返回值返回。
10. WinProc執行完成后,程序又返回到DispatchMessage(&msg)函數體內。(因為是在DispathMessage( )中調用WinProc的。)
11. 退出DispatchMessage(&msg);函數后程序又執行下一個循環。即
while(GetMessage(&msg, NULL, 0, 0))
又取下一個消息,進行下一個消息的處理。
12. 直到GetMessage “摘取”到了退出程序的消息WM_QUIT,返回零值,退出循環,結束程序。
(哦,整個流程是通過我們的程序與Window系統相互協作來完成的。你可要多加理解羅!)
六 調試這個程序
不設任何斷點,按F5運行程序,
看來一切正常。可以移動窗口;可以調整窗口大小;可以最大化最小化;總之可以進行窗口的基本的操作了。(這些動作都是由DefWindowProc來完成的噢!)
哈!我們可是漸入佳境啦!這就是Window系統消息處理機制帶給我們的成果。
不過……只是……有一個問題。
你有沒有注意到,點擊窗口右上角的關閉按鈕時,窗口是關閉了,但程序并沒有退出(看來點擊關閉按鈕時并沒有產生WM_QUIT的消息。)。
你只能點擊VC菜單的:Debug->Stop Debugging來退出程序了。
(欲知此為如何,請聽下回分解!)
第八篇、關閉窗口的同時退出程序
白云小飛
還記得上篇中最后說到一個問題嗎?當我們關閉程序窗口時,窗口確實是關閉了,可是程序并沒有退出啊。為什么呢???
一. 理解程序的退出條件:
首先,我們要先明白程序退出的條件,看上篇中的這段代碼:
while(GetMessage(&msg, NULL, 0, 0)) //獲取一個消息,成功后會放在msg中。
{
TranslateMessage(&msg); //消息進行必要的預處理轉換。
DispatchMessage(&msg); //調用WinProc回調函數,將msg傳遞給WinProc函數
}
如果程序一直在這個消息循環中,程序就沒能退出。只有當GetMessage收到一個WM_QUIT的消息,則返回值才會為零,退出循環,程序得以結束。(這個道理應該好理解吧?)
二. 點關閉按鈕時,發生了什么
當我們點窗口右上角的關閉按鈕時,到底發生了什么事呢?(請邊看源代碼,邊體會下面的分析噢!)
第一. 它并沒有(或最終沒有導致)發出WM_QUIT的消息。因此GetMessage函數不會收到WM_QUIT消息,就沒法跳出循環了。(那么又產生了什么消息呢?)
第二. 點關閉按鈕時,產生WM_CLOSE的消息。GetMessage會收到WM_CLOSE消息的MSG結構信息。
第三. 按前篇所述的消息處理流程可知:DespatchMessage會調用WinProc回調函數,并把WM_CLOSE消息的相關信息傳遞給WinProc函數參數中。
第四. 現在我們的WinProc里只有一句:
LRESULT CALLBACK WinProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
//這里可以添加你的消息處理代碼
return DefWindowProc(hwnd, msg, wparam, lparam);
}
它將WM_CLOSE繼續傳遞給缺省窗口過程函數DefWindowProc。
第五. 在DefWindowProc函數里,判斷是WM_CLOSE消息后,就會對參數hwnd所代表的窗口進行銷毀。(看吧,銷毀窗口的事也是由DefWindowProc來完成了。)
第六. 成功銷毀窗口后,DefWindowProc里接著還會發一個WM_DESTROY的消息到消息隊列中(表示說窗口已經被銷毀了)。然后DefWindowProc函數才結束。
第七. 回到我們的消息循環的GetMessage函數。這個函數又會獲得WM_DESTROY消息的信息,開始了下一個消息處理過程。
第八. 這個WM_DESTROY可在WinProc函數中由我們處理。但在WinProc函數體的代碼中我們沒有自己去處理它。仍然是讓DefWindowProc去處理。
第九. 然而,DefWindowProc只是簡單地把它給“扔掉”了。
第十. 整個點窗口右上角的關閉按鈕作所的所有動作就這樣完成了。
你看,上述中,上述程序始終沒有產生WM_QUIT的消息,所以窗口確實是銷毀了,但程序并沒有退出這個消息處理循環。
哦,怪不得我們的程序沒法結束了。(那該怎么辦呢?)
三. 如何使程序結束。
退出程序的三點說明:
1. 我們希望是通過單擊這個窗口右上角的關閉按鈕時來退出程序。
2. 應該在窗口成功銷毀后,才讓程序退出。
3. 只要讓程序產生一個WM_QUIT消息,就可以退出循環而結束程序。
終上所述,程序應在收到WM_DESTROY消息后才能退出程序。因為WM_DESTROY消息表示窗口已經銷毀。
那么我們又如何才能產生一個WM_QUIT的消息呢?用下面這個--API函數:
PostQuitMessage(0);
參數代入0值就可。它將產生一個WM_QUIT消息。WM_QUIT消息最終會被GetMessage函數“摘取”到并返回0值。從而退出循環,結束程序。看我實現代碼:
LRESULT CALLBACK WinProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
switch (msg) //msg中保存的就是正要處理的消息
{
case WM_DESTROY: //這是我們自行處理的第一個消息
{
PostQuitMessage(0); //發出一個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;
//…… 這里省略了前面所述的注冊窗口類的過程
//
hWnd=CreateWindowEx(NULL,WND_CLS_NAME,
"這是我的第一個窗口",
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函數參數中的UINT msg就是程序傳遞進來的消息標識。我們只要判斷msg是否為WM_DESTROY消息,如果是就發一個WM_QUIT消息。
補充說明一點:
WinProc函數參數中有一個msg變量,而WinMain函數中也定義了一個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;
你看,它是一個結構體。而WinProc參數中的msg是一個UINT類型,它其實只是WinProc函數里的msg結構成員message的值。
你看看DispatchMessage(&msg);是如何傳遞這個msg給WinProc的:
它在調用WinProc時,會——
將msg.hwnd值傳遞給WinProc參數中的HWND hwnd;
將msg.message值傳遞給WinProc參數中的UINT msg;
將msg.wParam傳遞給WinProc參數中的WPARAM wParam;
將msg.lParam值傳遞給WinProc參數中的LPARAM lParam。
整個程序實現退出的流程還是讓你自行去分析啦!應該不會太難吧!?
至此,終于完成了這個SDK程序的基本框架了!(哈,真值得我們去舉杯慶祝啦!不過,我的任務還沒完啊!還有許多要解決的問題等著我噢!)。
第九篇、窗口標題欄上顯示自定義圖標(手動編輯代碼)
白云小飛
本篇想通過手動直接編輯代碼的方式(而不是可視化的方式)來操作使用自定義的圖標。(以在窗口標題欄上顯示自定義圖標為例)
通過本篇,你將知道如何使用圖標資源及實質,并有助于你理解在可視化方式編輯使用圖標資源過程中的代碼實質。
另外,可以觸類旁通,明白VC中的Window應用程序對各種類似資源(如光標資源、位圖資源、聲音資源等)操作的一般機制。
一 開始我們的思考:程序所要用到的各類圖像、聲音等資源應放在什么地方為好?
一個程序可能需要使用各種圖像、聲音。那么,你認為應把這些東西放在什么地方呢?
一種很顯然的做法:就是讓這些圖形、圖像、聲音仍以文件形式(如.bmp .jpg .ico .cur .wav等等擴展名的文件,)保存在磁盤中,不包含在應用程序(.exe)中。當我的.exe程序執行時需要這些文件時再把它們從磁盤中加載到內存里并對它們操作。
如果你是想寫一個看圖軟件或播放音樂之類的軟件,這當然是一種最佳的做法啦。但是,有時候你的程序可能要顯示一個代表你程序的圖像等等的情況。由于前述的方法中,圖形、圖像、聲音是以獨立的文件形式,所以軟件的使用者是很容易就改動及刪除它們的,唉!我可不希望這樣啊!
那么,還有什么辦法呢?
還有一種可以想到的做法:就是讓這些圖形、圖像、聲音等數據在編譯時就把它們編譯進.exe文件中。
顯然,由于程序要用到的各種圖像、聲音等的數據已經包含在.exe里面了,這樣,軟件使用者只要擁有這個.exe文件就可以。同時使用者要想改動程序所用的這些圖形、圖像、聲音也就不是那么容易。
啊!exe程序文件里居然也能存儲各種類型圖像、聲音!嗯,這可是Windows的一個設計目標:可執行程序里不僅只含有程序代碼,還可以存儲各種圖像、聲音等的數據呢!也可能第一次讓你有了這個念頭!但不管怎么說,程序是可以這樣設計的!
好了,那么又如何創建一個包含有圖標資源并使用這個圖標的.exe文件呢?
二 圖像、聲音資源操作的一般步驟。
1 顯然,你得先創建一個自己想要的圖標(提示:這里我以圖標為例)
但具體應如何做呢?
這個問題好解決也好理解:我們只要用一個能編輯圖標的圖形圖像軟件就可以啦。制做完圖標后,把它們以.ico文件的形式(.ico 是圖標文件的擴展名)保存在磁盤的某個文件夾中。
當然啦,VC本身也提供了一個圖標編輯器。
2 然后你要給這個圖標定義一個字符串ID或者整數ID
為什么要給它定義一個字符串ID或整數ID呢?
那是因為這個圖標是要被編譯鏈接進可執行程序中(即.exe文件,當然也可能是.dll文件),這樣程序就不能通過文件名來訪問該圖標了。
所以,我們就得給這個圖標定義一個字符串ID或整數ID了,用以代表這個圖標。(你可以選擇使用字符串ID,也可以使用整數ID。)
這個定義是寫在一個叫資源腳本文件(擴展名為 .rc)里的。
當然,還要記住把.rc文件加入工程。
3 使用圖標第一步:通過ID號加載圖標
請先想想前面兩部做了什么?前面兩步產生了一個圖標并定義了一個圖標ID。這樣我們的程序就可以使用這個圖標了:程序首先通過以下這個API函數:
HICON LoadIcon ( HINSTANCE hInstance, LPCSTR lpIconName);
來加載圖標。
暫略第一個參數,先來說一下第二個參數LPCSTR lpIconName。該參數是代表要加載圖標的字符串ID。
加載成功后,返回系統分配給該圖標的一個句柄值(類型是HICON)。
(該函數具體的使用,后面還會有介紹。)
4 加載后,程序都是通過圖標句柄來操作該圖標
有了圖標句柄值,就可以通過這個值來操作相應的圖標了。(還記得“句柄”這個概念及作用嗎?Window系統總是通過句柄來操作已加載到內存的某個對象。)
5 最后,我們的程序都編好后,只要把資源與程序代碼一起編譯到.exe文件中
VC會自動用一個專門的資源編譯器會把.rc文件及相關的資源文件(*.ico、*.bmp、*.wav等)編譯生成一個擴展名為.res的二進制中間文件。然后再用連接程序與程序代碼的二進制中間文件一同連接成可執行程序了。
這看上去比較復雜。不用害怕啊!其實你只要按原來的方式編譯連接就行了。
到此,最好重新瀏覽一下上述1~5的步驟,并多加體會這個操作流程。
現在頭腦有了概念沒有啊?有,那就開始動手吧!(注意:本篇中我是用手動編輯代碼的方式來完成的。)
三 具體實現范例:本例為圖標定義一個字符串ID(注意:不是整數ID)
任務:在窗口標題欄上顯示一個自定義的圖標。
(提醒:務必請先對前一篇所完成工程做一個備份。因為以后我還要從上一篇的工程狀態開始新的內容呢!)
1 首先我們要有一個自定義圖標(文件名以myicon.ico為例)
在我們的工程文件夾(即D:\MyApp)下創建一個myResLib文件夾(用以集中存放各種資源文件)。
然后,你可用一個.ico編輯器創建一個圖標文件myicon.ico,把它放在D:\MyApp\myResLib文件夾下。
不過,也可能你并不懂得使用任何一款.ico編輯器,那也沒關系,隨便找一個.ico(16*16或32*32的)文件(這不應成為問題吧?)。把它復制到D:\MyApp\myResLib文件夾下,并改名為myicon.ico。
好了,現在我們有一個圖標文件,請你記住它的路徑和名稱。
2 用記事本程序創建一個資源腳本文件(文件名為myRes.rc),并在這個文件中為myicon.ico定義一個字符串ID(本例為:IDI_MYICON)作為這個資源的名稱。
之所以用記事本來創建而不用VC本身來創建,是因為我不希望讓VC生成一些無關碼,以便于解說和理解。
另外一點要提醒:如果你的工程里已經包含有一個.rc的資源腳本文件,那么在下面的操作會出現一些不同的情況。但如果從第一篇就按我所述的來操作,本工程是沒有.rc文件的。
操作:
=>開始->程序->附件->記事本
=>在記事本中輸入如下一行:
IDI_MYICON ICON DISCARDABLE " myResLib\ myicon.ico"
看,在ICON DISCARDABLE的左邊寫上ID名,右邊寫上圖標所在的相對路徑字符串。這樣也就將myResLib\myicon.ico圖標定義ID號為IDI_MYICON,并且這樣定義的ID就是字符串ID。(等一下你就會知道如何使用字符串ID了!)
=>點擊“記事本”程序菜單“文件”->另存為->在“保存在”框中選D: ->雙擊打開MyApp文件夾->在“保存類型”框中選“所有文件(*.*)->在“文件名”框中輸入:myRes.rc->點“保存”(操作完成)
現在我們已經為myicon.ico定義了一個字符串ID:IDI_MYICON 。接下來,要干什么呢?哦,你要知道,這樣方式創建腳本文件是不會自動加到我們的工程中,所以你要記得自己把myRes.rc加到你的工程中。
3 將myRec.rc加入到工程中。
=>在工作區(Workspace)視圖中選FileView選項卡->在其中右擊Source Files -> 單擊“添加文件到目錄… ->雙擊“myRes.rc”
本操作的目的:將資源腳本文件myRes.rc加入到該工程中。下面我們就可以通過代碼來訪問這個圖標了。
4 要使用圖標資源,得先用LoadIcon函數加載圖標資源。
LoadIcon原型:
HICON LoadIcon ( HINSTANCE hInstance, LPCSTR lpIconName);
參數:
HINSTANCE hInstance:要加載圖標是存在那個應用程序里,就代入這個應用程序實例的句柄。
LPCSTAR lpIconName:是你要加載的圖標的字符串ID,就是我們在第3步中定義的。
返回值:加載成功后會返回一個圖標句柄值,其類型是HICON。加載后,我們就可以通過這個句柄值來操作對應圖標了。
下面就是原來在窗口類結構體中設置窗口標題欄圖標的代碼(應該還記得下面一行代碼在哪里吧!):
wndclass.hIconSm = LoadIcon(NULL, MAKEINTRESOURCE( IDI_APPLICATION ) ) ;
你先別理上面LoadIcon中的參數使用,現在我們把它改成如下:
wndclass.hIconSm = LoadIcon(hinstance,” IDI_MYICON”) ;
其中,hinstance就是本應用程序實例的句柄。”IDI_MYICON”就是我們要加載的圖標。
現在,我們把WinMain主函數里的代碼修改如下:
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就行了。
看到了沒,程序的窗口已經變成我們想要的圖標了。
四 繼續上例:將圖標資源由使用字符串ID改為使用整數ID
前面我反復地說到字符串ID和整數ID。你首先要明白,所有資源的ID號可以定義為字符串的,也可以定義為整數型的。我在前例中使用的是一個字符串ID的例子,現在我又要改為使用整數ID。
1 創建一個名為Resource.h頭文件,內容如下:
#define IDI_MYICON 100
將IDI_MYICON 定義為一個整數型符號常量。這個數值應是以1以上一個數值。
(注意:頭文件最后要有一空行,也是是說 #define IDI_MYICON 100 后要按一個回車鍵。)
2 修改myRes.rc文件。
//myRes.rc
#include “Resource.h” //包含頭文件resource.h
IDI_MYICON ICON DISCARDABLE " myResLib\ myicon.ico"
經過上述兩處的添加后,IDI_MYICON就不在是字符串ID了,而是整數ID。因為Resource.h已經將IDI_MYICON定義為一個整數的符號常量。
3 在WinMain( )函數所在的源文件中添加包含Resources.h頭文件,并修改LoadIcon()函數。
// MyAppMain.cpp :主函數及回調函數
#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) ;
//第二個參數由” IDI_ICON1”字符串改成(LPCTSTR) IDI_ICON1
wndclass.lpszMenuName =NULL;
wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH);
wndclass.lpszClassName =WND_CLS_NAME;
}
分析:
凡是要使用整數ID資源的文件都要包含Resource.h 頭文件,這是因為整數ID是由Resource.h定義的。(這個好理解!)
修改LoadIcon()調用。因為LoadIcon的第二個參數接受的是字符串ID(就是LPCTSTR指針),所以,我們得把整數ID轉化成LPCTSTR。(LPCTSTR) IDI_MYICON目的就是將IDI_MYICON強制轉化成LPCTSTR。
我們可是辛辛苦苦地把字符串ID改成整數ID,現在調用LoadIcon()時又要將IDI_MYICON 強制類型轉化成LPCTSTR類型。嘻嘻,真有意思,好似我們在瞎折騰似的,到頭來又要回到了原來的狀態。但不管怎么說,這也是一種方式噢!
好了,現在你可以編譯運行試試了。哈哈,也是相同的作用喲!
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成