Win32學習筆記 第三章 HelloWin (轉)

worldblog發表於2007-12-14
Win32學習筆記 第三章 HelloWin (轉)[@more@]

學習筆記

作者: 姜學哲(tosail0@163.net">netsail0@163.net)

教材: 設計(第五版)北京大學出版社
 [美]Charles Petzold 著
 北京博彥科技發展有限公司 譯  ¥:160
參考資料:
 Windows 應用程式設計原理_方法_技術(因為是PDF格式的EBOOK,作者等不詳)
 新編Windows 參考大全  電子工業出版社  ¥:98
 C++ Primer(第三版)中國電力出版社 Stanley B.Lippman & Josee Lajo著 潘愛民 張麗 譯 ¥:128
 TURBO C實用大全 徐金梧 楊德斌 徐科 編 ¥:42

環境:  Pro + Internet Explorer 6.0 + 8.1 + Visual C++ 6.0

圖們江程式編制小組()版權所有,轉載請說明出處
--------------------------------------------------------------------
【第三章 HelloWin】

4月16日是值得紀念的日子。主頁第十六天,我們的網站訪問量是19500。我們什麼都沒有做,連搜擎都沒有提交。所以感到很意外。怎麼會有這麼多人訪問的,訪客是怎麼知道我們的網站的呢?真是匪夷所思。

經過了前兩章的準備活動後要正式看WIN32程式碼了。在這之前我假設您已經具有C語言的知識。如果您沒有C方面的知識,那麼抓緊時間學吧。第三章的重點就是第三十九頁開始的HelloWin了。我們的全部任務就是看懂它。可能是我特別低能+沒耐心,幾個月前第一次看到第三章的時候我選擇了放棄,那時候覺得我這輩子都看不懂它,真是慚愧。

對我來講它實在是太難了。因為很多新知識點對我來講都是空白。頭腦中一點概念都沒有,真的是十足的“一頭霧水”。因為感到WIN32太難,我又轉向了DOS下的C,也就是TC2環境下的,學了一會兒,又覺得TC2也很難,又回到了WIN32,我也知道這種學習態度是不好的,雖然我這次從TC2轉到WIN32的原因又是避重就輕,但是這次一定要學好WIN32,這再也不能像前幾次那樣了。

其實現在想起來,我缺的就是耐心,如果當時多看幾遍就能看懂的,白白浪費了那麼多時間。所以,如果您覺得這個程式太難,您一定要堅持多讀幾遍。過程是比較枯燥的。從一開始您就會碰到一些困難,比如Win32專用的術語。有些術語的含義簡單,卻很不好解釋。

我們需要一段時間來適應。開頭處您會碰到“視窗過程”,“視窗類”等術語。因為頭腦中完全沒有概念,所以當您看到這些術語的時候可能產生對人生的絕望。請珍稀您的生命,堅強一點,對付這些術語的方法是硬著頭皮往下讀。可以先不管具體的意思。只要在腦子裡留點印象就可以了。等您看懂了HelloWin程式碼後再回過頭來重讀 N 遍,您會有意外的收穫。

我是農民出身嘛!再加上智商也不是很高,讀書只能用這種笨辦法了。這種事要靠天份的。可能您看一遍就懂了也說不定。

當您讀完了第三章後可能還有很多疑點。其中的相當一部分將在第四章中解釋。所以當您看到了一個疑點,書中又沒有詳細說明時可以先跳過那段。學過後面幾章後再回過頭來看一看。這樣做絕對不是浪費時間。

為了學好WIN32我曾經想過放棄"帝國時代"和"星際爭霸",不不!!我死也不放棄!!我的最愛啊!可能隨著學習的深入這些遊戲會漸漸離我遠去。對了,不知道星際什麼時候出2代啊?

在前兩章示例程式使用了MessageBox()來向輸出文字。MessageBox()建立一個訊息框視窗。在Windows中"視窗"一詞有確切的含義。一個視窗就是螢幕上的一個矩形區域。它接收使用者的輸入,並以文字或圖形的格式顯示輸出內容。

MessageBox()建立一個訊息框視窗,但這只是一個功能有限的特殊視窗。這個視窗有一個帶關閉按鈕的標題欄、一個可選的圖示、一行或多行文字,以及最多4個按鈕。

MessageBox()雖然有用,但我們不能在訊息框中顯示圖形,也不能在訊息框中新增選單。想要新增這些東東就需要建立自己的視窗,現在就開始。

書上說建立視窗很簡單,只需要CreateWindow()即可。實際上Windows已經做好了大部分事情,我們所要做的只是記住API函式的名字和功能,然後到了實際的開發過程中照搬就可以了。這聽起來似乎很簡單,但是事實上還有很多工作需要程式設計師來做。

零件雖然做好了,但是組裝起來還是得費一番功夫的。另外如果您的英語水平過得去,將對學習有很大的幫助。的MSDN是很好的東東。可惜我不會看啊。

進行Windows程式設計,實際上是進行一種面向的程式設計()。不要跟我說C不支援物件導向,我什麼都不知道。

桌面上最明顯的視窗就是應用程式視窗。這些視窗含有顯示程式名稱的標題欄,選單,工具欄,捲軸。裝飾對話方塊表面的還有各式各樣的按鈕,單選框,核取方塊,列表框,捲軸和文字輸入區域。這些都是視窗。更確切地說這些都稱為"子視窗","視窗","子視窗控制元件"。

其實按鈕也是一種視窗。比如Windows的“開始”選單按鈕,它就是一個視窗,叫做“按鈕視窗”。如果在不使用Windows API的情況下以C語言編寫捲軸最快也得兩個星期,而且寫出來的捲軸一定是很難看的那種。對於像我們這樣的菜鳥來說自己寫捲軸函式並不是令人Happy的事情。非常Lucky的是Windows已經提供了一大串現成的捲軸函式。

當我們移動滑鼠的時候,當我們敲鍵盤的時候,當我們單擊滑鼠左右鍵的時候…當…當…當…,會產生一種訊號。這種訊號代表某種操作。我們可以用一種訊號表示滑鼠的移動,用另一種訊號表示滑鼠左鍵的按下等等等等。Windows把這種訊號稱為“訊息”。

當使用者點選一個視窗時這個視窗就會收到一個“訊息”。視窗以“訊息”形式接收視窗的輸入,視窗也用訊息與其它視窗通訊。對於訊息的理解將是學習編寫Windows程式必須逾越的障礙之一。很重要啊!雖然訊息這個東東並不是很難理解,但是確實是很陌生的東東。

按一個鍵盤會產生一個訊息,移動滑鼠產生一個訊息,點選滑鼠左鍵產生訊息……總之Windows是靠訊息來的。到底什麼是訊息呢?等您看懂HelloWin程式之後大概也會明白。我只能說,多看幾遍吧!!

我們可以用滑鼠拖動視窗邊框來改變視窗大小。程式會改變視窗中的內容來響應這種變化。重新調整視窗尺寸的工作是Windows處理的。應用程式沒有這種負擔。應用程式所要做的只是改變視窗的內容來響應這種變化。

應用程式是如何知道使用者改變了視窗的尺寸的呢?有很多程式設計師已經習慣了字元程式。在字元模式下操作沒有將此類訊息通知給應用程式的機制。問題的關鍵在於理解Windows使用的體系結構。當使用者改變視窗尺寸時Windows給程式傳送一條訊息,指出視窗的新尺寸。然後程式調整視窗中的內容,以反映尺寸的變化。

“Windows給程式傳送訊息”。這對於有過字元模式下程式設計的一些人來說有點不好理解。怎麼會給程式傳送訊息呢?其實“Windows給程式傳送訊息”是指Windows呼叫程式中的一個函式,該函式的引數被設計為接收Windows和使用者發出的訊息。這種位於應用程式中的,被系統呼叫的函式被稱為“視窗過程”。

我們對於應用程式呼叫作業系統是很好理解的。比如說在DOS下應用程式經常呼叫DOS中斷。但是對於作業系統呼叫應用程式中的函式可能很不習慣。而這正是Windows物件導向體系結構的基礎。

程式建立的每一個視窗都有一個相關的視窗過程。視窗過程是一個函式。這個函式可以在程式中,也可以在動態連結庫中。Windows透過呼叫視窗過程來處理視窗傳送的訊息。視窗過程根據此訊息進行處理,然後將控制返回給Windows。

視窗通常是在視窗類的基礎上建立的。在閱讀HelloWin的時候您會了解到什麼是視窗類。視窗類中指定了處理該視窗的訊息的視窗過程。多個視窗能夠基於同一個視窗類,並且使用同一個視窗過程。例如,所有Windows程式中的所有按鈕均基於同一個視窗類。我們所見過的所有的Windows按鈕都使用一個視窗過程。

在視窗尺寸改變或視窗表面需要重畫時由一種訊息通知視窗。向視窗傳送的訊息由該視窗的視窗過程函式處理。

Windows程式開始後,Windows為該程式建立一個“訊息隊例”。這個訊息佇列用來存放該程式建立的各種不同的視窗訊息。程式中有一小段程式碼,叫做“訊息迴圈”,用來從佇列中取出訊息,並且將它們傳送給相應的視窗過程。有些訊息直接傳送給視窗過程,不用放入訊息佇列中。

雖然我嘰嘰歪歪say了一堆,但是您可能還是不太明白。沒關係,接下來我們看第一個Win32程式。這是本章的重點,而且是整個Win32的真正開始。這個程式碼具有代表性,您最好是手工敲幾遍,這絕對有必要!這樣腦子會留下一些印象。千萬不要偷懶哦……

◎第三十九頁:

/*------------------------------------------------------------
  HELLOWIN.C -- Displays "Hello, !" in client area
  (c) Charles Petzold, 1998
  ------------------------------------------------------------*/

#include

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
  PSTR szCmdLine, int iCmdShow)
{
  static TCHAR szAppName[] = TEXT ("HelloWin") ;
  HWND  hwnd ;  //視窗控制程式碼
  MSG  msg ;  //訊息結構
  WNDCLASS  wndclass ;  //視窗類結構

  wndclass.style  = CS_HREDRAW | CS_VREDRAW ;
  wndclass.lpfnWndProc  = WndProc ;
  wndclass.cbClsExtra  = 0 ;
  wndclass.cbWndExtra  = 0 ;
  wndclass.hInstance  = hInstance ;
  wndclass.hIcon  = LoadIcon (NULL, IDI_APPLICATION) ;//載入圖示供程式使用
  wndclass.hCursor  = LoadCursor (NULL, IDC_ARROW) ;  //載入滑鼠指標供程式使用
  wndclass.hbrBackground = (HBRUSH) GetStock (WHITE_BRUSH) ;//獲取一個圖形物件,在這個例子中,是獲取繪製視窗背景的刷子
  wndclass.lpszMenuName  = NULL ;
  wndclass.lpszClassName = szAppName ;

  if (!RegisterClass (&wndclass))//為程式視窗註冊視窗類
  {
  MessageBox (NULL, TEXT ("This program requires !"),
  szAppName, MB_ICONERROR) ;
  return 0 ;
  }
  //根據視窗類建立一個視窗
  hwnd = CreateWindow (szAppName,  // window class name
  TEXT ("The Hello Program"), // window caption
  WS_OVERLAPPEDWINDOW,  // window style
  CW_USEDEFAULT,  // initial x position
  CW_USEDEFAULT,  // initial y position
  CW_USEDEFAULT,  // initial x size
  CW_USEDEFAULT,  // initial y size
  NULL,  // parent window handle
  NULL,  // window menu handle
  hInstance,  // program instance handle
  NULL) ;  // creation parameters
 
  ShowWindow (hwnd, iCmdShow) ;  //在螢幕上顯示視窗
  UpdateWindow (hwnd) ; //指示視窗重新整理自身
 
  while (GetMessage (&msg, NULL, 0, 0))  //從訊息佇列中獲取訊息
  {
  TranslateMessage (&msg) ;  //轉換某些鍵盤訊息
  DispatchMessage (&msg) ;  //將訊息傳送給視窗過程
  }
  return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  HDC  hdc ;
  PAINTSTRUCT ps ;
  RECT  rect ;
 
  switch (message)
  {
  case WM_CREATE:
  //一個
  PlaySound (TEXT ("HelloWin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
  return 0 ;
 
  case WM_PAINT:
  hdc = BeginPaint (hwnd, &ps) ; //開始視窗繪製
 
  GetClientRect (hwnd, &rect) ; //獲取視窗客戶區的尺寸
 
  DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect,
  DT_SINGLELINE | DT_CENTER | DT_VCENTER) ; //顯示文字串
 
  EndPaint (hwnd, &ps) ; //結束視窗繪製
  return 0 ;
 
  case WM_DESTROY:
  PostQuitMessage (0) ; //在訊息佇列中插入一條“退出”訊息
  return 0 ;
  }
  return DefWindowProc (hwnd, message, wParam, lParam);//執行預設的訊息處理
}

-----------------------------------------------------------------------------
整個程式由兩個函式組成。WinMain()是程式的入口。而WndProc()就是視窗過程。Windows系統會呼叫它。

WinMain()前面有WINAPI,這個東東是以前從來沒見過的。所以特地跑到論壇問了一下。有人說這是定義引數的呼叫順序的。到底是什麼意思我真的不明白。但是我知道在WINDEF.H中它的定義如下:

#define WINAPI __stdcall

在視窗過程函式前面有一個CALLBACK,他的定義如下:

#define CALLBACK __stdcall
 
視窗過程函式的型別是LRESULT,它的定義如下:

typedef long LRESULT;

但是請注意!千萬不要把

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

寫成:

long CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

雖然LRESULT的確是long型,但是在定義視窗過程函式時的格式不能隨便改。

WPARAM LPARAM 是 long

#define  PASCAL  pascal
#define  NEAR  near
#define  FAR  far

typedef  unsigned int  UINT;
typedef  unsigned char  BYTE;
typedef  unsigned short  ;
typedef  unsigned long  DWORD;
typedef  long  LONG;
typedef  char  *PSTR;
typedef  char NEAR  *NPSTR;
typedef  char FAR  *LPSTR
typedef  void  VOID;
typedef  int  *LPINT;
typedef  LONG  (PASCAL FAR* FROC)();

隨書光碟中有和HelloWin聲音檔案。把整個HelloWin複製到您的中,然後去掉只讀屬性。然後編譯執行一次程式。一定要去掉只讀屬性。

程式建立一個普通的應用程式視窗。在視窗客戶區的中央顯示"Hello, Windows98!"。程式的WinMain()部分具有代表性,在以後的很多程式中會重複出現N次,而且幾乎是原封不動的。您會發現各個程式間不同的是視窗過程函式部分。

HelloWin程式在視窗客戶區中央顯示文字串。客戶區就是程式自由繪圖並且向使用者交付可視輸出的視窗區域。HelloWin程式中有一大片白色區域,那就是客戶區。

和其它的程式一樣,HelloWin建立的視窗可以移動,可以改變視窗尺寸。右上角有“最大化”,“最小化”和“關閉”按鈕。無論使用者如何改變視窗尺寸,程式都會自動將"Hello, Winodws98!"文字串重新定位在客戶區域的中央。我將對實現上述功能的程式碼一一做介紹。

HelloWin呼叫了18個函式。這18個函式都是Windows API函式。如果您想了解這18個函式的細節,可以查閱Windows API參考手冊。我們絕對有必要買一本Windows API的參考資料。如果您有好的API參考手冊別忘了告訴我書名。如果您的英語水平很不錯,也可以去看MSDN。

程式中有很多大寫的識別符號。這些識別符號都是在Windows的標頭檔案中定義的。

CS_HREDRAW  DT_VCENTER  SND_FILENAME  CS_VREDRAW  IDC_ARROW  WM_CREATE
CW_USEDEFAULT  IDI_APPLICATION  WM_DESTROY  DT_CENTER  MB_ICONERROR 
WM_PAINT  DT_SINGLELINE SND_ASYNC  WS_OVERLAPPEDWINDOW

以上都是簡單的數值常量。識別符號的字首表示該常量所屬的類別。

CS --- 類風格選項
CW --- 建立視窗選項
DT --- 繪製文字選項
IDI---圖示ID號
IDC---游標ID號
MB ---訊息框選項
SND---聲音選項
WM ---視窗訊息
WS ---視窗風格

MSG -- 訊息結構
WNDCLASS -- 視窗類結構
PAINTSTRUCT -- 繪圖結構
RECT -- 矩形結構

最後還有三個大寫識別符號,用於不同型別的“控制程式碼”。

HINSTANCE --例項(程式自身)控制程式碼
HWND -- 視窗控制程式碼
HDC -- 裝置描述表控制程式碼

控制程式碼在Windows中使用非常頻繁。也是非常重要的一個概念。我們還將遇到圖示控制程式碼HICON、滑鼠指標控制程式碼HCURSOR、圖形刷控制程式碼HBRUSH。到底什麼是控制程式碼呢?

控制程式碼是一種新的資料型別。選單,視窗,圖示,,裝置,程式,點陣圖等都被稱為“物件”。也就是說選單是選單物件,視窗是視窗物件,記憶體是記憶體物件。對於這樣的稱呼您一定要習慣。控制程式碼可以代表一個物件。我們用控制程式碼引用一個物件。例如我們可以用裝置描述表控制程式碼引用一個裝置(比如顯示器)。當我們用一個裝置描述表控制程式碼引用顯示器時,這個控制程式碼代表的就是顯示器。我們透過這個控制程式碼使用顯示器。

■常用的控制程式碼型別

HANDLE------------------通用控制程式碼型別
HWND--------------------標識一個視窗物件
HDC---------------------標識一個裝置物件
HMENU-------------------標識一個選單物件
HICON-------------------標識一個圖示物件
HCURSOR-----------------標識一個游標物件
HBRUSH------------------標識一個刷子物件
HPEN--------------------標識一個筆物件
HFONT-------------------標識一個字型物件
HINSTANCE---------------標識一個應用程式模組的一個例項
HLOCAL------------------標識一個區域性記憶體物件
HGLOBAL-----------------標識一個全域性記憶體物件

也就是說HWND代表的是一個視窗物件,HDC代表的是一個裝置物件。這個裝置有可能是記憶體,也有可能是。控制程式碼是一個數,通常為32位數,以十六進位制形式表示。您明白我在說什麼嗎?不明白?看完這本書後可能就明白了,請堅持下去吧。

進入程式後第一個碰到的是一組變數定義。

static TCHAR szAppName[] = TEXT ("HelloWin") ;
HWND  hwnd ;  //視窗控制程式碼
MSG  msg ;  //訊息結構
WNDCLASS  wndclass ;  //視窗類結構

第二個變數是一個視窗控制程式碼。它代表一個視窗。那麼它代表的是哪個視窗呢?現在還沒有賦值啊,所以它哪個視窗都沒有代表。

當然,如果您高興第二個變數也可以定義成:

HWND aaa;  //或者:
HWND bbb;

和int, float等標準資料型別一樣,變數名可以隨便起。下面的兩個也是同樣道理:

MSG addf;
WNDCLASS sdakd221;

首先要定義視窗類,也就是給視窗類結構賦值。視窗類只是定義視窗大概的樣子。so,所有基於此視窗類建立的視窗物件都會有視窗類中給出的特點。下面我們一個一個地分析視窗類的每一個域。

wndclass.style = CS_HREDRAW | CS_VREDRAW;

上面的語句表示:每當視窗的水平方向尺寸(CS_HREDRAW)或者垂直方向尺寸(CS_VREDRAW)改變後,要完全重新整理視窗(重新整理就是在螢幕上重畫。我們所看到的所有圖形介面都是"畫"出來的。)。無論如何改變Hellowin的視窗尺寸我們都可以看到文字串“Hello Windows98!”仍然顯示在視窗的中央,這兩個識別符號確保了這一點。

wndclass.lpfnWndProc = WndProc;

這條語句將這個視窗類的視窗過程函式指定為WndProc()。以後凡是基於此視窗類建立的所有新視窗都會把WndProc()當作自己的視窗過程函式。這一條很重要哦,一定要記住!

wndclass.cbClsExtra = 0;

基於同一個視窗類建立的視窗物件的公共資料區大小。不太明白這是什麼意思,我是菜鳥嘛。看字面上的意思好像是說所有基於此視窗類建立的視窗物件都會擁有一個公共的記憶體區。我的理解正確嗎?有沒有高手教我啊?

wndclass.cbWndExtra = 0;

當前視窗物件私有的資料區大小。

wndclass.hInstance = hInstance; //當前程式物件例項控制程式碼

本程式的控制程式碼。也就是代表HelloWin程式的控制程式碼。如果讓我細細講來,可就難為我了。我還是個菜鳥啊!可能看完了整個書就會懂吧?這個是WinMain()的引數之一。

wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);

