以前會一點C++,寫出來的程式都是黑屏的。最近心血來潮想學學VC,編寫一些帶GUI的程式。學習VC的第一個經典程式就是hellowin了。通過這幾天的查閱資料,終於是對這個程式有了一些理解。分享給大家:
首先windows程式設計和傳統的C語言或者C++程式設計思路截然不同的地方在於windows的訊息機制。什麼是訊息機制呢?簡單的說,就是別呼叫我,我會呼叫您。它不像傳統的C語言程式,在main函數中調用其他函數。它的做法是:當你給應用程式發訊息(比如用滑鼠點了一下)以後,你其實並沒有給應用程式發訊息,而是把這個訊息發給了作業系統。系統先會通過計算滑鼠的位置來判斷你的訊息是發給誰的,然後再把訊息發給特定的應用程式。當你的應用程式收到這個訊息以後,進行一定的轉換,再返回給系統,系統在調用程式員事先寫好的回呼函數來對你的滑鼠點擊做出響應。
有了這個基本思路以後我們再看看程式的基本過程:
聲明了一個回呼函數以後就開始了WinMain。WinMain函數大致做了一下幾個工作:定義並初始化了一個視窗,向系統註冊了這個視窗(RegisterClass),在記憶體中建立了視窗(CreateWindow),顯示並更新視窗。然後進入訊息迴圈,等待系統給程式發訊息,如果收到了訊息,通過DispatchMessage把訊息給系統,然後系統通過調用回呼函數WndProc判斷是什麼訊息並作出響應。
這裡有幾個問題需要特別注意:
第一,WinMain裡面沒有調用WndProc。而是在初始化視窗的時候通過lpfnWndProc將回呼函數與WinMain綁定起來。註冊時系統也就知道了二者的關係。
第二,為什麼不在訊息迴圈中直接處理訊息,而是在回呼函數中處理呢?
正常情況下,每個程式有一個訊息佇列,當訊息到來後從中讀取訊息再做處理。但是有些特殊的訊息卻不是這樣,比如creat訊息。當執行CreateWindow以後,系統就會發出creat訊息,這時訊息迴圈還沒有建立!從這個簡單的例子可以看出,把所有訊息處理流程放在訊息迴圈中是不合適的,所以windows採用回呼函數機制統一處理訊息。
程式中注釋掉了很多功能:
第一, 程式裡設定了自訂訊息。自訂訊息也是一個數,WM_USER以後的數用來表示自訂訊息,以前的是系統已經定義的訊息,不要隨便更改。該訊息由WM_LBUTTONDOWN發出,訊息的相應是發出聲音。
第二, WM_LBUTTONDOWN下的無效區會導致視窗的重繪。如果WM_LBUTTONDOWN下沒有PlaySound函數,而WM_PAINT下面有,則單擊左鍵也會導致發出聲音。
第三, windos系統沒有所謂的雙擊,只有左鍵按下時右鍵也按下或者右鍵按下時左鍵也按下。
總之,這個程式的可玩性很強,大家可以通過實驗充分理解訊息都是什麼時候發出的。
最後我對這個程式進行了詳盡的注釋,有的問題我也不是很確定,如果有人指出了其中的問題,我感激不盡。
#include <windows.h>#include <mmsystem.h> //等效於添加了在設定裡添加了WINMM.LIB#pragma comment(lib, "WINMM.LIB") //自訂訊息#define WM_MYMSG (WM_USER + 100)//回呼函數聲明LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;int WINAPI WinMain( HINSTANCE hInstance, // 當前執行個體控制代碼HINSTANCE hPrevInstance, // 先前執行個體控制代碼,通常為空白LPSTR lpCmdLine, // 命令列int nCmdShow ) // 顯示狀態:最大化、最小化{//視窗類別的名字static TCHAR szAppName[] = TEXT ("MyWindowsProgram") ;//控制代碼HWND hwnd ;//訊息:訊息結構體內容有:控制代碼、訊息識別符、附加訊息、附加訊息、訊息投遞時間、訊息投遞時的游標位置//訊息是由系統填寫的。MSG msg ;//視窗類別WNDCLASS wndclass ;//視窗風格,可以是任何Class Styles的組合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) GetStockObject (WHITE_BRUSH);//菜單:這裡暫時沒有wndclass.lpszMenuName = NULL;//0結尾的字串或者按鈕:這裡是一個字串,指明視窗類別名wndclass.lpszClassName = szAppName;//註冊視窗if(!RegisterClass(&wndclass)){return 0;}//建立視窗hwnd = CreateWindow(szAppName, //視窗類別名TEXT("我的windows程式"), //視窗標籤WS_OVERLAPPEDWINDOW, //視窗風格:CW_USEDEFAULT, //X座標CW_USEDEFAULT, //Y座標CW_USEDEFAULT, //寬度CW_USEDEFAULT, //高度NULL, //父視窗控制代碼NULL, //視窗菜單控制代碼hInstance, //程式執行個體控制代碼NULL); //建立參數//顯示視窗,參數為:視窗控制代碼,顯示狀態ShowWindow(hwnd,SW_SHOWNA);//更新視窗UpdateWindow(hwnd);while(GetMessage(&msg,NULL,0,0))//擷取訊息參數:指向MSG結構,視窗控制代碼(NULL表示調用線程的任何視窗),第一條訊息,最後一條訊息{//虛擬鍵訊息轉換為字元訊息字元訊息被寄送到調用線程的訊息佇列裡,當下一次線程調用函數GetMessage或PeekMessage時被讀出TranslateMessage(&msg);//該函數分發一個訊息給視窗程序。通常訊息從GetMessage函數獲得。訊息被分發到回呼函數(過程函數),作用是訊息傳遞給作業系統,然後作業系統去調用我們的回呼函數,也就是說我們在表單的過程函數中處理訊息。DispatchMessage(&msg);}//msg.wParam是退出訊息return msg.wParam ; }//回呼函數:由程式員定義,系統調用的函數,前兩個是訊息參數,後兩個是Win16系統遺留下來的產物LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){//裝置內容控制代碼HDC hdc;//PAINTSTRUCT結構包含了繪製用戶端區域的資訊,第一個參數就是HDC類的PAINTSTRUCT ps;RECT rect;switch(message){//建立視窗訊息:CreateWindow函數被調用以後就會發出,這個訊息不進入訊息佇列。case WM_CREATE://參數為:播放的聲音,(中間這個參數一般為NULL,除非使用SND_RESOURCE 播放標記標記),播放標記(同步和非同步區別在於:如果你連續點兩下,同步會逐一播放;非同步會在點第二下時停止第一下的聲音,直接播放第二下)//只能播放wav格式//PlaySound(TEXT ("D:/videos/msg.wav"),NULL,SND_FILENAME | SND_SYNC);return 0;//繪製視窗訊息case WM_PAINT://BeginPaint函數準備指定的視窗畫圖//參數為:視窗控制代碼、指向PAINTSTRUCT結構的畫圖資訊hdc = BeginPaint (hwnd, &ps) ;//擷取客戶區的座標:參數為:將要被擷取座標的視窗,指向RECT結構的指標來存放座標GetClientRect(hwnd,&rect);//在指定地區畫格式化的文本//參數為:裝置內容控制代碼,將要被展示的字元,文本長度(-1表示假定是0結尾的),RECT類型的地址,表明文本將要在那裡被格式化,格式化方式:這裡是水平、垂直都置中DrawText(hdc,TEXT("Hello, Windows7!"),-1,&rect,DT_SINGLELINE | DT_CENTER | DT_VCENTER);PlaySound(TEXT ("D:/videos/msg.wav"),NULL,SND_FILENAME | SND_SYNC);//繪製視窗結束EndPaint (hwnd, &ps) ; return 0;case WM_LBUTTONDOWN://是指定地區變為無效區//PlaySound(TEXT ("D:/videos/msg.wav"),NULL,SND_FILENAME | SND_SYNC);//使指定地區變為無效區,然後會自動調用paint函數//InvalidateRect(hwnd,NULL,FALSE);//發送訊息,參數為:訊息發給誰、發什麼訊息、兩個附加訊息資訊//SendMessage(hwnd,WM_MYMSG,wParam,lParam);if(wParam & MK_RBUTTON)//同時右鍵也按下PlaySound(TEXT ("D:/videos/msg.wav"),NULL,SND_FILENAME | SND_SYNC);return 0;case WM_RBUTTONDOWN:if(wParam & MK_LBUTTON)PlaySound(TEXT ("D:/videos/msg.wav"),NULL,SND_FILENAME | SND_SYNC);return 0;case WM_MYMSG:PlaySound(TEXT ("D:/videos/msg.wav"),NULL,SND_FILENAME | SND_SYNC);return 0;//關閉視窗訊息case WM_DESTROY:PostQuitMessage (0) ;return 0;}return DefWindowProc (hwnd, message, wParam, lParam) ; //執行預設訊息}