為所有基於此視窗類建立的視窗設定一個圖示。想要載入自己畫的圖示時這個引數應該被設定為程式的例項控制程式碼hInstance。如果想要獲取預先定義的圖示控制程式碼我們可以把LoadIcon()的第一個引數設定為NULL。當第一個引數為NULL時,第二個引數有以下選項:

IDI_APPLICATION 預設的應用程式圖示。您可以看看HelloWin的樣子,那個方框就是了。
IDI_ASTERISK 星號
IDI_EXCLAMATION 驚歎號
IDI_HAND 手形圖示
IDI_QUESTION 問號
IDI_WINLOGO Windows徽標

以上的識別符號可以在WINUSER.H中找到。函式返回代表該圖示物件的控制程式碼。

wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

LoadCursor()函式載入一個預先定義的游標,並返回該項游標的控制程式碼。該項控制程式碼被賦給WNDCLASS結構的hCursor域。當滑鼠經過"基於此視窗類建立的視窗"時,它變成一個小箭頭。不過因為之前的游標也是小箭頭,所以我們看不出游標的變化。下面是預定義的滑鼠指標識別符號:

IDC_APPSTARTING 標準箭頭及小沙漏
IDC_ARROW 標準箭頭
IDC_CROSS 十字交叉
IDC_HAND (Windows2000)手形
IDC_HELP 箭頭和問號
IDC_IBEAM 文字I形
IDC_ICON 空圖示
IDC_NO 斜槓圈
IDC_SIZE 四向箭頭
IDC_SIZEALL 四向箭頭
IDC_SIZENESW 指向東北和西南的雙向箭頭
IDC_SIZENS 指向南北的雙向箭頭
IDC_SIZENWSE 指向西北和東南的雙向箭頭
IDC_SIZEWE 指向東西的雙向箭頭
IDC_UPARROW 垂直箭頭
IDC_WAIT 沙漏

您可以把語句中的IDC_ARROW換成別的,然後再編譯執行一次。當您把滑鼠移到視窗上面時會看到不同的結果。試一試啊!!你可以先試一試IDC_CROSS。

wndclass.hCursor = LoadCursor(NULL, IDC_CROSS);

然後編譯執行一次。把滑鼠移到視窗物件上面,看看滑鼠有什麼變化?滑鼠是不是變成十字形了?之前的LoadIcon()也是同樣道理,您可以試著換一下第二個引數。看看有什麼變化。

wndclass.hbrBackground = GetStockObject(WHITE_BRUSH);

設定基於此視窗類建立的視窗物件的背景顏色。hbr代表“handle to a brush(刷子控制程式碼)”。刷子是圖形學上的術語。指用來填充一個區域的著色畫素模式。Windows有幾個標準刷子,也稱為備用(stock)刷子。上面所示的GetStockObject()呼叫將返回一個白色刷子的控制程式碼。視窗客戶區將完全為白色。這是一種及其普遍的做法。

wndclass.lpszMenuName = NULL;

指定視窗類選單。HelloWin沒有選單,所以這項為NULL。

wndclass.lpszClassName = szAppName;

給這個視窗類取名字。以後就用這個名字認它了。

if (!RegisterClass (&wndclass))//為程式視窗註冊視窗類
{
  MessageBox (NULL, TEXT ("This program requires Windows NT!"),
  szAppName, MB_ICONERROR) ;
  return 0 ;
}

建立一個視窗首先需要註冊一個視窗類。RegisterClass()的功能是註冊視窗類。但是註冊視窗類並不是每次都能成功。所以應該有一個查錯機制。

RegisterClass()註冊失敗後會返回0。所以當視窗類註冊失敗後if為真,接著執行MessageBox()。執行MessageBox()的結果是顯示一個訊息框,並顯示"This program requires Windows NT!"。然後退出整個程式。這段英文的大概意思是說"本程式需要Windows NT的支援!"。如果您使用的是Windows98,當您執行附書光碟中的HelloWin.exe時可能有機會看到這個訊息框。我的是,所以看不到啊。出現這個訊息框表明註冊視窗類失敗。

視窗類定義了視窗的一般特徵,可以使用同一視窗類建立許多不同的視窗。呼叫CreateWindow()建立視窗時指定有關視窗的更詳細的資訊。

為什麼一個視窗的所有特徵不能被一步到位指定呢?實際上以這種方式分開這些風格資訊是非常方便的。例如,所有的按鈕視窗都可以基於同樣的視窗類來建立。與這個視窗類相關的視窗過程位於Windows內部。所有的按鈕都是以同樣的方式工作的。但是每一個按鈕都有不同的尺寸,不同的螢幕位置,以及不同的文字串。這些不同的特徵是CreateWindow()定義的一部分,而不是視窗類定義的。

hwnd = CreateWindow (szAppName, // 指定一個視窗類,基於該視窗類建立視窗
  TEXT ("The Hello Program"), // 這個字串會出現在標題欄中
  WS_OVERLAPPEDWINDOW,  // 本視窗風格
  CW_USEDEFAULT,  // 視窗的X座標,更準確地說是視窗左上角的X座標
  CW_USEDEFAULT,  // 視窗的Y座標
  CW_USEDEFAULT,  // 視窗的寬度
  CW_USEDEFAULT,  // 視窗的高度
  NULL,  // 視窗物件的父視窗控制程式碼
  NULL,  // 視窗物件的選單控制程式碼或者子視窗編號
  hInstance,  // 當前程式的例項控制程式碼
  NULL) ;  // 視窗物件的引數指標控制程式碼(我也不知道這是什麼意思)

我們要關注的是第三個引數WS_OVERLAPPEDWINDOW

WINUSER.H中對WS_OVERLAPPDWINDOW定義如下。

#define WS_OVERLAPPDWINDOW (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)

您想搞清楚這一長串到底是什麼東東嗎?很…很…很Easy!這是定義該視窗物件的風格。從上面的語句裡可以看出第三行的真正的內容是:

WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX

問題是書上沒有說明上面六個WS_XXXX都是幹什麼用的,所以只能由我們自己想辦法。

請將第三個引數WS_OVERLAPPDWINDOW換成:

WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX

接下來要做的就是把六個WS_XXXX一個一個地去掉,每去掉一次就重新編譯一次,看看視窗會變成什麼樣子。首先去掉WS_SYSMENU,也就是說第三個引數變成了:

WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX

然後編譯一次,您會Lucky地看到您的HelloWin.exe已經是面目全非了,視窗右上角的三個按鈕不見了,左上角的小圖示也不見了,用滑鼠右擊標題欄時不會出現系統選單。由此我們已經知道WS_SYSMENU的含義了。接下來讓我們看看WS_MINIMIZEBOX,WS_MAXIMIZEBOX是幹什麼用的,改成如下所示後,再編譯

WS_OVERLAPPED |WS_CAPTION | WS_SYSMENU | WS_THICKFRAME

您會High地看到右上角只有一個按鈕了,最大化,最小化兩個按鈕消失於時間和空間的盡頭……以此類推,您可以一個一個的試。

第四,第五個引數分別是視窗物件在螢幕上的x,y座標,準確地說是視窗物件的左上角的座標。

第六,第七個引數分別是視窗物件的寬度和高度。

第八個引數父視窗控制程式碼。您不知道父視窗是什麼東東?好,請開啟記事本程式,按CTRL+O,會發現出現一個視窗,這個視窗總是在記事本上面 ,這個視窗是記事本的子視窗,而記事本就是父視窗。OK?還不知道?………………HelloWin沒有父視窗,也就沒有子視窗。所以這一項選的是NULL。

HelloWin沒有選單,所以第九項也是NULL。

第十項,書上寫的是,hInstance是程式的例項控制程式碼,也就是說這個控制程式碼代表的是程式自身。同時它也是WinMain()的引數之一。

第十一項,視窗物件的引數指標控制程式碼。看到這麼長的名字是不是頭暈了?我不知道具體含義sorry......

CreateWindow()返回被建立的視窗的控制程式碼,該控制程式碼被存放在變數hwnd中。Windows中的每個視窗都有一個控制程式碼,程式用控制程式碼來引用視窗。許多Windows函式都需要使用hwnd作為引數,這樣,Windows才能知道函式是針對哪個視窗的。如果一個程式建立了許多視窗,則每個視窗都有一個控制程式碼。視窗控制程式碼是Windows程式處理的最重要的控制程式碼之一。

就這樣,CreateWindows()呼叫返回後視窗已經建立完畢。Windows已經分配了一塊記憶體,用來儲存在建立視窗過程中設定的全部資訊。

但是我們仍然無法在螢幕上看到視窗。還需要兩個函式顯示視窗。一個是:

ShowWindow(hwnd, iCmdShow);

hwnd是剛剛用CreateWindow()建立的視窗物件的控制程式碼。我們要好好看一看第二個引數。這也是WinMain()的第四個引數,int型別。那麼,這是幹什麼用的呢?Come on Baby!

第二個引數有三個常量可供選擇。

SW_SHOWNORMAL  SW_SHOWMAXIMIZED  SW_SHOWMINNOACTIVE

這些都是幹什麼用的???跟上次一樣,改程式碼!請把

ShowWindow(hwnd, iCmdShow);

改成

ShowWindow(hwnd, SW_SHOWMAXIMIZED) 或是 ShowWindow(hwnd, SW_SHOWMINNOACTIVE)

執行一次您就Know了。雖然名字是ShowWindow(),但是我們還是不能Show到HelloWin生成的視窗。因為我們需要重新畫螢幕,也就是重新整理。

UpdateWindow(hwnd);

 該函式的功能是重新整理視窗。現在終於可以看見視窗了。

while (GetMessage (&msg, NULL, 0, 0))  //從訊息佇列中獲取訊息
{
  TranslateMessage (&msg) ;  //轉換某些鍵盤訊息
  DispatchMessage (&msg) ;  //將訊息傳送給視窗過程
}

Windows為當前執行的每個程式維護一個訊息佇列。這一點很重要。當您點選滑鼠左右鍵的時候會發生訊息,當您點了某個按鈕的時候也會發生訊息。總之,不管您幹什麼,都會產生訊息。這些訊息會進入訊息佇列中。GetMessage()負責從訊息佇列中取出訊息。

TranslateMessage()在按鍵時系統產生虛擬鍵訊息(VK_TAB等等)。在接收虛擬鍵程式碼時該函式將相應的WM_CHAR程式碼傳送到應用程式訊息佇列中。具體的意思嘛我不懂啊。不過這個東東暫時好像不是很重要。
DispatchMessage()從應用程式的訊息迴圈中傳送訊息至相應的視窗過程。

訊息有入隊與不入隊之分。入隊訊息按照順序一個一個送到視窗過程,就像排隊買票一樣。不入隊訊息的優先順序是比較高的,可以直接傳送給視窗過程。就好像領導不用排隊買電影票一樣。while迴圈處理的都是入隊的訊息。

msg變數是型別為MSG的結構,型別MSG在WINUSER.H中定義如下:

typedef struct tagMSG
{
  HWND hwnd; //向視窗過程函式傳送該訊息的視窗物件的控制程式碼,也就是該訊息的來源。
  UINT message; //訊息識別符號,也就是訊息內容。
  WPARAM wParam; //訊息的附加資訊,取決於message的值。
  LPARAM lParam; //訊息的附加資訊,取決於message的值。
  DWORD time; //傳送訊息的時間。
  POINT pt; //傳送訊息時螢幕上滑鼠的位置。
}
MSG, *PMSG;

POINT資料型別也是一個結構,它在WINDEF.H中定義如下:

typedef struct tagPOINT
{
  LONG x;
  LONG y;
}
POINT, *PPOINT;

訊息迴圈以GetMessage()呼叫開始,它從訊息佇列中取出一個訊息:

GetMessage(&msg, NULL , 0, 0);

在這裡我們有必要了解一下GetMessage()。它的原型是:

GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilten, UINT wMsgFilterMax);

GetMessage()檢索訊息佇列,然後把訊息放入lpMsg指向的MSG結構中。讓我們來LOOK一下函式的四個引數。

lpmsg  --&gt 指向MSG結構的指標
hWnd  --&gt 接收訊息的視窗控制程式碼。通常設定為NULL,檢索屬於當前程式的視窗訊息以及使用PostThredMessage()呼叫產生的訊息。有關PostThredMessage()以後再說。
wMsgFilterMin  --&gt 檢索的最小訊息值。一般情況下設定為0。
wMsgFilterMax  --&gt 檢索的最大訊息值。如果wMsgFilterMin和wMsgFilterMax都設定為0,則檢索所有訊息。一般情況下設定為0。 

返回值 BOOL型。檢索到WM_QUIT訊息時返回FALSE,否則為TRUE。應該繼續訊息迴圈直到GetMessage()返回FALSE以退出while迴圈,終止程式。

之前我們所討論的都是準備性工作。註冊視窗類,建立視窗,然後在螢幕上顯示視窗,程式進入訊息迴圈,從訊息佇列中取出一條訊息,然後由DispatchMessage()將訊息傳送到相應的視窗過程中,WinMain()所做的只有這些。HelloWin實際的動作都是在它的視窗過程WndProc()中進行的。當使用者改變了視窗的大小,或者移動了視窗,所有的這些訊息都是由視窗過程來處理的。所以HelloWin的學習重點是它的視窗過程WndProc()。沒有搞明白WndProc()等於沒有學HelloWin。視窗過程函式確定了在視窗的客戶區域中顯示的內容,以及視窗怎樣響應使用者輸入。

視窗過程說到底只是一個函式,所以我們可以任意給它取名字。在HelloWin中視窗過程的名字是WndProc()。一個Windows程式可以包含多個視窗過程,但是一個視窗類只能有一個視窗過程。

視窗過程總是定義為如下形式:

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

在定義視窗過程的時候除了函式名可以變之外其它的一定要原封不動。大家可以看到視窗過程的四個引數跟MSG結構的前四個域是一樣的。DispatchMessage()從應用程式的訊息迴圈中傳送訊息至相應的視窗過程。因為視窗過程是用來接收訊息的,所以它的四個引數跟MSG結構的前四個域是一樣的。

第一個引數是視窗控制程式碼。這個控制程式碼代表的是HelloWin的視窗。因為:

hwnd = CreateWindow( /* 此處省略…… */ );

CreateWindow()的返回值是新建視窗的控制程式碼。上面的語句將新建視窗的控制程式碼賦給了hwnd。所以hwnd代表的是HelloWin視窗。如果沒有這個引數,鬼才知道視窗過程要處理的是哪個視窗的訊息。前面已經提過,因為多個視窗使用同一個視窗過程,所以用第一個引數說明視窗過程要處理的是哪一個視窗發出的訊息。HelloWin只有一個視窗物件。所以第一個引數只能是hwnd。

第二個引數message是訊息的內容,比如WM_CREATE。WM_CREATE訊息是CreateWindow()發出的。最後兩個引數是32位的訊息引數,它提供關於訊息的更多資訊。這些引數包含每個訊息型別的詳細資訊。

一般來說,Windows程式設計師使用switch語句來處理視窗過程接收到的訊息。視窗過程在處理完訊息後必須返回0。視窗過程不予處理的其它所有的訊息應該被傳給DefWindowProc()。從DefWindowProc()返回的值必須由視窗過程返回。這句話可能有點不好理解。可以Look一下HelloWin的最後一個語句:

return DefWindowProc(hwnd, message, wParam, lParam);

現在明白了嗎?好好想一想。這個應該很簡單。

在HelloWin中WndProc()只選擇處理三種訊息:WM_CREATE, WM_PAINT, WM_DESTROY。視窗過程的結構如下:

switch(message)
{
case WM_CREATE:
  [處理 WM_CREATE 訊息]
  return 0;

case WM_PAINT:
  [處理 WM_PAINT 訊息]
  return 0;

case WM_DESTROY:
  [處理 WM_DESTROY 訊息]
  return 0;
}

return DefWindowProc(hwnd, message, wParam, lParam);

呼叫DefWindowProc()來為視窗過程不予處理的所有訊息提供預設處理,也就是說除了上述三種訊息之外的所有訊息都是由DefWindowProc()來處理。這是很重要的。

case WM_CREATE: //播放一個聲音檔案
  PlaySound(TEXT ("HelloWin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
  return 0 ;

視窗過程收到的第一個訊息----也是WndProc()選擇處理的第一個訊息是----WM_CREATE。當Windows在WinMain()中處理CreateWindow()時WndProc()接收這個訊息。更準確地講,這個WM_CREATE是由CreateWindow()發出的。

當WM_CREATE產生後Windows呼叫WndProc(),將WndProc()的第一個引數設定為HelloWin視窗物件的視窗控制程式碼。第二個引數設定為WM_CREATE訊息。WndProc()處理完WM_CREATE之後將控制權返回給Windows。然後Windows繼續執行WinMain()中CreateWindow()後面的語句。

在HelloWin程式中視窗過程收到WM_CREATE訊息後播放HelloWin.wav。使用的是PlaySound()。從函式的名字上我們就大概可以看出它是用來播放聲音檔案的。

case WM_PAINT:
  hdc = BeginPaint(hwnd, &ps); //開始視窗繪製
 
  GetClientRect(hwnd, &rect);  //獲取視窗客戶區的尺寸
 
  DrawText(hdc, TEXT ("Hello, Windows 98!"), -1, &rect,
  DT_SINGLELINE | DT_CENTER | DT_VCENTER) ; //顯示文字串
 
  EndPaint (hwnd, &ps) ; //結束視窗繪製
  return 0 ;

WndProc()處理的第二個訊息是WM_PAINT訊息。這個訊息在Windows程式設計中很重要。當視窗客戶區域的部分或者全部變為“無效”,以致於必須“重新整理”時,將由這個訊息通知程式。

這裡又出現了新名詞---“無效”。客戶區域怎麼會變得無效呢?比如說,假設HelloWin視窗的座標原來是(300, 400)。使用者使用滑鼠拖動了視窗,使視窗的座標變成了(301,400)。既然視窗的座標已經發生了變化,那麼顯示器上也必須重畫這個視窗。我已經說過,螢幕上的圖形介面都是畫出來的,是位件(跟BMP一樣)。新畫出來的視窗座標是(301,400)。使用者又使用滑鼠改變了視窗的位置,使得座標變成了(302,400),這個時候顯示器又得重畫這個視窗,新畫的視窗座標當然是(302,400)。這一切都是一瞬間的事情。

當原來在顯示器上顯示的內容已經過時了,需要重畫時,這個時候就說視窗的客戶區域變得“無效”了。Windows必須重新在顯示器上畫視窗,以符合新的位置。

在最初建立視窗的時候整個客戶區都是無效的。因為程式在顯示器上沒有畫任何東東。當程式執行到UpdateWindow()時這個函式發出第一個WM_PAINT訊息。

對WM_PAINT訊息的處理幾乎總是從一個BeginPaint()呼叫開始:

hdc = BeginPaint(hwnd, &ps);

以一個EndPaint()呼叫結束:

EndPaint (hwnd, &ps);

在BeginPaint()呼叫中如果客戶區域背景還沒有被刪除,Windows會負責刪除。它使用註冊視窗類的wndclass.hbrBackground域中指定的刷子來刪除背景。在HelloWin中這是一個白色的備用刷子。所以Windows將把視窗背景設定為白色。BeginPaint()呼叫使得整個客戶區有效,並返回一個“裝置描述表控制程式碼”。裝置描述表控制程式碼是指物理輸出裝置。比如顯示器或者是印表機。在視窗的客戶區域顯示文字和圖形需要裝置描述表控制程式碼。不能用BeginPaint()返回的裝置描述表控制程式碼在客戶區之外繪圖。想要在客戶區外繪圖需要其它函式返回的裝置描述表控制程式碼。

EndPaint()釋放裝置描述表控制程式碼,使之該裝置描述表控制程式碼不再有效。

在使用者改變HelloWin的尺寸後客戶區變得無效。讀者應該還記得在wndclass.style域設定為標誌CS_HREDRAW和CS_VREDRAW,這一風格指示Windows當視窗尺寸發生變化後使得整個視窗無效。然後視窗過程接收到一個WM_PAINT訊息。

在移動視窗使幾個視窗互相重疊時Windows不儲存一個視窗中被另一個視窗所遮蓋的部分。當原先被遮蓋的部分又露出來以後它就被標誌為無效,視窗過程接收到一個WM_PAINT訊息,以重新整理視窗的內容。

呼叫完BeginPaint()之後WndProc()接著呼叫GetClientRect():

GetClientRect(hwnd, &rect); //獲取視窗客戶區的尺寸

函式的功能是獲取視窗客戶區的尺寸。第一個引數是程式視窗的控制程式碼,第二個引數是一個指標,指向一個RECT型別的rect結構。該結構有四個LONG域,分別為left、top、right、bottom。這四個域用來儲存視窗客戶區的尺寸。left和top域通常設定為0,right和bottom域設定為客戶區域的寬度和高度(單位是畫素點數)。

DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect,
  DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;  //顯示文字串

該函式在指定的矩形裡寫入格式化文字,根據指定的方法對文字格式。

第一個引數是裝置描述表控制程式碼。第二個引數就是顯示在客戶區中央的文字串。第三個引數是  -1,表示在客戶區顯示的文字串是以'/0'結束的。

第四個引數客戶區的尺寸和位置。表示在這個矩形內顯示格式化文字。

第五個引數:

DT_SINGLELINE 表示文字串必須顯示在同一行。即使是插入了回車和換行符都不能折行。
DT_CENTER 表示文字串在客戶區水平居中。
DT_VCENTER 表示文字串在客戶區垂直居中。當使用這個標誌時必須同時指定DT_SINGLELINE。

一旦客戶區域變得無效,WndProc()就接收一個新的WM_PAINT訊息。WndProc()透過呼叫GetClientRect()獲取變化後的視窗尺寸,並使用DrawText()在視窗中央顯示文字。

WM_DESTROY訊息是另一個重要訊息。這一個訊息指示Windows正在根據使用者輸入的命令清除視窗。該訊息是使用者單擊"關閉"按鈕或者在程式的系統選單上選擇"關閉"時發生的。

HelloWin透過呼叫PostQuitMessage()以標準方式響應WM_DESTROY訊息。

PostQuitMessage(0);

該函式在程式的訊息佇列中插入一個WM_QUIT訊息。前面提到過GetMessage()對於除了WM_QUIT之外的從訊息佇列中取出的所有訊息都返回非0值。而當GetMessage()取到一個WM_QUIT訊息時它返回0。這將導致WinMain()退出訊息迴圈,並終止程式。然後程式執行下面的語句:

return msg.wParam;

我不知道這個時候返回msg.wParam的意義,以後會明白的。總之整個程式就這樣結束了。終於可放鬆一下了,接下來的內容是對第三章的總結。

即使有了對HelloWin的說明,讀者可能仍然對程式的結構和原理覺得神秘。在為傳統環境編寫簡單的C程式時整個程式可能包含在main()中,而在HelloWin中WinMain()只包含了註冊視窗類,建立視窗,從訊息佇列中取出訊息和傳送訊息所必需的程式碼。

程式的所有實際動作均在視窗過程中發生。在HelloWin中這些動作不多。WndProc()只是簡單地播放了一個聲音檔案,並在視窗中顯示一個文字串。

在後面的章節中讀者將發現Windows程式所做的一切都是響應傳送給視窗過程的訊息。這是概念上的主要難點之一,在開始編寫Windows程式之前必須先搞清楚。

程式設計師已經習慣了使用作業系統呼叫的思路。例如C程式設計師使用fopen()開啟檔案。fopen()最終透過呼叫作業系統來開啟檔案,這毫無疑問。

但是Windows不同,儘管Windows有1000多個函式供程式呼叫,但是Windows也呼叫使用者程式。比如前面定義的視窗過程WndProc()。視窗過程與一個視窗類相關聯,視窗類是程式呼叫RegisterClass()註冊的。基於該視窗類建立的所有視窗使用視窗類指定的視窗過程處理所有訊息。

在第一次建立視窗時Windows呼叫WndProc(),視窗被消除時Windows呼叫WndProc(),視窗改變尺寸、移動或者變成圖示時、從選單選擇某一項、滾動捲軸、按下滑鼠或者從鍵盤輸入字元時、以及視窗客戶區域必須被重新整理時Windows都要呼叫WndProc()。

所有這些WndProc()呼叫都以訊息形式進行。在大多數Windows程式中程式的主要部分都用來處理訊息。Windows傳送給視窗過程的訊息通常都以 WM 打頭的名字標識,並且都在WINUSER.H標頭檔案中定義。

Windows程式有一個訊息迴圈。它使用GetMessage從訊息佇列中取出訊息,並且呼叫DispatchMessage()將訊息傳送給視窗過程。

Windows程式是按照順序將訊息傳送給視窗過程的?還是直接從程式外面接收訊息的?實際上這兩種情況都存在。訊息被分為“進隊的”和“不進隊的”。進隊的訊息是Windows放入訊息佇列中的。在程式的訊息佇列中排隊的訊息被GetMessage()依次取出並傳給視窗過程。不進隊的訊息在Windows呼叫視窗時直接傳送給視窗過程。也就是說“進隊”的訊息被髮送給訊息佇列,“不進隊”的訊息傳送給視窗過程。任何情況下視窗過程都將獲得視窗所有的訊息--包括進隊的和不進隊的。視窗過程是視窗的“訊息中心”。

進隊訊息基本上是使用者輸入的結果,擊鍵(如WM_KEYDOWN和WM_KEYUP)、擊鍵產生的字元(WM_CHAR)、滑鼠移動(WM_MOUSEMOVE)和滑鼠鍵(WM_LBUTTONDOWN)的形式給出。進隊訊息還包含時鐘訊息(WM_TIMER)、重新整理訊息(WM_PAINT)、退出訊息(WM_QUIT)。

不進隊訊息通常來自特定的Windows函式。例如當WinMain()呼叫CreateWindow()時Windows將傳送一個WM_CREATE訊息。當WinMain()呼叫ShowWindow()時Windows將給視窗傳送WM_SIZE和WM_SHOWWINDOW訊息。

這一過程很複雜,非常Lucky的是其中大部分是由Windows解決的,不關程式設計師的事情。當我看到第四章的內容時才發現第三章簡直就是小兒科。第三章的HelloWin只是準備運動。


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

相關文章