本帖計划從四個大的方面來講 這四個方面是 窗口類、文件操作類、網絡類、數據庫類。 都是時下流行的編程必備技術 也是軟件開發者 必須掌握的技術。中間以實例講解 逐步學習 相信大家看完后 會有很大的提高的。 第一章 窗口類程序的編寫 這一章就先來講解下窗口類程序的編寫。因為現在程序沒有界面 就像人沒有臉面一樣 而且好的界面更能吸引人。 從基本的界面開始 相信能給大家指明出一條路的 使大家很容易地掌握窗口序的編寫。其實界面設計利用VC 6.0 的 MFC 很容易地制作出來。這里從底層開始寫代碼來寫界面程序 使大家知道一些底層的東西 為以后學習打下好的 基礎 相信您學了這些 再用VC 的MFC會得心應手的。 1.1 用 C 寫的第一個一個窗口程序 作為編程的開始 我們還是以一個Hello World來開始我們的學習之旅。代碼如下 #include <stdio.h> void main() { printf("Hello World!"); } 這是一個再簡單不過的C程序了 只要有點C語言的知識就能夠懂的 不過這里估計還有些人 到現在還不知道#include <stdio.h>中的頭文件stdio.h到底是什么東西 我就來說下了 stdio.h是一個文本文件 存在於磁盤上的 已VC為例 它的位置如下圖 也許你聽說過printf 函數是在stdio.h中預定義的 但是你見過其定義的形式沒有 沒有且看下圖 其定義形式 就如圖中所示 也許你並不懂前面那些東西是什么 不用擔心 以后我會慢慢解釋給大家的。函數是先定義才能使 用的 所以stdio.h中定義printf函數 我我們在引用了stdio.h頭文件后就可以在程序中調用printf函數了。 上面是在命令行中顯示一個“Hello World!”,沒什么意思 下面我寫一個窗口程序 顯示個Hello World! #include <windows.h> void main() { MessageBox(NULL," Hello World!","我的第一個窗口程序",MB_OK); } 編譯運行后如下圖 彈出的是一個對話框 上面有Hello World 還有一個標題和一個“確定”按鈕。 當然你會說這對話框也算個窗口嗎 這里肯定的告訴你 是的 對話框是窗口程序的一個子集。你可能還會這樣問 這樣一個簡 單的窗口有啥用呢 其實這樣的窗口非常有用 我們在操作計算機的時候 會出現一些警告或提示的對話框 都是基本是這種方 法寫出來的。就算是這個很簡單 學習本來不就是有易向難 有淺顯深奧去的過程嗎。 整個效果幾乎就是靠一個函數MessageBox的功勞。這里也先不介紹這個函數了 說些其他的。 其實用C編寫一些惡程序 就是把編程環境中所提供的一些函數熟悉了基本就可以了。用VC來寫成序 其中的頭文件有很多 定義了很多Windows API 函數 、數據結構、宏 可以讓我們大家運用 通過它們 我們可以快速開發出使用的程序。這些Windows API在微軟的MSDN上查 上面有很多說明 部分還有代碼示例。不會是可以輸入函數名 查找相關信息 建議大家用英文版的 Library 因為其內容比中文版的全面 英語不好的同學呢 就先看中文了 中文MSDN:http://msdn.microsoft.com/library/zh-cn/ 英文MSDN:http://msen.micorsoft.com/library/en-us/ 到這里 我們就完成第一個有界面程序的編寫 你感覺寫有界面的程序難嗎 顯然不難。 下面看一個向鋒和波波感興趣的程序 九九乘法 采用命令行形式 #include “stdio.h” int i=0,j=0; for(i=1;i<10;i++) for(j=1;j<i+1;j++) printf(“%d*%d=%d \t”,j,i,j*i); printf(“\n”); 和那個javascript效果都是一樣的 所以語言只要學好一樣 其他的就很容易旁通的 學習就撿一種學好 不要貪多。 好的 這一節就這樣吧 大家先各自了解下微軟的MSDN 對以后的學習會有很大的幫助的。 1.2 第一個真正的窗口程序 上一節中 我們用MessageBox函數輕松地實現了一個對話框窗口 可能你會說 那僅僅是個沒有用的對話框而已 是的 只是 對話框而已。我之所以以一個對話框為例呢 是因為我只是想讓你知道寫一個有界面的程序並不是件難辦的事。明白了這一點后 我們繼續。今天來編寫一個真正的窗口程序。 下面就該羅嗦一段了 由於大家以前並沒有寫過什么窗口程序 寫的都是命令行下的 我們知道在命令行下的程序都有一個主函 數main 這個函數也就是程序的入口函數。我們現在用VC 6.0來寫 而且要寫窗口類程序 VC 6.0給我們提供了一個專門用作 窗口類程序的入口函數WinMain() 這個函數原型是這樣的 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTRlpCmdLine, int nCmdShow ); 大家是不是感覺這個函數挺復雜的 有這么幾個參數 而像main好像就沒有參數。其實main是有參數 但是main函數的參數是可以省略的 而WinMain是不可以省的。這里也要對VC 6.0的編譯模 式改下 看下圖 依次 是“工程”→“設置”→“連接” 在“工程選項”里把console改為windows就可以了。如果認真學了匯編 或是手寫命令編譯連接過C程序 就會知道這是干什么的。Console是控制台的意思 以前我們用mian函數寫的程序 都是以控制台模式連接的 所以很少會有界面的。現在我們要寫有界面的程序 所以要選Windows 窗口 模式了。 我們寫入以下代碼 並按照上面說的方法去做 看看結果 #include "windows.h" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nShowCmd) { MessageBox(NULL,"WinMain創建的窗口程序","WinMain",MB_OK); return 0; } 結果如下圖: 與第一節中的這段代碼代碼比較下 #include “windows.h” void main() { MessageBox(NULL," Hello World!","我的第一個窗口程序",MB_OK); } 兩者比較下 后者多了個cmd窗口。可見用main寫的並沒有完全脫離命令行呀。所以以后我們寫窗口程序就用winmain 了。 好了 轉過來 我們來看看WinMain()函數 其中有4個參數 先看下解釋 看不明白得先看完 hInstance 應用程序當前事例的句柄。 hPrelnstance 應用程序的先事例的句柄。對於同一個程序打開兩次 出現兩個窗口第一次打開的窗口就是先前實例 的窗口。對於一個32的位程序 該參數總為NULL。 lpCmdLine 指向應用程序命令行的空字符串的指針 不包括函數名。獲得整個命令行 參看GetCommandLine。 nCmdShow 指明窗口如何顯示 是隱藏還是顯示 有沒有最大化按鈕之類的 。取值可以參考MSDN 這里我相信有一個詞大家好應該比較陌生 句柄(HANDLE)是吧。下面我就來簡單的說下 句柄其實就是Windows系統中一個東西的唯一標識。就是系統中有很多運行的程序或者資源之類的 為了更好的管理 使用 Windows系統給它們每人一個ID一樣。懂得網頁制作的人應該知道網頁中各個元素的ID吧 網頁的ID如果重 復話可能出現錯誤。那么系統的句柄會不會有相同的 那是肯定不會有的了 就和我們的學號一樣 系統自動分配每 一個模塊的句柄 是不會相同的了。 對於句柄大家可以先這樣理解着 不用一下子搞懂得。以后學着學着就明白了。 估計大家對那幾個參數的類型改犯迷糊了吧。其實那幾個類型 並不是什么新類型 都是Windows開發人員為了自己 和他人編程方便 同過基本的C語言語法定義一種新的結構體 或者是共同體 再者就是枚舉類型。我知道結構體、 共同體和枚舉類型 很多老師是沒有講到的 因為在書的后邊 很多教C的 又是很垃圾的老師 所以不會講那么快 的。其實結構體這些數據類型 就是通過我們常用的字符、整型、浮點等數據類型構造一個比較復雜的類型而已 舉 個例子 就是我們知道C沒有一個數據類型可以描述一個人吧 那么我構造一個是不是很方便我們編程呢。我們可以 這樣構造一個 struct People { int age;//年齡 char sex[2];//性別 int height;//身高 …… } 我們這樣定義以后就可以在我們以后的程序中利用這個數據類型了 People zhangsan;把zhangsan的身高172放到 zhangsan.height中。這樣可以方便完成很多工作。所以結構體是很簡單的 還有其他的復雜數據類型也是很簡單的 都是有常用的簡單的類型來結合到一起構造一個復雜的而已。這和JAVA定義類是很相似的 java定義個人類 不是 可以這樣的 public class People { public int age; public string sex; public height; …… } 看起來都差不多 而且用法也很相像。唯一的差別其實就是類可以有方法 而結構體是沒有的 經過特殊處理也是可 以的 這里不用考慮 。 上面是為了讓大家了解下復雜數據類型的定義 羅嗦了一大堆。下面來看下WinMain中第一個參數的類型 HINSTANCE這個只是個結構體而已 實際上和HANDLE這個類型差不多 但是有一點差別 而HANDLE 是這樣typedef PVOID HANDLE;定義的 PVOID是什么呢 我們來看下typedef void *PVOID;說明PVOID是一個指針 初始指向空 void 。因此可以知道句柄也是個指針而已。看着這么復雜原來也只是指針。 這些都可以在微軟的msdn上查得到的 而且很詳細的 那個第二個LPSTR 根據字面上的意思就知道是字符串類型了。查一查果然是。 大家一定要利用好msdn 很有用的。 本節就到此結束了 主要是說明了一個WinMain函數和結構體的事情 東西也不算太多 大家應該能接受得了吧。下 節就來點復雜點深點的東西 希望大家做好心理准備。 1.3 窗口程序的編寫 在來啰嗦之前 希望大家能夠做好准備 這一節知識有點多 內容有點長。但願大家能夠一口氣讀完 如果一口 氣讀不完 那就換口氣接着讀。 上節中我們用MessageBox()就實現了一個真正的窗口。MessageBox()中的原型如下 Int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType); 參數解釋 hWnd 所屬對話框所屬窗口的句柄 如果是NULL 則此對話框不屬於任何一個窗口。 lpText 對話框窗口的顯示內容。 lpCaption 對話框窗口的標題。 uType 對話框的樣式和動作 像是確定按鈕 還是取消按鈕就是設置這里的 關於這個函數的細節可以看這里 http://msdn.microsoft.com/en-us/library/ms645505(VS.85).aspx 到此為止 你也算是會了窗口程序的編寫 但只是一個開始 不過這已經很好 可能會讓你感覺到了C的魅 力 也可能會稍微解點C語言能干什么的疑惑。在開始寫代碼之前 我有必要把細節和原理先說明下。 Windows下一個窗口創建的過程有以下幾個步驟 1. 程序創建一個窗口 首先要向Windows系統注冊一個窗口類wndclassex 其實就是定義一個變量 變量的 類 型 是WNDCLASSEX(結 構 體)。 該 結 構 體 的 定 義 與 介 紹 看 這 里 (http://msdn.microsoft.com/en-us/library/ms633577(VS.85).aspx) typedef struct { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; HICON hIconSm; } WNDCLASSEX, *PWNDCLASSEX; 成員介紹 cbSize 值為sizeof(WNDCLASSEX),在調用GetClassInfoEx前必須要先設置它值。 style 窗口類的樣式 它的值可以是窗口樣式值的任意組合。 可以有以下的值 lpfnWndProc 指向窗口處理函數 回調函數 。處理窗口事件 像單擊鼠標會怎樣 右擊鼠標會怎樣 都 是由此函數控制的。 cbClsExtra 為窗口類的額外信息做記錄 系統初始化為0。 cbWndExtra 記錄窗口實例的額外信息 系統初始為0.如果程序使用WNDCLASSEX注冊一個從資源文件里 創建的對話框 則此參數必須設置為DLGWINDOWEXTRA hIcon 窗口類的圖標 為資源句柄 如果設置為NULL 系統將為窗口提供一個默認的圖標。 hCursor 窗口類的鼠標樣式 為鼠標樣式資源的句柄 如果設置為NULL 系統提供一個默認的鼠標樣式。 hbrBackground 窗口類的背景刷 為背景刷句柄 也可以為系統顏色值 如果顏色值已給出 則必須轉化為 以下的HBRUSH的值 COLOR_ACTIVEBORDER COLOR_ACTIVECAPTION COLOR_APPWORKSPACE COLOR_BACKGROUND COLOR_BTNFACE COLOR_BTNSHADOW COLOR_BTNTEXT COLOR_CAPTIONTEXT COLOR_GRAYTEXT COLOR_HIGHLIGHT COLOR_HIGHLIGHTTEXT COLOR_INACTIVEBORDER COLOR_INACTIVECAPTION COLOR_MENU COLOR_MENUTEXT COLOR_SCROLLBAR COLOR_WINDOW COLOR_WINDOWFRAME COLOR_WINDOWTEXT lpszMenuName 指向一個以NULL結尾的字符床 同目錄資源的名字一樣。如果使用整型id表示菜單 可 以用MAKEINTRESOURCE定義一個宏。如果它的值為NULL,那么該類創建的窗口將都沒有默認的菜單。 lpszClassName 窗口類的名字 字符串類型。 hIconSm 小圖標的句柄 在任務欄顯示的圖標 可以和上面的那個一樣。 定義一個WNDCLASSEX類型變量后 在給變量成員初始化后 我們就可以用 RegisterWindowEx(&wndclassex)來注冊這個窗口類了。 這個注冊過程 就和我們平常創建一個項目一樣 都要先注冊才能創建 。 2. 創建窗口 這一步很簡單 就是利用CreateWindowEx()函數來創建就是了。 CreateWindowEx函數的原型如下: HWND CreateWindowEx( DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam ); 參數說明 dwExStyle 指定窗口的擴展風格。該參數可以是下列值 WS_EX_ACCEPTFILES 指定以該風格創建的窗口接受一個拖拽文件。 WS_EX_APPWINDOW 當窗口可見時 將一個頂層窗口放置到任務條上。 WS_EX_CLIENTEDGE 指定窗口有一個帶陰影的邊界。 WS_EX_CONTEXTHELP 在窗口的標題條包含一個問號標志。 WS_EX_CONTROLPARENT 允許用戶使用Tab鍵在窗口的子窗口間搜索。 WS_EX_DLGMODALFRAME 創建一個帶雙邊的窗口 該窗口可以在dwStyle中指定 WS_CAPTION風格來創建一個標題欄。 WS_EX_LEFT 窗口具有左對齊屬性 這是缺省設置的。 WS_EX_LEFTSCROLLBAR 如果外殼語言是如Hebrew Arabic 或其他支持reading order alignment的語言 則標題條 如果存在 則在客戶區的左部分。若是其他語言 在該風格被忽略並且不作 為錯誤處理。 WS_EX_LTRREADING 窗口文本以LEFT到RIGHT 自左向右 屬性的順序顯示。這是缺 省設置的。 WS_EX_MDICHILD 創建一個MD子窗口。 WS_EX_NOPATARENTNOTIFY 指明以這個風格創建的窗口在被創建和銷毀時不向父 窗口發送WM_PARENTNOTFY消息。 WS_EX_OVERLAPPED WS_EX_CLIENTEDGEWS_EX_WINDOWEDGE的組合。 WS_EX_PALETTEWINDOW WS_EX_WINDOWEDGE, WS_EX_TOOLWINDOW 和 WS_WX_TOPMOST 風格的組合 WS_EX_RIGHT: 窗口具有普通的右對齊屬性 這依賴於窗口類。 WS_EX_RIGHTSCROLLBAR 垂直滾動條在窗口的右邊界。這是缺省設置的。 WS_EX_RTLREADING 如果外殼語言是如 Hebrew Arabic 或其他支持讀順序對齊 reading order alignment 的語言 則窗口文本是一自左向右 RIGHT 到 LEFT 順序的讀出順序。 WS_EX_STATICEDGE 為不接受用戶輸入的項創建一個3一維邊界風格 WS_EX_TOOLWIDOW 創建工具窗口 即窗口是一個游動的工具條。 WS_EX_TOPMOST 指明以該風格創建的窗口應放置在所有非最高層窗口的上面並且停留在其L 即使窗 口未被激活。使用函數SetWindowPos來設置和移去這個風格。 WS_EX_TRANSPARENT 指定以這個風格創建的窗口在窗口下的同屬窗口已重畫時 該窗 口才可以重畫。 由於其下的同屬富日已被重畫 該窗口是透明的。 IpClassName: 窗口類的名字。 lpWindowName:指向一個指定窗口名的空結束的字符串指針。其實就是窗口的名字。 dwStyle:指定創建窗口的風格。該參數可以是下列窗口風格的組合再加上說明部分的控制風格。 x:窗口的橫坐標。 y:窗口的豎坐標。 nWidth:窗口的寬度。 nHeight:窗口的高度。 hMenu 菜單句柄 或依據窗口風格指明一個子窗口標識。 hlnstance 與窗口相關聯的模塊事例的句柄。 lpParam:指向一個值的指針 該值傳遞給窗口 WM_CREATE消息 返回值 如果函數成功 返回值為新窗口的句柄 如果函數失敗 返回值為NULL。若想獲得更多錯誤信息 請調用GetLastError函數。 3. 顯示窗口 顯示窗口就是更簡單的事情了。 連個函數輕松搞定 第一個函數就是ShowWindow 原型如下 BOOL ShowWindow( HWND hWnd,//當前的窗口句柄 int nCmdShow //可見狀態 ); 因為CreateWindowEx函數創建的窗口是在內存中的 並沒有顯示到顯示器上 用ShowWindow()函數 設 定窗口的可見狀態 並把數據從內存中移動到顯卡上 以便顯示。 第二個函數就是UpdateWindow() 函數原型 BOOL UpdateWindow(HWND hWnd); 描述 這個 UpdateWindow 函數通過發送重繪消息 WM_PAINT 給目標窗體來更新目標窗體客戶區的無效 區域。如果那個窗體的無效區域沒有 就不發送重繪消息 WM_PAINT 了 。注意了 這個 API 函數是直 接發送消息 WM_PAINT 給目標窗體的 沒有進入過消息隊列。 函數參數 hWnd 一個要更新的窗體的句柄 函數返回值 如果函數調用成功 返回值為非零值。 如果函數調用不成功 返回值為零。 經過這三步后 一個窗口就實現了 就創建了出來 難不 也真夠難的 Windows 想的正周到 把創 建過程的每一個細節都給想到了 每毫秒可能發生的事情都想到了 難怪 Windows 那么貴 還不開源。也 算是人間的產品嘛 費的心血可真不少呀。說難其實也不難 創建一個窗口程序也就三步 一注冊 二創建 三顯示。很容易就 ok 了。這 T 他媽容易了吧。 原來就是這些的 我想我已經說的挺明白的了 如果你有什么疑惑 可以給我發郵件 cangsanbujin@126.com 下面我們就按照上面所說的來編程實現一個窗口 #include <windows.h> //回調函數 LRESULT WINAPI WinProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) { switch(Msg)//處理消息過程 什么是消息 下節再講 { case WM_DESTROY://響應鼠標單擊關閉按鈕事件 PostQuitMessage(0);//退出消息隊列 至於什么是消息隊列 下節說 return 0;//退出函數 } return DefWindowProc(hWnd,Msg,wParam,lParam); } //主函數 int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd) { char *cName = "myWindow"; WNDCLASSEX wc; HWND hWnd; MSG Msg; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.cbSize = sizeof(WNDCLASSEX); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//通過函數來設置一個白色的背景 這里大家設置 為NULL看看 會很有趣的 wc.hCursor = NULL;//不設置 wc.hIcon = NULL;//不設置 wc.hIconSm = NULL;//不設置 wc.hInstance = hInstance;//當前程序的句柄 hInstance是有系統給傳遞的 wc.lpfnWndProc = WinProc;//窗口處理過程的回調函數。 wc.lpszClassName =(LPSTR)cName;//窗口類的名字。 wc.lpszMenuName = NULL;//目錄名 不設置 wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClassEx(&wc);//在系統中注冊 hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,cName,"我的窗口我喜歡",WS_OVERLAPPEDWINDOW, 200,100,600,400,NULL,NULL,hInstance,NULL);//創建窗口 窗口標題為"我的窗口我喜歡" if(hWnd == NULL) {//容錯處理 MessageBox(NULL,"There's an Error","Error Title",MB_ICONEXCLAMATION|MB_OK); return 0; } ShowWindow(hWnd,nShowCmd);//顯示窗口 UpdateWindow(hWnd); //下面是對消息的循環處理 大家先不必管這些 下節課我會細說的 while(GetMessage(&Msg,NULL,0,0)) { TranslateMessage(&Msg);//翻譯消息 DispatchMessage(&Msg);//分派消息 } return Msg.message; } 編譯運行后 可以看到一個白色背景的窗口出來了。如下圖 哎 這一節 篇幅可是真有點長的 看完估計得換幾口氣吧 但是只要你看到了這些 你的水平就立馬上了一個檔次。 想你看完后也許會頭昏腦脹的 沒有再看下去的信心的 但是估計當你把我的代碼復制到VC中編譯運行后 看到一個 可愛的窗口時 肯定又會重新點燃你心中學習的熱情吧 因為你已經看到了成功 看到了成就 一種成就感猶然自心 中生 自信也提起來了 這比什么都好 人嘛就得對自己充滿信心的。所以大家要發揚持之以恆的精神 堅持和我一 起把這段苦悶的入門過程給走完 那么編程就不再是痛苦 而是一種樂趣。其實寫這些程序很多東西都不用去記的想 WNDCLASSEX結構的成員及成員作用 這些都不用去死記 只要知道有這么個東西 到時時再查就可以了 編程用到的 函數、結構體那么多 誰想記呀。這一節已經留下了些問題 在下節介紹的 大家如果有余力的話 可以先查下資料 的。 1.4 鼠標指針特效 大家在都玩過網絡游戲吧 里面的界面都是很吸引人的 好的界面的確能給人以美的感受。而里面的鼠標並不是我們 平常見到的箭頭了 而是獨具匠心的。網游我就只玩過魔域 所以就以魔域為例 魔域中的鼠標是這樣的 。今天我 們就來實現讓鼠標到程序窗口上就變為我們想要的圖案。 在寫代碼之前 我們還是先來看下先驅知識 這里要說的就是上節說資源了 當時大家看了可能並不知道什么是 資源 這里就詳細說一下。 大家知道Windows程序都有圖標 鼠標有光標 窗口上有圖片、按鈕、文字等等 這些都是程序的部分 這樣就 是程序的資源。程序沒有進入內存運行的時候 我們就叫它可執行文件吧 在磁盤保存的時候 並不只是保存了程序 運行的代碼部分 即cpu指令部分 還有一些圖片、字符、按鈕、圖標並不是在代碼段的。可執行文件的大致機構 如下圖 一個可執行文件是很復雜的 這里就簡單的畫這么一個難看的圖 知道資源所在的大概位置 能理解程序的執行 部分和知道程序的圖標是從哪來的就可以了。 今天我們只是修改鼠標的指針 所以用到的資源 只有鼠標的光標資源而已。資源的源文件是以rc為擴展名的腳 本文件 仍然是C語言格式的 很簡單 有資源編譯器Rc.exe編譯成以res為擴展名的二進制資源文件 最后用連 接器 把res文件和obj文件連接到一起就成了我們的程序exe文件了 現在知道了程序編譯后要連接了吧。光標的 圖片格式有兩中cur和ani的。這個文件我在魔域的圖片庫里面找到了就復制到 當前項目目錄下。下面來定義下資 源文件myOwnCursor.rc //myOwnCursor.rc written by xhk 2009.3.1 #include <resource.h> //資源文件要用到的圖文件 #define CUR 0x1000 //定義資源的ID 為整型id CUR CURSOR "myOwnCursor.ani" //用到的光標圖案 寫完后 在命令提示符下進入目錄 然后用rc.exe編譯 輸入rc myOwnCursor.rc命令 回車 我們查看下項目目錄下多了個myOwnCursor.RES的文件 就是編譯生成的二進制資源件。 接下來就該編寫代碼了 來應用這個資源文件 建立myOwnCursor.c文件 其實代碼和上節所寫代碼很相似的 只是稍微加以修改而已。 #include <windows.h> #define CUR 0x1000 //預定義光標的id //回調函數 LRESULT WINAPI WinProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) { switch(Msg)//處理消息過程 什么是消息 下節再講 { case WM_DESTROY://響應鼠標單擊關閉按鈕事件 PostQuitMessage(0);//退出消息隊列 至於什么是消息隊列 下節說 return 0;//退出函數 } return DefWindowProc(hWnd,Msg,wParam,lParam); } //主函數 int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd) { char *cName = "myWindow"; WNDCLASSEX wc; HWND hWnd; MSG Msg; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.cbSize = sizeof(WNDCLASSEX); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//通過函數來設置一個白色的背景 這里大家 設置為NULL看看 會很有趣的 wc.hCursor = LoadCursor(hInstance,MAKEINTRESOURCE(CUR));//這里改了 來載入光標資源 wc.hIcon = NULL;//不設置 wc.hIconSm = NULL;//不設置 wc.hInstance = hInstance;//當前程序的句柄 hInstance是有系統給傳遞的 wc.lpfnWndProc = WinProc;//窗口處理過程的回調函數。 wc.lpszClassName =(LPSTR)cName;//窗口類的名字。 wc.lpszMenuName = NULL;//目錄名 不設置 wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClassEx(&wc);//在系統中注冊 hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,cName,"我的窗口我喜歡",WS_OVERLAPPEDWINDOW, 200,100,600,400,NULL,NULL,hInstance,NULL);//創建窗口 窗口標題為"我的窗口我喜歡" if(hWnd == NULL) {//容錯處理 MessageBox(NULL,"There's an Error","Error Title",MB_ICONEXCLAMATION|MB_OK); return 0; } ShowWindow(hWnd,nShowCmd);//顯示窗口 UpdateWindow(hWnd); //下面是對消息的循環處理 大家先不必管這些 下節課我會細說的 while(GetMessage(&Msg,NULL,0,0)) { TranslateMessage(&Msg);//翻譯消息 DispatchMessage(&Msg);//分派消息 } return Msg.message; } 用vc編譯生成myOwnCursor.obj,把myOwnCursor.obj和myOwnCursor.res放到同一個文件夾下 然后在命令行 下進入它們所在的目錄 輸入命令 linke kernel32.lib user32.lib gdi32.lib /subsystem:windows myOwnCursor.obj myOwnCursor.res把兩個文件連接成myOwnCursor.exe.運行后界面如下; 看到了吧 當鼠標移入窗口的時候 光標就變成了那個手型圖案了 這和魔域的是一樣的。到現在想想一個特效 又咋地 不還是一句一句代碼寫出來的 而特效和普通程序往往只有數據代碼不同而已。網絡游戲的界面很好看 也 只不過是資源文件用的比較多而已 而且計算量很大 所以網游總是很占內存的 因為圖片、聲音文件都很大 而且 變換比較多、快 就比較占用資源了。 其實再好的程序 只要有了思路 就能寫出來 而且寫出來也難的 是不是 今天大家應該會有點收獲了 都會 設計個性的鼠標光標了 比起以前學習C的東西 應該有一種層次感了吧。這些東西都比較接近系統了 所以學了之 后 你對Windows系統也會有很深的了解的。如果各位看官看到本節還有興趣繼續看下去 那么這對小人就是一種支 持 小人在此謝過了 如果看官覺得看這些沒有半點收獲 那么請看官不要再勉強自己看下去了 免得浪費看官大人 的寶貴時間 那是小人所承擔不起的。 總之了 要想寫好程序 就得多練 編譯連接過程中很容易發現錯誤的所在 那么這時你解決一個錯誤你就提高 一次 解決的錯誤越多越快 你就學的越多越快。終於后來你會發現 你太難找到錯誤了 那么恭喜你 你已經升級 為大蝦了 已經完全脫離了菜菜級了。希望大家繼續努力 1.5 在窗口上寫上“Hello World” 這一節我們乘勝追擊 來繼續深入學習下 學習窗口處理時間的東東。 也許你以前聽說過 windows系統是消息驅動的 可是可能根本就不知道什么消息 更不知道什么消息驅動了。 其實什么是消息呢 說白了就是我們點鼠標擊鍵盤而程序發生反應 消息是一種數據 就是我們點鼠標擊鍵盤后 系 統把我們的操作封裝到數據中 然后發送給程序 讓程序對我們點鼠標擊鍵盤的動作做出反應 當然程序也可以置之不理。Windows可是一個多任務的系統 而且同時可能產生很多的擊鍵動作 那么同時可能能會有很多消息 windows 系統為了更好的管理維護這些消息 就把這些消息加入消息隊列中 消息隊列其實就是消息的集合。 學過VB的人知道 VB中的程序是事件驅動的 因為一般都是發生時 調用相應的事件處理函數 所以整個處理 過程都好像是事件引發的一樣。這里的事件就是指我們擊鍵的動作等。 學過JAVA的人知道 JAVA中有事件適配器 來捕獲相應的事件 並交給相應的處理方法進行處理。 其實三種語言的處理過程也都是大同小異 只不過JAVA和VB把這些處理過程給封裝了 VB尤其封裝的更厲害 所以編程者不必考慮和知道這中間的細節問題 仍然可以編寫出實用的程序 但正是由於細節的原因 用VB的開發的 程序並不能高效地處理問題。 而C語言本身就是面向過程的語言 所以這一過程可以用C語言更好地表現出來 這也是我用C而不用C++的原 因之一。 通過前幾節的學習 我們知道了 在窗口程序中都有一個處理窗口的函數 其實所有的消息將會得到怎樣的處理 都是此函數安排的。現在大家再回去看看那個程序代碼和注釋 相信應該能明白些了吧。 系統產生的消息是不斷的 但是中間是有間隔的 程序要想知道有沒有自己的消息 得不停地去問系統 問系統 當前有沒有屬於自己的消息 這就需要一個循環來實現了。 下面先看下前兩節種用到的消息循環代碼 while(GetMessage(&Msg,NULL,0,0)) { TranslateMessage(&Msg);//翻譯消息 DispatchMessage(&Msg);//分派消息 } Windows為消息定義一種新的數據類型MSG 用於保存消息的相關信息。在windows中GetMessage函數從消息隊 列種取得消息 填寫好MSG結構並返回 如果獲取的消息是WM_QUIT消息 則退出循環。 TranslateMessage將MSG結構傳給Windows進行一些鍵盤消息的轉換 當有鍵盤按下或者放開時 Windows產生 WM_KEYDOWN和WM_KEYUP或WM_SYSKEYDOWN和WM_SYSKEYUP消息 像WM_KEYDOWN這些都是微軟定義的一些宏 是什么 意思 看字面意思就可以知道了 但是這些消息的參數種包含的是按鍵的掃描碼 暫時不用理會 轉換成常用的 ASCII碼要經過查表 很不方便 TranslatMessage遇到鍵盤消息則將掃描碼轉換成ASCII碼並在消息隊列種插入 WM_CHAR或WM_SYSCHAR消息 參數就是轉換好的ASCII碼 如此一來 要處理鍵盤消息的話只要是處理WM_CHAR消息 就好了。菊與刀非鍵盤消息TranslateMessage則不做處理。 最后 由DispatchMessage將消息發送到窗口對應的窗口過程去處理。窗口過程返回后DispatchMessage函數才 返回 然后開始新一輪的消息循環。 想想我們這節的目的是為了在潔白的窗口種寫下“Hello World” 那么我們怎么來留下我們的筆跡呢 窗口我們 是能做出來了 那么怎么在上面寫東西呢 等等 在上面寫東西的前提是不是窗口做出來之后 當初我是這么想的 后來看到別人的代碼才知道原來可以在窗口繪制的過程就繪制“Hello world”了。Windows有時真是個細心的家伙 把窗口創建到顯示的一瞬間又給划分了很多小的過程。在繪制窗口時 Windows會產生WM_PAINT消息 那么我們在得 到這個個消息的時候 來留下我們的筆跡 豈不就是下手最早的時刻。其實Windows在屏幕上輸出文字和圖像是一樣 的 都是在屏幕上畫 和我們在紙上畫圖和寫字是一樣的 都是用筆來畫的 只不過用的筆不一樣而已 如果牽強用 一支筆去做所有的工作 效果並不會理想的。Windows的筆也是這樣的 不過這些筆是函數而已 畫圖和畫文字的函 數不一樣而已。 下面就接着上節修改的代碼繼續修改 必要的注釋和改變的地方我會標明的 #include <windows.h> #define CUR 0x1000 //預定義光標的id HDC hDC;//HDC是指設備上下文 暫時不用管 只要能這樣用就可以了 的句柄 //PAINTSTRUCT要繪制的信息 詳情請登陸http://msdn.microsoft.com/en-us/library/dd162768(VS.85).aspx //了解下就可以了 沒什么重要的東西 PAINTSTRUCT paint; RECT rect;//RECT用來存儲窗口信息的結構 只要是窗口的坐標、寬度和高度。 //回調函數 LRESULT WINAPI WinProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) { switch(Msg)//處理消息過程 什么是消息 下節再講 { case WM_PAINT: //BginPaint做些繪畫的開始工作 填充PAINSTURCT結構 返回設備上下文 暫時不用理解 句柄 hDC=BeginPaint(hWnd,&paint); //GetClientRect用來獲取窗口所在客戶區的位置大小信息 GetClientRect(hWnd,&rect); //DrawText就是Windows用來“畫字”的筆了 DT_*之類是指文字的樣式 看字面意思也能看懂的 //有多少樣式呢 可以查看這里http://msdn.microsoft.com/en-us/library/ms901121.aspx //本例中是單線、水平居中和豎直居中。 DrawText(hDC,"Hello World!",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER); //EndPaint就是做些收尾的工作了。 EndPaint(hWnd,&paint); break; case WM_DESTROY://響應鼠標單擊關閉按鈕事件 PostQuitMessage(0);//退出消息隊列 至於什么是消息隊列 下節說 return 0;//退出函數 } return DefWindowProc(hWnd,Msg,wParam,lParam); } //主函數 int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd) { char *cName = "myWindow"; WNDCLASSEX wc; HWND hWnd; MSG Msg; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.cbSize = sizeof(WNDCLASSEX); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//通過函數來設置一個白色的背景 這里大家 設置為NULL看看 會很有趣的 wc.hCursor = LoadCursor(hInstance,MAKEINTRESOURCE(CUR));//這里改了 來載入光標資源 wc.hIcon = NULL;//不設置 wc.hIconSm = NULL;//不設置 wc.hInstance = hInstance;//當前程序的句柄 hInstance是有系統給傳遞的 wc.lpfnWndProc = WinProc;//窗口處理過程的回調函數。 wc.lpszClassName =(LPSTR)cName;//窗口類的名字。 wc.lpszMenuName = NULL;//目錄名 不設置 wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClassEx(&wc);//在系統中注冊 hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,cName,"我的窗口我喜歡",WS_OVERLAPPEDWINDOW, 200,100,600,400,NULL,NULL,hInstance,NULL);//創建窗口 窗口標題為"我的窗口我喜歡" if(hWnd == NULL) {//容錯處理 MessageBox(NULL,"There's an Error","Error Title",MB_ICONEXCLAMATION|MB_OK); return 0; } ShowWindow(hWnd,nShowCmd);//顯示窗口 UpdateWindow(hWnd); //下面是對消息的循環處理 大家先不必管這些 下節課我會細說的 while(GetMessage(&Msg,NULL,0,0)) { TranslateMessage(&Msg);//翻譯消息 DispatchMessage(&Msg);//分派消息 } return Msg.message; } 最終運行結果 其實就是比原來的多了三個變量和幾句代碼 多的我也標出來了 而且都說明 那些簡單的函數 大家可以自己 查下 很簡單的 我就不再為一些簡單的函數來打字了 這樣也可以鍛煉大家的動手能力。 編譯連接后 大家看看預期的結果出現了吧 潔白的窗口上留下了我們的字跡。建議學過VB或JAVA的讀者 可 以聯系起來想一想 把C的處理消息過程給理解下 理解下消息的結構和概念 熟悉西Windows的消息機制 這樣就 可以為以后編寫優質的軟件打下堅實的基礎。此言不虛的 像金山詞霸的屏幕取詞功能就是對Windows消息巧妙的運 用 鍵盤記錄器 木馬 也是利用了截獲Windows消息 而記錄我們的按鍵行為 從而盜取信息的。大家好好理解下 本節內容 自己動手寫點東西 查些其他的事件信息 改進下程序 多熟練下 為后面的學習做一點鋪墊。 1.6 讓窗口響應鼠標的事件 為了讓大家能夠多熟悉下事件和消息的概念 本節再以一個小的例子看下鼠標事件的應用。鼠標的事件有 單擊、右擊、雙擊和滾動輪的 我們這里先讓鼠標響應兩種事件 單擊和右擊。我們在實現在窗口上單擊時彈出一個 上面 有“你擊了左鍵”的對話框 右擊時彈出一個上面有“你擊了右鍵”的對話框。代碼仍用上節的 只在窗口處理 過程的 消息處理語句 switch 中加入一下代碼 case WM_LBUTTONUP://鼠標左鍵松開時 MessageBox(hWnd,"你擊了左鍵","提示",MB_OK); break; case WM_RBUTTONUP://鼠標右鍵松開時 MessageBox(hWnd,"你擊了右鍵","提示",MB_OK); break; 編譯運行 單擊左鍵如下圖 當然 至於是彈出對話框還是干別的什么 你可以自己添加代碼的。不管怎樣 我想通過這個例子 你應該理解 了程序是怎么處理鼠標的單擊和右擊了吧 應該對消息驅動有了更好一點理解吧。自己多寫寫代碼 多查查資料成就 很快的。就在寫這個實例的時候 因為VC 6.0的MFC中定義的消息和API中的不太一樣 一時忘了API中鼠標事件的 宏是怎么寫的 我查了MSDN、百度和谷歌 竟然沒有查出來 真是豈有此理 最后 我就只有自己解決了 硬是在 winuser.h的頭文件中找到鼠標事件消息的宏定義的。真個過程中有一種山窮水盡疑無路 柳暗花明又一村的感覺 就在我快要放棄的時候 想起來了用基本的方法 直接查看頭文件的定義 真可謂天才 然是最笨的方法了。不過這 樣也好 一下子看了很多消息的宏定義。大家學習一定要自己多查多練習 相信聰明的你一定會輕松解決遇到的問題 的。想我這么笨的人 都學會了用C寫Windows程序 又何況聰明的你呢。 1.7 單擊鼠標來改變窗口的位置 目的還是為了大家進一步熟悉Windows的窗口實現消息的機制 也使大家了解多一點的Windows API 函數 從而利於日后的實際編程。平常我們都是用鼠標拖着窗口來改變窗口的 今天我們來點新鮮的 通過單擊鼠標來使窗 口改變位置。 從前面的知識中 我們知道 窗口的初始位置是在CreateWindow函數中設定的 Windows既然可以讓用戶通過鼠 標拖來改變窗口位置 那么肯定就有函數是專門用來改變窗口位置的。是的 的確有這樣的函數 常用的有兩個 它 們是SetWindowPos和MoveWindow。兩個函數的詳細情況如下 SetWindowPos 函數功能 該函數改變一個子窗口 彈出式窗口式頂層窗口的尺寸 位置和Z序。子窗口 彈出式窗口 及頂層窗口根據它們在屏幕上出現的順序排序、頂層窗口設置的級別最高 並且被設置為Z序的第一個窗口。 函數原型 BOOL SetWindowPos HWN hWnd HWND hWndlnsertAfter,int X int Y,int cx int cy,UNIT Flags 參數 hWnd:窗口句柄。 hWndlnsertAfter:在z序中的位於被置位的窗口前的窗口句柄。該參數必須為一個窗口句柄 或下列值 之一 HWND_BOTTOM 將窗口置於Z序的底部。如果參數hWnd標識了一個頂層窗口 則窗口失去頂級位 置 並且被置在其他窗口的底部。 HWND_NOTOPMOST 將窗口置於所有非頂層窗口之上 即在所有頂層窗口之后 。如果窗口已經是 非頂層窗口則該標志不起作用。 HWND_TOP:將窗口置於Z序的頂部。 HWND_TOPMOST:將窗口置於所有非頂層窗口之上。即使窗口未被激活窗口也將保持頂級位置。 查看該參數的使用方法 請看說明部分。 x 以客戶坐標指定窗口新位置的左邊界。 Y 以客戶坐標指定窗口新位置的頂邊界。 cx:以像素指定窗口的新的寬度。 cy 以像素指定窗口的新的高度。 uFlags:窗口尺寸和定位的標志。該參數可以是下列值的組合 SWP_ASNCWINDOWPOS 如果調用進程不擁有窗口 系統會向擁有窗口的線程發出需求。這就防止 調用線程在其他線程處理需求的時候發生死鎖。 SWP_DEFERERASE 防止產生WM_SYNCPAINT消息。 SWP_DRAWFRAME 在窗口周圍畫一個邊框 定義在窗口類描述中 。 SWP_FRAMECHANGED 給窗口發送WM_NCCALCSIZE消息 即使窗口尺寸沒有改變也會發送該消 息。如果未指定這個標志 只有在改變了窗口尺寸時才發送WM_NCCALCSIZE。 SWP_HIDEWINDOW;隱藏窗口。 SWP_NOACTIVATE 不激活窗口。如果未設置標志 則窗口被激活 並被設置到其他最高級窗口或非 最高級組的頂部 根據參數hWndlnsertAfter設置 。 SWP_NOCOPYBITS 清除客戶區的所有內容。如果未設置該標志 客戶區的有效內容被保存並且在窗 口尺寸更新和重定位后拷貝回客戶區。 SWP_NOMOVE 維持當前位置 忽略X和Y參數 。 SWP_NOOWNERZORDER 不改變z序中的所有者窗口的位置。 SWP_NOREDRAW:不重畫改變的內容。如果設置了這個標志 則不發生任何重畫動作。適用於客戶區 和非客戶區 包括標題欄和滾動條 和任何由於窗回移動而露出的父窗口的所有部分。如果設置了這個標志 應用程序必須明確地使窗口無效並區重畫窗口的任何部分和父窗口需要重畫的部分。 SWP_NOREPOSITION 與SWP_NOOWNERZORDER標志相同。 SWP_NOSENDCHANGING 防止窗口接收WM_WINDOWPOSCHANGING消息。 SWP_NOSIZE 維持當前尺寸 忽略cx和Cy參數 。 SWP_NOZORDER 維持當前Z序 忽略hWndlnsertAfter參數 。 SWP_SHOWWINDOW 顯示窗口。 返回值 如果函數成功 返回值為非零 如果函數失敗 返回值為零。若想獲得更多錯誤消息 請調用 GetLastError函數。 備注 如果設置了SWP_SHOWWINDOW和SWP_HIDEWINDOW標志 則窗口不能被移動和改變大 小。如果使用SetWindowLoog改變了窗口的某些數據 則必須調用函數SetWindowPos來作真正的改變。 使用下列的組合標志 SWP_NOMOVEISWP_NOSIZEISWP_FRAMECHANGED。 有兩種方法將窗口設為最頂層窗口 一種是將參數hWndlnsertAfter設置為HWND_TOPMOST並確保 沒有設置SWP_NOZORDER標志 另一種是設置窗口在Z序中的位置以使其在其他存在的窗口之上。當一 個窗口被置為最頂層窗口時 屬於它的所有窗口均為最頂層窗口 而它的所有者的z序並不改變。 如果HWND_TOPMOST和HWND_NOTOPMOST標志均未指定 即應用程序要求窗口在激活的同時改 變其在Z序中的位置時 在參數hWndinsertAfter中指定的值只有在下列條件中才使用 在hWndlnsertAfter參數中沒有設定HWND_NOTOPMOST和HWND_TOPMOST標志。 由hWnd參數標識的窗口不是激活窗口。 如果未將一個非激活窗口設定到z序的頂端 應用程序不能激活該窗口。應用程序可以無任何限制地改 變被激活窗口在Z序中的位置 或激活一個窗口並將其移到最高級窗口的頂部或非最高級窗口的頂部。 如果一個頂層窗口被重定位到z序的底部 HWND_BOTTOM 或在任何非最高序的窗口之后 該窗口 就不再是最頂層窗口。當一個最頂層窗口被置為非最頂級 則它的所有者窗口和所屬者窗口均為非最頂層窗 口。 一個非最頂端窗口可以擁有一個最頂端窗口 但反之則不可以。任何屬於頂層窗口的窗口 例如一個對 話框 本身就被置為頂層窗口 以確保所有被屬窗口都在它們的所有者之上。 如果應用程序不在前台 但應該位於前台 就應調用SetForegroundWindow函數來設置。 Windows CE 如果這是一個可見的頂層窗口 並且未指定SWP_NOACTIVATE標志 則這個函數將激 活窗口、如果這是當前的激活窗口 並且指定了SWP_NOACTIVATE或SWP_HIDEWINDOW標志 則激 活另外一個可見的頂層窗口。 當在這個函數中的nFlags參數里指定了SWP_FRAMECHANGED標志時 WindowsCE重畫窗口的整 個非客戶區 這可能會改變客戶區的大小。這也是重新計算客戶區的唯一途徑 也是通過調用SetwindowLong 函數改變窗口風格后通常使用的方法。 SetWindowPos將使WM_WINDOWPOSCHANGED消息向窗口發送 在這個消息中傳遞的標志與傳遞 給函數的相同。這個函數不傳遞其他消息。 MoveWindow 函數功能 該函數改變指定窗口的位置和尺寸。對於頂層窗口 位置和尺寸是相對於屏幕的左上角的 對於子窗口 位置和尺寸是相對於父窗口客戶區的左上角坐標的。 函數原型 BOOL MoveWindow HWND hWnd,int x.int y,int nWidth,int nHeight,BOOL BRePaint 參數 hWnd 窗口句柄。 x 指定窗口的新位置的左邊界。 Y 指定窗口的新位置的頂部邊界。 nWidth 指定窗口的新的寬度。 nHaight 指定窗口的新的高度。 bRepaint:確定窗口是否被刷新。如果該參數為TRUE 窗口接收一個WM_PAINT消息 如果參數為 FALSE 不發生任何刷新動作。它適用於客戶區 非客戶區 包括標題欄和滾動條 及由於移動子窗口而 露出的父窗口的區域。如果參數為FALSE 應用程序就必須明確地使窗口無效或重畫該窗口和需要刷新的 父窗口。 返回值 如果函數成功 返回值為非零 如果函數失敗 返回值為零。若想獲得更多錯誤信息 請調用 GetLastError函數。 備注 如果bRepaint為TRUE 系統在窗口移動后立即給窗口過程發送WM_PAINT消息 即由 MoveWindow函數調用UPdateWindow函數 。如果bRepaint 為FALSE 系統將WM_PAINT消息放在該 窗口的消息隊列中。消息循環只有在派遣完消息隊列中的其他消息時才派遣WM_PAINT消息。 MoveWindow給窗口發送WM_WfNOWPOSCHANGING WM_WINDOWPOSCHANGED WM_MOVE WM_SIZE和WM_NCCALCSIZE消息。 以上的東西 都是從msdn上翻譯過來的 把它們翻譯過來 是在有故意添文字之嫌。看了函數說明就 好的多了吧 我們只把上節中的代碼稍加修改即可 我這里給出我的代碼 大家可以借鑒下 我覺得知道這 兩個函數怎么用 真是沒什么要的說的了 #include <windows.h> #define CUR 0x1000 //預定義光標的id HDC hDC;//HDC是指設備上下文 暫時不用管 只要能這樣用就可以了 的句柄 //PAINTSTRUCT要繪制的信息 //詳情請登陸http://msdn.microsoft.com/en-us/library/dd162768(VS.85).aspx //了解下就可以了 沒什么重要的東西 PAINTSTRUCT paint; RECT rect;//RECT用來存儲窗口信息的結構 只要是窗口的坐標、寬度和高度。 //自定義函數MoveLeft,使窗口向左移動5像素,此函數中調用MoveWindow函數 int MoveLeft(HWND hWnd) { GetWindowRect(hWnd,&rect);//獲取窗口的信息 MoveWindow(hWnd,rect.left-5,rect.top,rect.right-rect.left,rect.bottom-rect.top,TRUE); return 1; } //自定義函數MoveRight 是窗口向右移動5像素 此函數中調用SetWindowPos函數 換個口味 int MoveRight(HWND hWnd) { GetWindowRect(hWnd,&rect);//獲取窗口的信息 SetWindowPos(hWnd,HWND_NOTOPMOST, rect.left+5,rect.top, rect.right-rect.left, rect.bottom-rect.top, SWP_NOZORDER); return 1; } //回調函數 LRESULT WINAPI WinProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) { switch(Msg)//處理消息過程 什么是消息 下節再講 { case WM_PAINT: //BginPaint做些繪畫的開始工作 填充PAINSTURCT結構 返回設備上下文 暫時不用理解 句 柄 hDC=BeginPaint(hWnd,&paint); //GetClientRect用來獲取窗口所在客戶區的位置大小信息 GetClientRect(hWnd,&rect); //DrawText就是Windows用來“畫字”的筆了 DT_*之類是指文字的樣式 看字面意思也能看懂 的 //有多少樣式呢 可以查看這里http://msdn.microsoft.com/en-us/library/ms901121.aspx //本例中是單線、水平居中和豎直居中。 DrawText(hDC,"Hello World!",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER); //EndPaint就是做些收尾的工作了。 EndPaint(hWnd,&paint); break; case WM_LBUTTONUP://鼠標左鍵松開時 MoveLeft(hWnd); break; case WM_RBUTTONUP://鼠標右鍵松開時 MoveRight(hWnd); break; case WM_DESTROY://響應鼠標單擊關閉按鈕事件 PostQuitMessage(0);//退出消息隊列 至於什么是消息隊列 下節說 return 0;//退出函數 } return DefWindowProc(hWnd,Msg,wParam,lParam); } //主函數 int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd) { char *cName = "myWindow"; WNDCLASSEX wc; HWND hWnd; MSG Msg; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.cbSize = sizeof(WNDCLASSEX); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//通過函數來設置一個白色的 背景 這里大家設置為NULL看看 會很有趣的 wc.hCursor = LoadCursor(hInstance,MAKEINTRESOURCE(CUR));//這里改了 來載入光標資源 wc.hIcon = NULL;//不設置 wc.hIconSm = NULL;//不設置 wc.hInstance = hInstance;//當前程序的句柄 hInstance是有系統給傳遞的 wc.lpfnWndProc = WinProc;//窗口處理過程的回調函數。 wc.lpszClassName =(LPSTR)cName;//窗口類的名字。 wc.lpszMenuName = NULL;//目錄名 不設置 wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClassEx(&wc);//在系統中注冊 hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,cName,"我的窗口我喜歡 ",WS_OVERLAPPEDWINDOW, 200,100,600,400,NULL,NULL,hInstance,NULL);//創建窗口 窗口標題為"我的窗口我喜歡" if(hWnd == NULL) {//容錯處理 MessageBox(NULL,"There's an Error","Error Title",MB_ICONEXCLAMATION|MB_OK); return 0; } ShowWindow(hWnd,nShowCmd);//顯示窗口 UpdateWindow(hWnd); //下面是對消息的循環處理 大家先不必管這些 下節課我會細說的 while(GetMessage(&Msg,NULL,0,0)) { TranslateMessage(&Msg);//翻譯消息 DispatchMessage(&Msg);//分派消息 } return Msg.message; } 這個結果 不能用靜態的圖片來說明什么 大家自己編譯連接后 擊鼠標試試 看能出現預期的效果不 我這里就不貼圖出來了 貼靜態的沒啥意思 貼動態的 有點不想整 我也懶 哈。 經過這幾次折騰 如果大家真的每一次都手寫了 相信其中的那關鍵的且相同的那部分代碼應該是非常熟悉了 到此就我們就該升級了 就行高一層次的修煉 后面兩節 我准備給大家說些資源的深入細節 還有再在寫幾個完全 實用的小程序和幾個惡作劇程序 不知大家意下如何。 1.8 資源的初步深入 前面已經說了資源的基本概念 不過只是做了和很簡單的介紹 這次我們來點狠的 深入的。前面我雖然也 用了資源 不過只是鼠標光標的 回憶下我們的程序 是那么的簡陋。我們早就想把它給裝點下了吧 不用着急 學 完了這節后 你就可以成為一個雕刻師了 想讓你的窗口咋樣基本都可以了 需要練習了 呵呵 。 以當前我這個Word編輯窗口為例 可以看到一個窗口有很多項的 而我們之前的串口跟這個相比 真可謂 小巫見大巫。前面的程序連最起碼的菜單欄都沒有 真是慚愧呀。 在Vb做界面 簡直就跟畫圖是一樣一樣的 Java中可以在編程時 一個一個組件往窗體對象 JFrame 上 畫 也許有IDE可以手畫的 VC中呢 也可以畫 但是注意的細節明顯比VB要多。其實手畫的過程 只是程序幫 了我們 幫我們寫了資源文件。這和用網頁設計工具是一樣的 我們只顧點鼠標 代碼則是網頁設計工具生成的了。 同樣 其他編程也是這樣 這樣的好處是 一可以讓初學者很容易進入狀態 二是可以加快開發 可以少寫n拖代碼。 壞處是 不懂得底層機制 很多人寫了n久的程序 也只能是比葫蘆畫瓢 寫的程序界面還是自己學習時候的那種樣 式 界面單調死板 開發不出個性界面的。鑒於工具帶來的負面影響 我才給大家從基本說起 雖然我們是用VC 6.0 的環境 但是我還是手寫資源來教大家定義資源文件 並不利用VC中IDE工具。如果大家資源文件寫的很熟練的話 再用VC中的IDE工具 不用去看多余的書 自然一看就知道是怎么回事 到時用起來就是得心應手。說實在話 如果 不理解Windows的一些處理機制 上去直接去學習VC 我敢肯定學一段時間后 大部分人會頭昏腦脹 事倍功半 雖 有收獲 然仍是皮毛 有放棄之想。好了廢話不多說了 言歸正傳。 如上圖 是我這節要實現的效果 上面有菜單欄 其中點擊“查看”可以菜單子菜單項 彈出的有禁用的菜 單、分割線和灰化的菜單項。還有一個我自己做的圖標 xhk字樣的 左上角 。單擊標題欄上的圖標可以彈出系統 菜單 在有的程序 在窗口中擊鼠標右鍵 就可以彈出“快捷菜單” 這些菜單都屬於彈出式菜單。 菜單中的菜單項有好幾種 從資源定義的角度來看 分割用的橫線也是一個菜單項。除橫線外其他菜單項可 以供用戶選擇 也可以設置為“禁止”或“灰化”狀態暫時停用 如果上圖的。 快捷鍵 這個不用說了 大家都知道是做什么用的。菜單項顯示的字符都是在資源文件中定義 至於如何來 響應按鍵則要在消息處理函數中添寫代碼了 本節先不討論怎樣獲取這些消息和處理這些消息 這寫留到下節中完成 本節先常用資源的定義格式說下 先完成界面上的東東。 1. 菜單資源的定義 在資源腳本文件菜單中的定義格式是這樣的 菜單 ID MENU [DISCARDABLE] BEGIN 菜單項的定義 END 也可以這樣定義 菜單 ID MENU [DISCARDABLE] { 菜單項的定義 } “菜單 ID MENU [DISCARDABLE]”可以用來制定菜單的ID值和內存屬性 菜單ID可以是16位 二進制 位 的整數 也可以是字符串。但是如果ID位字符串的話 在程序中引用的時候就要用字符串指針代替菜 單ID值 顯然這樣不太方便 所以在我們經常用整數來做菜單的ID值。MENU關鍵詞后面的DISCARDABLE 是菜單的內存屬性 表示菜單在不再使用的時候可以暫時從內存中釋放以節省內存 是個可選屬性。菜單 項的定義必須在BEGIN和END關鍵詞之內 這兩個關鍵詞也可以用{和}來代替。 菜單項目的定義方法有三類 1. 常用的 MENUITEM 菜單文字,命令ID [,選項列表] 2. 分割線 MENUITEM SEPARATOR 3. 下級菜單 和菜單定義的方式一樣 POPUP 菜單文字 [,選項列表] BEGIN Item-definitions END 下面對這三類加以說明 第一類 菜單文字——顯示在菜單項中的字符串。像上圖中的“被禁用的菜單項”和“被灰化的菜單項”。 命令ID——不同菜單項的標識。當菜單被選中的時候 Windows會向窗口過程發送WM_COMMAND消息 消息的參數就是 這個命令ID。這個可以分辨用戶選中了哪個菜單項 如果想讓兩個菜單項具有相同的功能 可以設置為相同的ID。 選項列表——用來形容菜單項的各種屬性 它可以是下列選項 CHECHKED——表示打上選定標識。 GRAYED——表示菜單項是灰化的。 INACTIVE——表示菜單項是禁用的。 MENUBRREAK或MENUBARBREAK——表示將這個菜單項和以后的那個列到新的列中。 第二類 菜單項之間的分割線 沒什么好說的了。 第三類 彈出式菜單 前文有解釋 這里說下它的選項 GREAYED——灰化。 INACTIV——禁用。 HELP——表示本項和以后的菜單項是右對齊的 像上圖中的“幫助”菜單。 2.快捷鍵的定義 快捷鍵定義是很簡單的 格式如下 快捷鍵 ID ACCELERATORS BEGIN 鍵名,命令ID[,類型][,選項] END BEGIN和END仍然可以用{和}替換。 鍵名——表示加速鍵對應的按鍵 可以有3中定義方式 “^字母” 表示Ctrl鍵加上字母鍵。 “字母” 表示字母 這時類型必須指明VIRTKEY。 數值 表示ASCII碼 這時類型必須為ASCII 命令ID——按下快捷鍵后 Windows就向程序發送此命令ID。 類型——用來指定鍵的定義方式 可以是VIRTKEY和ASCII 分別用來表示“鍵名”字段定義的是虛擬鍵還是ASCII 碼。 選項——可以使Alt,Control或Shift中的單個或多個 如果指定多個 則中間用逗號隔開 表示快捷鍵是按鍵加上 這些控制鍵的組合鍵。 說了這么多 考驗我們的時候終於到了 下面我們就來寫程序了。 兵馬未動 糧草先行 我們先來把界面定義好 定義一個MyMenu.rc的資源文件 內容如下 /**************MyMenu.rc Written By XHK 2009.3.3*************/ #include <resource.h> #define ICO_MAIN 0X1000 //圖標 #define IDM_MAIN 0X2000 //菜單 #define IDA_MAIN 0X2000 //快捷鍵 #define IDM_OPEN 0X4101 //“打開”菜單項 #define IDM_INACTIVE 0X4201 //“被禁用的菜單項” #define IDM_GRAYED 0X4202 //“灰化的菜單項” #define IDM_HELP 0X4301 //“幫助”菜單項 /********The ico file of the window***********/ ICO_MAIN ICON "xhk.ico" /*********************************************/ /**Next is the definition of the Menus**********/ IDM_MAIN menu discardable { popup "文件(&F)" { menuitem "打開(&O)\tCtrl+Alt+O",IDM_OPEN } popup "查看(&V)" { menuitem "被禁用的菜單項",IDM_INACTIVE,INACTIVE menuitem separator menuitem "被灰化的菜單項",IDM_GRAYED,GRAYED } popup "幫助(&H)",HELP { menuitem "幫助主題(&H)\tF1",IDM_HELP } } //下面定義快捷建 IDA_MAIN accelerators { VK_F1,IDM_HELP,VIRTKEY //F1 "O",IDM_OPEN,VIRTKEY,CONTROL,ALT //Ctrl+Alt+O } 把我們用到的資源ico文件xhk.ico也和此文件放到同一目錄下 然后用資源編譯器rc.exe把MyMenu.rc編譯成 MyMenu.res 下面該出兵了 程序代碼 采用最精簡的 /***********MyMenu.c Written By XHK 2009.3.3************/ #include <windows.h> #define ICO_MAIN 0X1000 //圖標 #define IDM_MAIN 0X2000 //菜單 #define IDA_MIAN 0X2000 //快捷鍵 //回調函數 LRESULT WINAPI WinProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) { switch(Msg)//處理消息過程 什么是消息 下節再講 { case WM_DESTROY://響應鼠標單擊關閉按鈕事件 PostQuitMessage(0);//退出消息隊列 至於什么是消息隊列 下節說 return 0;//退出函數 } return DefWindowProc(hWnd,Msg,wParam,lParam); } //主函數 int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd) { char *cName = "myWindow"; char *cCaption = "帶目錄的窗口 - Made By XHK"; WNDCLASSEX wc; HWND hWnd; MSG Msg; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.cbSize = sizeof(WNDCLASSEX); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.hCursor = NULL; wc.hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(ICO_MAIN));//載入圖標 wc.hIconSm = NULL; wc.hInstance = hInstance; wc.lpfnWndProc = WinProc; wc.lpszClassName =(LPSTR)cName; wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClassEx(&wc); hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,cName,cCaption,WS_OVERLAPPEDWINDOW, 200,100,300,200,NULL,LoadMenu(hInstance,MAKEINTRESOURCE (IDM_MAIN)),hInstance,NULL); if(hWnd == NULL) {//容錯處理 MessageBox(NULL,"There's an Error","Error Title",MB_ICONEXCLAMATION|MB_OK); return 0; } ShowWindow(hWnd,nShowCmd);//顯示窗口 UpdateWindow(hWnd); while(GetMessage(&Msg,NULL,0,0)) { TranslateMessage(&Msg);//翻譯消息 DispatchMessage(&Msg);//分派消息 } return Msg.message; } 把此便以為MyMenu.obj,z再和MyMenu.res進行連接成MyMenu.exe 運行看看和我截的圖一樣不。當然你也可以定義 自己想要的界面 不過如果是初學者 可能沒有那么高的悟性吧 不急 慢慢來 你會成為高手的。 本節又是長篇大論 可能勞您心煩 然資源這方面的知識 在網上也不太好找 想介紹簡單點 怕大家日后碰到沒 見過而又不好找 所以我盡量壓縮篇幅 依然是冗余漫長。如果大家能夠看到這里 說明您的耐力是很強的 是做大 事者 相信您有如此精神 一定會光宗耀祖 出人頭地 成就一番輝煌的事業的。 1.9 讓菜單和快捷鍵起作用 上節中我們把界面完成了 可是只是界面 沒有一點作用 這節我們就讓上面的菜單和快捷鍵其作用 上節中已 經介紹了 在我們單擊菜單或者快捷鍵時 Windows會把相應的ID傳給窗口處理過程。這個時候我們只要在消息處理 代碼中加入要處理的代碼之后 那么這個程序就可以響應菜單和快捷鍵了。今天大家又要了解兩個新函數 主要是處 理快捷鍵 有點小特別。首先和其他資源一樣要先載入快捷鍵資源 這里用到LoadAccelerators函數 具體情況如下 LoadAccelerators 函數功能 調入加速鍵表。該函數調入指定的加速鍵表。 函數原型 HACCEL LoadAccelerators HINSTANCE hlnstance LPCTSTR lpTableName 參數 hlnstance:模塊的一個事例的句柄 該模塊的可執行文件中包含將要調入的加速鍵表。 IpTableName:指向一個以空結尾的字符串的指針 該字符串包含了即將調入的加速鍵表的名字。另一種 可選的方案是 該參數可以在加速鍵表資源的低位字中指定資源標識符 而高位字中全零。 MADEINTRESOURCE宏可被用於創建該值。 返回值 若函數調用成功 則返回非零值。若函數調用失敗 則返回值為零。若要獲得更多的錯誤信息 可以調用GetLastError函數。 備注 若加速鍵表尚未裝入 該函數可從指定的可執行文件中將它裝入。從資源中裝入的加速鍵表 在 程序結束時可自動釋放。Windows CE 資源不被拷貝到RAM中 因而不能被修改。 對於加速鍵的消息處理也有點特別 因為程序發現消息來源是快捷鍵就直接把消息發送到窗口處理過程 窗口處 理函數 進行處理 和其他消息不太一樣 所以這次消息處理代碼需要這樣來了 while(GetMessage(&Msg,NULL,0,0)) { if(!TranslateAccelerator(hWnd,hAccel,&Msg)) { TranslateMessage(&Msg);//翻譯消息 DispatchMessage(&Msg);//分派消息 } } return Msg.message; 需要先行判斷是不是快捷鍵消息 不是才進行消息的翻譯和派送。 下面請看TranslateAccelerator的詳細內容 TranslateAccelerator 函數功能 翻譯加速鍵表。該函數處理菜單命令中的加速鍵。該函數將一個WM_KEYDOWN或 WM_SYSKEYDOWN消息翻譯成一個WM_COMMAND或WM_SYSCOMMAND消息 如果在給定的加速鍵 表中有該鍵的入口 然后將WM_COMMAND或WM_SYSCOMMAND消息直接送到相應的窗口處理過程。 TranslateAccelerator直到窗口過程處理完消息后才返回。 函數原型 int TranslateAccelerator HWND hWnd,HACCEL hAccTable LPMSG IpMsg ; 參數 hWnd:窗口句柄 該窗口的消息將被翻譯。 hAccTable:加速鍵表句柄。加速鍵表必須由LoadAccelerators函數調用裝入或由 CreateAccd_eratorTable函數調用創建。 LpMsg:MSG結構指針 MSG結構中包含了從使用GetMessage或PeekMessage函數調用線程消息隊 列中得到的消息內容。 返回值 若函數調用成功 則返回非零值 若函數調用失敗 則返回值為零。若要獲得更多的錯誤信息 可調用GetLastError函數。 備注 為了將該函數發送的消息與菜單或控制發送的消息區別開來 使WM_COMMAND或 WM_SYSCOMMAND消息的wParam參數的高位字值為1。用於從窗口菜單中選擇菜單項的加速鍵組合被 翻譯成WM_SYSCOMMAND消息 所有其他的加速鍵組合被翻譯成WM_COMMAND。若 TransLateAccelerator返回非零值且消息已被翻譯 應用程序就不能調用TranslateMessage函數對消息再 做處理。每個加速鍵不一定都對應於菜單命令。若加速鍵命令對應於菜單項 則WM_INITMEMU和 WM_INITMENUPOPUP消息將被發送到應用程序 就好像用戶正試圖顯示該菜單。然而 如下的任一條件 成立時 這些消息將不被發送 窗口被禁止 菜單項被禁止。 加速鍵組合無相應的窗口菜單項且窗口己被最小化。鼠標抓取有效。有關鼠標抓取消息 參看SetCapture 函數。若指定的窗口為活動窗口且窗口無鍵盤焦點 當窗口最小化時一般是這種情況 TranslatMssage 翻譯WM_SYSDEYUP和WM_SYSKEYDOWN消息而不是WM_KEYUP和WM_KEYDOWN消息。 當按下相應於某菜單項的加速鍵 而包含該菜單的窗口又已被最小化時 TranslateMessage不發送 WM_COMMAND消息。但是 若按下與窗口菜單或某單項的任一項均不對應的加速鍵時 TranslateMessage 將發送一WM_COMMAND消息 即使窗口己被最小化。 既然已經理論了一番 那么下來就實踐了 來 讓我把這可恨有該死的編程代碼貼出來供大家參考下 /*****MyMenu.c Written by XHK 2009.3.3****/ #include <windows.h> #define ICO_MAIN 0X1000 //圖標 #define IDM_MAIN 0X2000 //菜單 #define IDA_MAIN 0X2000 //快捷鍵 #define IDM_OPEN 0X4101 //“打開”菜單項 #define IDM_INACTIVE 0X4201 //“被禁用的菜單項” #define IDM_GRAYED 0X4202 //“灰化的菜單項” #define IDM_HELP 0X4301 //“幫助”菜單項 //回調函數 LRESULT WINAPI WinProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) { switch(Msg)//處理消息過程 什么是消息 下節再講 { case WM_COMMAND: switch(0x0000ffff&wParam) { case IDM_OPEN: MessageBox(hWnd,"你單擊了\"打開\"菜單項","提示",MB_OK); break; case IDM_HELP: MessageBox(hWnd,"你單擊了\"幫助主題\"菜單項","提示",MB_OK); break; } break; case WM_DESTROY://響應鼠標單擊關閉按鈕事件 PostQuitMessage(0);//退出消息隊列 至於什么是消息隊列 下節說 return 0;//退出函數 } return DefWindowProc(hWnd,Msg,wParam,lParam); } //主函數 int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd) { char *cName = "myWindow"; char *cCaption = "帶目錄的窗口 - Made By XHK"; WNDCLASSEX wc; HWND hWnd; HACCEL hAccel;//快捷鍵表句柄 MSG Msg; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.cbSize = sizeof(WNDCLASSEX); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.hCursor = NULL; wc.hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(ICO_MAIN));//載入圖標 wc.hIconSm = NULL; wc.hInstance = hInstance; wc.lpfnWndProc = WinProc; wc.lpszClassName =(LPSTR)cName; wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClassEx(&wc); hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,cName,cCaption,WS_OVERLAPPEDWINDOW, 400,300,300,200,NULL,LoadMenu(hInstance,MAKEINTRESOURCE(IDM_MAIN)),hInstance,NULL); if(hWnd == NULL) {//容錯處理 MessageBox(NULL,"There's an Error","Error Title",MB_ICONEXCLAMATION|MB_OK); return 0; } ShowWindow(hWnd,nShowCmd);//顯示窗口 UpdateWindow(hWnd); //獲取快捷鍵句柄 hAccel=LoadAccelerators(hInstance,MAKEINTRESOURCE(IDA_MAIN)); while(GetMessage(&Msg,NULL,0,0)) { //先判斷是不是快捷鍵消息 if(!TranslateAccelerator(hWnd,hAccel,&Msg)) { TranslateMessage(&Msg);//翻譯消息 DispatchMessage(&Msg);//分派消息 } } return Msg.message; } 大家想寫的寫下練練手 不想的就直接拷吧 如果有錯誤就自己該吧 總之最后 你得使你的程序可以響應擊目 錄和快捷鍵 廢話不說了 就此打住。 1.10 菜單的高級操作和快捷菜單的編程 上節中說道的是菜單的一些基本操作 顯然滿足不了實際上的需要 因為在我們操作程序的時候 有的菜單項有 時是可用的 有時是不可用的 也有可能根據情況動態地添加、刪除和修改菜單 本節將解決這些問題。此外 快捷 菜單往往能大大地提高工作的效率 所以還要說下快捷菜單的東西。 菜單項的添加、刪除、和修改 也就是通過這個API而已 所以這節就是學習這個函數的使用了。 1.添加菜單項 BOOL AppendMenu hMenu hMenu UINT uFlags UINT uIDNewltem LPCTSTR lpNewltem ; 2. 插入菜單項 BOOL InsertMenu(HMENU hMenu,UINt uPosition,UINT uFlags,UINT uIDNewltem,LPCTSTR lpNewltem); 3. 修改菜單項 BOOL ModifyMenu(HMENU hMnu,UINT uPosition,UINT uFlags,UINT uIDNewltem,LPCTSTR IpNewltem); 4. 刪除菜單項 BOOL DeleteMenu(HMENU hMenu,UINT uPosition,UINT uFlags); BOOL RemoveMenu( HMENU hMenu, UINT uPosition, UINT uFlags ); 其實 AppendMenu 和 InsertMenu 函數都是添加菜單項函數 只不過 ApendMenu 是在菜單的最后添加菜單項 Inser tMenu 則是在菜單項中間插入菜單項。 DeleteMenu 和 RemoveMenu 都可以刪除菜單 兩者的不同之處在於 當它們 用於 popup 屬性的菜單項時 DeleteMenu 不僅刪除菜單項 而且將這個 popup 菜單項的所有子項目全部刪除 而 R emoveMenu 函數進菜單項中移去這個 popup 菜單項 整個 popup 菜單在內存中還是存在的。 這些函數的參數基本上都差不多 hMenu 是要操作的菜單的句柄 uPositon 是菜單項的位置。位置的表示方法有 兩種 用命令 ID 定位或用位置索引。函數使用哪一種方法 是有后面的 uFlags 決定的。當 uFlags 為 MF_BYCOMM AND 時 uPosition 應為菜單項的命令 ID 而 uFlags 為 MF_BYPOSITON 時 uPosition 表示菜單項的位置索引 索 引是從 0 開始的 第一個菜單項的索引為 0. AppendMenu 和 InsertMenu 函數中的 uIDNewItem 表示這個新菜單項的命令 ID lpNewmItem 是新菜單項的文 字字符串指針。 ModifyMenu 函數則是修改這兩個參數了。 為了編程上的方便 接下來就把快捷菜單的給說了 然后在同一個程序中實現菜單的高級操作和快捷菜單的操作。 前面也說了 快捷菜單也是彈出時菜單 只不過在鼠標單擊右鍵時彈出 彈出的時候 是在鼠標單擊的位置彈出。 這里要用到一個函數 TrackPopupMenu 函數。 TrackPopupMenu 函數功能 該函數在指定位置顯示快捷菜單 並跟蹤菜單項的選擇。快捷菜單可出現在屏幕上的任何位 置。 函數原型 BOOL TrackPopupMenu HMENU hMenu UINT uFlags int x int y int nReserved HWND hWnd CONST RECT”prcRect 參數 hMenu 被顯示的快捷菜單的句柄。此句柄可為調用CreatePopupMenu創建的新快捷菜單的句柄 也 可以為調用GetSubMenu取得的與一個已存在菜單項相聯系的子菜單的句柄。 uFlags 一種指定功能選項的位標志。用下列標志位之一來確定函數如何水平放置快捷菜單 TPM_CENTERALIGN 若設置此標志 函數將按參數x指定的坐標水平居中放置快捷菜單。 TPM_LEFTALIGN 若設置此標志 函數使快捷菜單的左邊界與由參數X指定的坐標對齊。 TPM_RIGHTALIGN 若設置此標志 函數使快捷菜單的右邊界與由參數X指定的坐標對齊。 用下列標志位之一來確定函數如何垂直放置快捷菜單 TPM_BOTTOMALIGN 若設置此標志 函數使快捷菜單的下邊界與由參數y指定的坐標對齊。 TPM_TOPALIGN 若設置此標志 函數使快捷菜單的上邊界與由參數y指定的坐標對齊。 TPM_VCENTERALIGN 若設置此標志 函數將按參數y指定的坐標垂直居中放置快捷菜單 用下列標志位之一來確定在菜單沒有父窗口的情況下用戶的選擇 TPM_NONOTIFY 若設置此標志 當用戶單擊菜單項時函數不發送通知消息。 TPM_RETURNCMD 若設置此標志 函數將用戶所選菜單項的標識符返回到返回值里。 (補充 當TrackPopupMenu的返回值大於0 就說明用戶從彈出菜單中選擇了一個菜單。以返回的ID 號為參數wParam的值 程序給自己發送了一個WM_SYSCOMMAND消息) 用下列標志位之一來確定在快捷菜單跟蹤哪一個鼠標鍵 TPM_LEFTBUTTON:若設置此標志 用戶只能用鼠標左鍵選擇菜單項。 TPM_RIGHTBUTTON 若設置此標志 用戶能用鼠標左、右鍵選擇菜單項。 X 在屏幕坐標下 快捷菜單的水平位置。 Y:在屏幕坐標下 快捷菜單的垂直位置。 NReserved 保留值 必須為零。 HWnd 擁有快捷菜單的窗口的句柄。此窗口接收來自菜單的所有消息。函數返回前 此窗口不接受來 自菜單的WM_COMMAND消息。 如果在參數uFlags里指定了TPM_NONOTIFY值 此函數不向hWnd標識的窗口發消息。 但必須給 hWnd里傳一個窗口句柄 可以是應用程序里的任一個窗口句柄。 PrcRect 未用。 返回值 如果在參數uFlags里指定了TPM_RETURNCMD值 則返回值是用戶選擇的菜單項的標識符。 如果用戶未作選擇就取消了菜單或發生了錯誤 則退回值是零。如果沒在參數uFlags里指定 TPM_RETURNCMD值 若函數調用成功 返回非零值 若函數調用失敗 返回零。若想獲得更多的錯誤信 息 清調用GetLastError 函數: 備注 Windows CE不支持參數uFlags取下列值 TPM_NONOTIFY TPM_LEFTBUTTON TPM_RIGHTBUTTON。 至於獲取鼠標的位置 可以請出這個函數 GetCursorPos 函數功能 該函數檢取光標的位置 以屏幕坐標表示。 函數原型 BOOL GetCursorPos LPPOlNT IpPoint 參數 IpPint POINT結構指針 該結構接收光標的屏幕坐標。 使用時要先定義一個數據結構 Public Type POINTAPI x As Long y As Long End Type GetCursorPos biao 那么biao.x用來存放當前光標的x軸坐標,biao.y用來存放當前y軸的坐標。 返回值 如果成功 返回值非零 如果失敗 返回值為零。若想獲得更多錯誤信息 請調用GetLastError 函數。 使用TrackPopupMenu要注意的是 彈出的菜單句柄一般為popup類型的 而用LoadMenu函數裝載的並不是popup 類型的 popup只能在第二層或者夠多層中定義 這時就要用GetSubMenu函數來獲取第二層菜單句柄 也就是popup 型的菜單句柄 GetSubMenu這個函數比較簡單 大家看下就會明白的 不多說。 繼上節中的代碼 MyMenu.rc修改為如下代碼 /**************MyMenu.rc Written By XHK 2009.3.3*************/ /*****MyMenu.c Modified by XHK 2009.3.4****/ #include <resource.h> #define ICO_MAIN 0X1000 //圖標 #define IDM_MAIN 0X2000 //菜單 #define IDA_MAIN 0X2000 //快捷鍵 #define IDM_OPEN 0X4101 //“打開”菜單項 #define IDM_INACTIVE 0X4201 //“被禁用的菜單項” #define IDM_GRAYED 0X4202 //“灰化的菜單項” #define IDM_HELP 0X4301 //“幫助”菜單項 #define IDM_SHORTCUT 0X4400 //快捷菜單 #define IDM_APPEND 0X4401 //“添加新菜單項” #define IDM_DELETE 0X4402 //“刪除菜單項” /********The ico file of the window***********/ ICO_MAIN ICON "xhk.ico" /*********************************************/ /**Next is the definition of the Menus**********/ IDM_MAIN menu discardable { popup "文件(&F)" { menuitem "打開(&O)\tCtrl+Alt+O",IDM_OPEN } popup "查看(&V)" { menuitem "被禁用的菜單項",IDM_INACTIVE,INACTIVE menuitem separator menuitem "被灰化的菜單項",IDM_GRAYED,GRAYED } popup "幫助(&H)",HELP { menuitem "幫助主題(&H)\tF1",IDM_HELP } } IDM_SHORTCUT menu discardable { popup " "//不想起名字了 用空格了 { menuitem "添加新菜單項",IDM_APPEND menuitem "刪除菜單項",IDM_DELETE } } //下面定義快捷建 IDA_MAIN accelerators { VK_F1,IDM_HELP,VIRTKEY //F1 "O",IDM_OPEN,VIRTKEY,CONTROL,ALT //Ctrl+Alt+O } 程序代碼文件修改如下 /*****MyMenu.c Written by XHK 2009.3.3****/ /*****MyMenu.c Modified by XHK 2009.3.4****/ #include <windows.h> #define ICO_MAIN 0X1000 //圖標 #define IDM_MAIN 0X2000 //菜單 #define IDA_MAIN 0X2000 //快捷鍵 #define IDM_OPEN 0X4101 //“打開”菜單項 #define IDM_INACTIVE 0X4201 //“被禁用的菜單項” #define IDM_GRAYED 0X4202 //“灰化的菜單項” #define IDM_HELP 0X4301 //“幫助”菜單項 #define IDM_SHORTCUT 0X4400 #define IDM_APPEND 0X4401 #define IDM_DELETE 0X4402 HMENU hMenu;//定義全局變量 方面函數調用 HMENU hMenuShort;//同上 //定義彈出快捷菜單函數 int PopupShortcutMenu(hWnd) { POINT point; GetCursorPos(&point);//獲取鼠標的位置 //彈出菜單 TrackPopupMenu(GetSubMenu(hMenuShort,0),TPM_CENTERALIGN,point.x,point.y,0,(HWND)hWnd,NULL); return 1; } //回調函數 LRESULT WINAPI WinProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) { //HMENU hMenu; //HINSTANCE hInstance; switch(Msg) { case WM_COMMAND: switch(0x0000ffff&wParam) { case IDM_OPEN: MessageBox(hWnd,"你單擊了\"打開\"菜單項","提示",MB_OK); break; case IDM_HELP: MessageBox(hWnd,"你單擊了\"幫助主題\"菜單項","提示",MB_OK); break; case IDM_APPEND: //添加新菜單項 AppendMenu(GetSubMenu(hMenuShort,0),MF_CHECKED,0X4403,"新添加的菜單項"); break; case IDM_DELETE: //刪除菜單項 DeleteMenu(GetSubMenu(hMenuShort,0),0x4403,MF_BYCOMMAND); break; } break; case WM_RBUTTONUP: PopupShortcutMenu(hWnd); break; case WM_DESTROY://響應鼠標單擊關閉按鈕事件 PostQuitMessage(0); return 0;//退出函數 } return DefWindowProc(hWnd,Msg,wParam,lParam); } //主函數 int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd) { char *cName = "myWindow"; char *cCaption = "帶目錄的窗口 - Made By XHK"; WNDCLASSEX wc; HWND hWnd; HACCEL hAccel;//快捷鍵表句柄 MSG Msg; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.cbSize = sizeof(WNDCLASSEX); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.hCursor = NULL; wc.hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(ICO_MAIN));//載入圖標 wc.hIconSm = NULL; wc.hInstance = hInstance; wc.lpfnWndProc = WinProc; wc.lpszClassName =(LPSTR)cName; wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClassEx(&wc); hMenuShort = LoadMenu(hInstance,MAKEINTRESOURCE(IDM_SHORTCUT)); hMenu = LoadMenu(hInstance,MAKEINTRESOURCE(IDM_MAIN)); hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,cName,cCaption,WS_OVERLAPPEDWINDOW, 400,300,300,200,NULL,hMenu,hInstance,NULL); if(hWnd == NULL) {//容錯處理 MessageBox(NULL,"There's an Error","Error Title",MB_ICONEXCLAMATION|MB_OK); return 0; } ShowWindow(hWnd,nShowCmd);//顯示窗口 UpdateWindow(hWnd); //獲取快捷鍵句柄 hAccel=LoadAccelerators(hInstance,MAKEINTRESOURCE(IDA_MAIN)); while(GetMessage(&Msg,NULL,0,0)) { //先判斷是不是快捷鍵消息 if(!TranslateAccelerator(hWnd,hAccel,&Msg)) { TranslateMessage(&Msg);//翻譯消息 DispatchMessage(&Msg);//分派消息 } } return Msg.message; } 給最終效果拍個照 給大家秀秀 不好意思 照的時候 它頭一偏 結果一部沒照到 不過要看的部分都有 大家就將就下了 如果你不將就看的 話 那就自己寫一個出來了 哈。其實我是很希望你們能夠自己寫 不要直接Ctrl+C、Ctrl+V就可以了 哪怕是抄 也希望你能夠親自講那些羞澀的字符敲進去。因為編程時才能發現錯誤 解決了錯誤 你就進步了 如果我上面的代 碼沒有錯的話 你直接復制粘貼就了事了 即使你說你沒一行代碼都看過了 都理解了 但我可以肯定的說你沒有進 步 針對沒有寫過這類程序的人說的 知道的飛過 。謙虛使人進步 你想成為高手嗎 那就虛心地去寫代碼去吧。 1.11對話框資源及其他一些常用資源的使用 本來是想給大家詳細說說常用的每一個資源 然一位讀者說資源那么多 界面也是千變萬化的 就算說完估計也 不會取得什么好的效果 說完大家只會覺得啰嗦 應該留一點讓讀者自己去發現 去寫。想想也是 資源是很多的 寫完簡直就是長篇大論了 估計也許的確夠煩人的 想想這樣 干脆做個了斷算了 本節主要說下對話框資源 其他 的資源如果能用到的就說下 用不到的就不管了 其實就是資源定義 很簡單的 大家可以自己查下就可以解決的。 這節我們的目標是做一個計算器。 界面如上圖 這個用對話框、編輯框控件和按鈕控件資源寫的。下面就對這三種資源給詳細說一下。 我們已經很熟悉了 用MessageBox函數可以輕而易舉地創建一個對話框 不過那只是其中的一類 對話框是用來 給人們“對話”的 而對話的方式不可能只有一種 可以是任意的 所以對話框的樣式是千變萬化的。對話框只是窗 口程序的一個子集 所以對話框和一般的窗口程序沒有太大的區別。而且很好的一點 寫一個對話框窗口 我們可以 很簡單的做到 因為Windows為我們提供了這樣的函數 我只要用一個函數就可以創建一個對話框窗口 像MessageBox 一樣 很簡單地就可以創建一個窗口出來 還有看上面的計算器就是對話框做的窗口。對話框有兩種 模式的和非模 式的。什么是模式的 什么是非模式的呢 模式的對話框 就是對話框彈出后 你只能在對話框中進行操作 而不在 主窗口或其他子窗口操作 像我現在用的word的“插入圖片”這個對話框。 在我插入圖片或者取消插入之前 點其他地方是沒有任何反應的 只有這個對話框返回后 我才能繼續寫或進行 其他操作。知道了什么是模式對話框 根據字面意思也知道什么是非模式對話框了 這里仍以word為例截圖說明。 像這個“查找”對話框 當它彈出來后 我可以繼續進行寫入或者其他操作。主窗口或者其他子窗口不用等待非 模式對話框的返回。 上面說了 建立對話框只需一個函數即可 但對話框有兩種 所以分別用兩個不同的函數來創建。DialogBoxParam 函數用來創建模式對話框 CreateDialogParam函數用來創建非模式對話框。用函數就可以省去普通窗口創建過程中 的注冊、創建、顯示和更新的過程 甚是方便的 建議大家寫簡單的程序就用基於對話框的窗口程序。 Windows在這兩個函數的內部調用CreateWindowEx來建立對話框 使用的風格、大小和位置等參數取自資源中定 義的對話框模版 使用的窗口類則是Windows內部定義的類。窗口過程也被定義到了內部的“對話框管理器”代碼中 Windows在這里處理對話框的大部分消息 對話框管理器在初始化對話框時會根據對話框模版中定義的子窗口控件建 立對話框中所有的子窗口。 用戶程序中的對話框過程是由對話框管理器調用的 在處理消息前 對話框管理器會先調用用戶制定的對話框過 程 再根據對話框過程的返回值決定是否處理它們。 Windows對模式對話框和非模式對話框的吃力有些不同。創建並顯示模態對話框后 Windows會為它在內部建立一 個消息循環 在這個消息循環 在這個消息發送給對話框管理器 對話框管理器在處理在處理消息的過程中會調用用 戶定義的對話框過程 當對話框關閉的時候 Windows退出內建的消息循環 並從DialogBoxParam函數返回。而對於非模式對話框CreatDialogParam函數在創建對話框后直接返回 對話框窗口的消息是通過用戶程序中的消息循環派 送。 由於模式對話框的特征 使得用它來做小程序的主窗口非常方便 因為用DialogBoxParam函數就可以搞定了 這 樣可以省好多代碼 何樂而不為呢 對話框資源定義 在資源腳本中定義對話框的語法是 對話框 ID DIALOG [DISCARDABLE] x坐標,y坐標,寬度,高度 [可選屬性] { 子窗口控件 } 重用的可選屬性 標題文字 CAPTION “文字” 定義顯示在窗口標題欄上的文字 窗口類 CLASS “類名” 定義對話框窗口使用的窗口類 若不定義則使用Windows內建的類 窗口風格 STYLE 風格組合 定義對話框的風格 擴展風格 EXSTYLE 風格組合 定義對話框的擴展風格 字體 FONT 大小 “字體名” 定義對話框包括子窗口的字體 菜單 MENU 菜單ID 對話框使用的菜單 在同一腳本資源文件中定義 我們這個實例在對話框上用到了編輯框控件和按鈕控件 像這些控件都是Windows的一些預定義類 每一個控件也算 是一個窗戶 只不過屬於子窗口罷了 所以所有控件的定義和使用和對話框的定義和使用是大同小異的。同樣 這些 子窗口控件不用我們寫代碼去創建它們 在對話框中使用的時候 我們只要定義到資源文件中就可以了 對話框管 理器會在初始化對話框的時候 根據資源腳本的定義語句自動創建所有的子窗口的。 子窗口的定義方法有 CONTROL 文本,ID,風格,x,y,寬度,高度,[,擴展風格] 或 CONTROL [文本,] ID x,y,寬度,高度,[,風格][,擴展風格] 常用的控件有按鈕 PUSHBUTTON 、默認按鈕 DEEPBUTTON 、復選框 CHECKBOX 、單選按鈕 RADIOBUTTON 編輯 框 EDITTEXT 、組合框 COMBOBOX 、列表框 LISTBOX 、分組框 GROUPBOX 、滾動條 SCROLLBAR 和圖標框 ICON 等。大家用的時候 如果不知道哪個控件的風格或者擴展風格怎么定義 可以到msdn上搜下就知道了 因為 那么多的風格和擴展風格神仙也不一定能記得了 反正我是不記的 給大腦節省點空間記這個英語單詞也比記微軟程 序員定義的宏好的多。 做一個計算器的知識已經准備的差不多了 下載就該練兵了 還是先做界面 先寫資源文件。 /***********MyCalculator.rc**************/ #include <resource.h> #define ICO_MAIN 0X1000 #define DLG_MAIN 1 #define IDB_0 0X4400 //0 #define IDB_1 0X4401 //1 #define IDB_2 0X4402 //2 #define IDB_3 0X4403 //3 #define IDB_4 0X4404 //4 #define IDB_5 0X4405 //5 #define IDB_6 0X4406 //6 #define IDB_7 0X4407 //7 #define IDB_8 0X4408 //8 #define IDB_9 0X4409 //9 #define IDB_PLUS 0X4410 //+ #define IDB_SUB 0X4411 //- #define IDB_EQU 0X4412 //= #define IDB_DOT 0X4413 //. #define IDB_PAS 0X4414 //正負號 #define IDB_EDIT 0x4415 //編輯框 ICO_MAIN ICON "xhk.ico" DLG_MAIN DIALOG 300,150,102,140 STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU CAPTION "我的計算器" FONT 9,"宋體" { //定義編輯框控件 EDITTEXT IDB_EDIT,7,10,86,17,ES_RIGHT PUSHBUTTON "7",IDB_7,5,30,23,17 PUSHBUTTON "8", IDB_8, 38,30,23,17 PUSHBUTTON "9",IDB_9,71,30,23,17 PUSHBUTTON "4",IDB_4,5,52,23,17 PUSHBUTTON "5",IDB_5,38,52,23,17 PUSHBUTTON "6",IDB_6,71,52,23,17 PUSHBUTTON "1",IDB_1,5,74,23,17 PUSHBUTTON "2",IDB_2,38,74,23,17 PUSHBUTTON "3",IDB_3,71,74,23,17 PUSHBUTTON "0",IDB_0,5,92,23,17 PUSHBUTTON "+/-",IDB_PAS,38,92,23,17 PUSHBUTTON ".",IDB_DOT,71,92,23,17 PUSHBUTTON "+",IDB_PLUS,5,114,23,17 PUSHBUTTON "-",IDB_SUB,38,114,23,17 PUSHBUTTON "=",IDB_EQU,71,114,23,17 } 下面還是主程序代碼 /***********MyCalculator.c**************/ #include <windows.h> #include <stdio.h> #define ICO_MAIN 0X1000 #define DLG_MAIN 1 #define IDB_0 0X4400 //0 #define IDB_1 0X4401 //1 #define IDB_2 0X4402 //2 #define IDB_3 0X4403 //3 #define IDB_4 0X4404 //4 #define IDB_5 0X4405 //5 #define IDB_6 0X4406 //6 #define IDB_7 0X4407 //7 #define IDB_8 0X4408 //8 #define IDB_9 0X4409 //9 #define IDB_PLUS 0X4410 //+ #define IDB_SUB 0X4411 //- #define IDB_EQU 0X4412 //= #define IDB_DOT 0X4413 //. #define IDB_PAS 0X4414 //正負號 #define IDB_EDIT 0x4415 //編輯框 int num1=0;//定義了第一個數字 int num2=0;//定義了第二個數字 char s[10];//為了方面參數的傳遞 定義了這個全局變量 完全沒有這個必要 UINT uFlags=1;//標識是否按下了加號或者等號 切換給num1和num2賦值 char oPration='+';//操作符標志 判斷按下的是什么操作符 默認為加 //把字符串轉化成數字 int StrToNum(char * str) { return atoi(str); } //把數字轉化成字符串 char * NumToStr(int nNum) { itoa(nNum,s,10); return s; } //修改編輯框控件的文字 int SetEditValue(int nNum,HWND hEdit) { if(uFlags==1) { num1 = num1*10+nNum;//可以使數字進位 向左移 SetWindowText(hEdit,NumToStr(num1)); } else { num2 = num2*10+nNum;//可以使數字進位 向左移 SetWindowText(hEdit,NumToStr(num2)); } return 0; } LRESULT WINAPI DialogProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) { HICON hIcon; HWND hEdit = GetDlgItem(hWnd,IDB_EDIT); switch(Msg) { case WM_INITDIALOG: //設置圖標 hIcon = LoadIcon(GetModuleHandle("MyCalculator.exe"),MAKEINTRESOURCE(ICO_MAIN)); SendMessage(hWnd,WM_SETICON,ICON_BIG,(long)hIcon); SetWindowText(hEdit,"0.");//讓編輯框控件的內容為"0." return TRUE; case WM_COMMAND: switch(LOWORD(wParam))//LOWORD(wParam)用來取出命令ID { //一下處理過程可以更簡單的 大家自己想想 看怎樣處理好 //自己完成計算器可以滿足支持小數點和正負號 case IDB_0: SetEditValue(0,hEdit); break; case IDB_1: SetEditValue(1,hEdit); break; case IDB_2: SetEditValue(2,hEdit); break; case IDB_3: SetEditValue(3,hEdit); break; case IDB_4: SetEditValue(4,hEdit); break; case IDB_5: SetEditValue(5,hEdit); break; case IDB_6: SetEditValue(6,hEdit); break; case IDB_7: SetEditValue(7,hEdit); break; case IDB_8: SetEditValue(8,hEdit); break; case IDB_9: SetEditValue(9,hEdit); break; case IDB_PLUS: oPration = '+'; uFlags=0; break; case IDB_SUB: oPration = '-'; uFlags=0; break; case IDB_DOT: MessageBox(hWnd,"自己寫代碼讓計算器支持小數點","提示",MB_OK); break; case IDB_PAS: MessageBox(hWnd,"自己寫代碼讓計算器支持負數","提示",MB_OK); break; case IDB_EQU: //按等號 顯示結果 並把num1和num2清零 准備下一次運算 if(oPration=='+') SetWindowText(hEdit,NumToStr(num1+num2)); else SetWindowText(hEdit,NumToStr(num1-num2)); uFlags=1; num1=0; num2=0; break; } break; case WM_CLOSE://關閉消息 EndDialog(hWnd,0);//終止模態對話框 return TRUE; } return FALSE; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { //很簡單的創建了窗口 DialogBoxParam(hInstance,MAKEINTRESOURCE(DLG_MAIN),NULL,DialogProc,0); return 1; } 這樣就寫了一個很簡單的計算器 由於本人生性愚鈍 寫不出很好的算法來 這么簡單的計算器 只是整數的加減法 寫了如此多的代碼 大家想想用簡單的點 並且也支持小數點和正負數 這里權當拋磚引玉了。前面說過了我自己寫 資源文件 是為了讓大家知道 那界面到底是怎樣出來的 如果大家已經熟悉了的話 可以用資源編輯工具直接編輯 那樣更直觀更快。這樣以后有新的控件出現 我也不詳細說明了 大家應該可以根據字面意思就能猜到是什么用的。 下節中我們做一個QQ登陸的界面出來。 1.12 模范QQ登錄界面 在序言中 我曾給大家承諾過 大家看了我寫的這些東西 寫一個類似QQ的聊天工具應該不難 這里就先給大家 寫一個模范QQ登陸窗口的界面來。下面是QQ 2009的界面和我自己模仿的。 當然我模仿的很粗糙 跟騰訊的比起來簡直是大巫見小巫 沒法比呀 畢竟人家是優秀的高級人才 從美觀 才算法 都比我這好數百倍的 我的這個 給人的第一感覺 難看 第二感覺 還是難看 第三感覺 超級難看。大家就將就 下 畢竟我太倉促了 有些細節沒有關注掉 而且有些地方是需要特殊處理的 像控件的背景 但這些特殊處理的 知識我會在后面陸續推出的。大體上和QQ差不多 這節主要是練習一下上節課中講的控件的應用 也就是在資源腳本 中是怎樣定義的 我把注釋寫在代碼里 大家直接看代碼吧。 /**************MyqqWnd.rc Written By XHK****************/ #include <resource.h> #define DLG_MAIN 1 #define ICO_MAIN 0X1000 #define IDB_QQ 2 #define IDE_USER 0X4101 //帳號輸入框 #define IDE_PASS 0X4102 //密碼輸入框 #define IDC_RECORD 0X4201 //記錄密碼復選框 #define IDC_AUTO 0X4202 //自動登錄復選框 #define IDB_CHECK 0X4301 //查殺木馬按鈕 #define IDB_SET 0X4302 //設置按鈕 #define IDB_SUBMIT 0X4303 //提交按鈕 #define IDC_QQ 0X4304 ICO_MAIN ICON "qq.ico" IDB_QQ BITMAP "qq.bmp" DLG_MAIN DIALOG 255,205,222,145 STYLE WS_SYSMENU | WS_MINIMIZEBOX CAPTION "QQ 2009 Made By XHK" FONT 9,"宋體" { CONTROL "AAAAA",IDC_QQ,"Static",SS_BITMAP | WS_CHILD | WS_VISIBLE,0,0,20,40 GROUPBOX "",-1,-1,48,224,75 RTEXT "帳號:",IDC_STATIC,14,55,20,15,SS_CENTERIMAGE EDITTEXT IDE_USER,37,55,120,15,ES_NUMBER LTEXT "注冊新帳號",-1,160,55,50,15,SS_CENTERIMAGE RTEXT "密碼:",-1,14,75,20,15,SS_CENTERIMAGE EDITTEXT IDE_PASS,37,75,120,15,ES_PASSWORD LTEXT "取回密碼",-1,160,75,45,15,SS_CENTERIMAGE CHECKBOX "記住密碼",IDC_RECORD,20,105,50,15 CHECKBOX "自動登錄",IDC_AUTO,100,105,50,15 PUSHBUTTON "查殺木馬",IDB_CHECK,5,125,50,15 PUSHBUTTON "設置",IDB_SET,60,125,50,15 DEFPUSHBUTTON "登錄",IDB_SUBMIT,150,125,50,15 } 其實窗口代碼跟上節的稍微不同 #include <windows.h> #define DLG_MAIN 1 #define ICO_MAIN 0X1000 #define IDB_QQ 2 #define IDE_USER 0X4101 #define IDE_PASS 0X4102 #define IDC_RECORD 0X4201 #define IDC_AUTO 0X4202 #define IDB_CHECK 0X4301 #define IDB_SET 0X4302 #define IDB_SUBMIT 0X4303 #define IDC_QQ 0X4304 LRESULT WINAPI DialogProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) { HICON hIcon; HWND hImage; HBITMAP hBitmap; HINSTANCE hInstance; PAINTSTRUCT ps; HDC hDC; RECT rc; switch(Msg) { case WM_INITDIALOG: //設置窗口的圖標 hInstance = GetModuleHandle(NULL); hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(ICO_MAIN)); SendMessage(hWnd,WM_SETICON,ICON_BIG,(long)hIcon); //加載那個圖片qq.bmp hBitmap = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_QQ)); hImage = GetDlgItem(hWnd,IDC_QQ); SendMessage(hImage,STM_SETIMAGE,IMAGE_BITMAP,(long)hBitmap); break; case WM_PAINT: //下面是給窗口填充為藍色的背景 GetClientRect(hWnd,&rc); hDC = BeginPaint(hWnd,&ps); FillRect(hDC,&rc,(HBRUSH)CreateSolidBrush(RGB(200, 227, 255))); //填充為藍色 EndPaint(hWnd,&ps); return 0; case WM_CLOSE: EndDialog(hWnd,0); return TRUE; } return FALSE; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nShowCmd) { DialogBoxParam(hInstance,MAKEINTRESOURCE(DLG_MAIN),NULL,DialogProc,0); return 1; } 相當簡單的窗口 沒有任何功能 只是界面。像那些圖片按鈕或者是按鈕背景都是需要編程去實現的 而不是在資源 文件中定義的 所以這個界面沒有那種效果。那種效果處理起來 跟qq.bmp的加載 窗口背景色的設置是差不多的 因為任何控件都窗口 Windows在創建它們的時候 都用到了CreatWindows函數 這個過程當然不了解也沒有大的關 系 但只要知道有這么回事就可以了 那樣可以有助於舉一反三 因為理解了它們都是窗口都是同一樣東西 值要會 一個控件特效的設置 其它就都是雷同了 很簡單的 本節主要為了練習資源腳本的定義 為以后做基礎。 1.3 定時器 我們在寫程序處理一些問題時 有時需要在一定時間內重復執行某一個步驟 需要重復執行某個程序或者函數 想 定時關機程序 其實很多程序都需要這樣做的 QQ也需要這樣的處理的 只是大家都不知道它工作細節而已 我這里 說下吧 QQ在一定時間內就到服務上查看有沒有信息的消息 如果有的就提示或者顯示 沒有的話 就不會叫。所以 來說定時器是很重要的 本章雖然是說窗口界面的講解 但是由於定時器的重要的地位 為了以后編寫程序做基礎 這里就要啰嗦一下了。 定時器的定義 Microsoft Windows定時器是一種輸入設備 它周期性地在每經過一個指定的時間間隔后就通知應用程序一次。Windows 定時器是非常重要的 這里只說下編程時怎么來用 不來說它具體的細節和原理的東西 底層和硬件的 相信大家也 不想去學的 。 在寫Windows程序使用定時器時 可以用SetTimer函數想Windows申請一個定時器 要求系統在指定的時間以后“通 知”應用程序 如果申請成功的話 系統會以指定的時間周期調用SetTimer函數指定的回調函數 或者向指定的窗口 過程發送WM_TIMER消息 比如我們設置時間周期為1000ms 1s 那么Windows就會每隔一秒想窗口發送一個WM_TIMER 消息。周期時間是從1~4,294,967,295ms 約50天 。如果要終止定時器 可以用KillTimer函數來取消定時器。 使用定時器時有一點要注意的是WM_TIMER是一個優先級低的消息 Windows在派送消息時 如果隊列中有其 他消息 就不會發送WM_TIMER 直到消息隊列中沒有其他消息 才會發送WM_TIMER 而且如果窗口過程忙於處理某個 消息沒有返回的 使消息隊列中消息積累起來 那么就會丟棄WM_TIMER消息 消息隊列中也不會有多條WM_TIMER消 息 如果小隊列中已經有一條沒有來得及處理的WM_TIMER消息 那么到了定時的時刻 兩天WM_TIMER消息就會被合 成一條。所以應用程序不能依靠定時器來保證某件事情必須在規定時刻被處理 另外 也不能依賴定時器消息計數來 確定過去了多少時間 可以采用時間差 。 今天這個實例采用定時器 不停地交替顯示兩張圖片 我截了不同時刻的圖 我在百度上隨便找了兩張汽車的圖片 加載到程序中顯示 代碼非常簡單 聰明的你應該一下就知道該怎么寫了 不 過我還是貼上我的代碼 算是獻丑了 /***************Timer.rc**************/ #include <resource.h> #define DLG_MAIN 1 #define ICO_MAIN 0X1000 #define IDB_BMP1 0X4101 #define IDB_BMP2 0x4102 #define IDC_BMP 0x4103 ICO_MAIN ICON "XHK.ICO" IDB_BMP1 BITMAP "1.BMP" IDB_BMP2 BITMAP "2.BMP" DLG_MAIN DIALOG 255,205,92,69 STYLE WS_SYSMENU | WS_MINIMIZEBOX CAPTION "Timer" FONT 9,"宋體" { CONTROL "AAAAA",IDC_BMP,"Static",SS_BITMAP | WS_CHILD | WS_VISIBLE,0,0,20,40 } 下面是窗口程序的代碼 也極為簡單 關於各個新見函數 請參加msdn或者網絡吧 老是我翻譯沒什么意思 。 /****************Timer.c*************/ #include <windows.h> #define DLG_MAIN 1 #define IDB_BMP1 0X4101 #define IDB_BMP2 0x4102 #define IDC_BMP 0x4103 #define ICO_MAIN 0x1000 UINT uFlags=1; LRESULT CALLBACK DialogProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam) { HINSTANCE hInstance = GetModuleHandle(NULL); HBITMAP hBitmap; HWND hIdc = GetDlgItem(hWnd,IDC_BMP); HICON hIcon; switch(uMsg) { case WM_INITDIALOG: hBitmap = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BMP1)); hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(ICO_MAIN)); SendMessage(hWnd,WM_SETICON,ICON_BIG,(long)hIcon); SendMessage(hIdc,STM_SETIMAGE,IMAGE_BITMAP,(long)hBitmap); SetTimer(hWnd,0,500,NULL);//申請定時器 return TRUE; case WM_TIMER: //根據情況加載不同的圖片資源 if(uFlags) hBitmap = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BMP1)); else hBitmap = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BMP2)); SendMessage(hIdc,STM_SETIMAGE,IMAGE_BITMAP,(long)hBitmap); uFlags = uFlags ^ 1;//進行異或 若是1則變為0 若是0則變為1 return TRUE; case WM_CLOSE: KillTimer(hWnd,0);//取消定時器 EndDialog(hWnd,0); return TRUE; } return FALSE; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdline, int nShowCmd) { DialogBoxParam(hInstance,MAKEINTRESOURCE(DLG_MAIN),0,DialogProc,0); return 1; } 編譯連接運行的你程序 看看達到預期效果沒 你也可以用定時器制作其他的程序的 展開你的思維 想想什么可以 做的 做點送給朋友也可以 說實話 我就是做了幾張照片循環播放的程序 是為一個女孩子做的 我把她的照片處 理成圖標類型 和bmp類型的 然后加載 設定換圖片的時間 一個很簡單的圖片播放程序就OK了 相信她是會喜歡 的 雖然簡陋 但畢竟是量身定做的嘛。 1.14 簡單的整蠱——窗口抖動程序的實現 一次朋友曾給我傳了個有趣的程序 單擊后振動的窗口飄來飄去 其實也就是不停地移動窗口 后來分析后 才知 道原來只是移動當前活動的窗口而已 想想這個也很好現 就是獲得當前活動的窗口 然后用MoveWindow函數去改變 它的位置不就可以了 原來還以為這個程序有什么特別的呢 后來想也沒什么了 這節我們來寫個這樣的程序 我也 不想復雜的實現 就簡單點。 /********************WobbleWnd.rc***************/ #include <resource.h> #define DLG_MAIN 1 #define ICO_MAIN 0X1000 //#define IDB_BMP 0X4000 ICO_MAIN ICON "XHK.ICO" //IDB_BMP BITMAP "XHK.BMP" DLG_MAIN DIALOG 255,205,100,100 STYLE WS_SYSMENU | WS_MINIMIZEBOX CAPTION "Wobble Window" FONT 9,"宋體" { } 下面是程序實現代碼 /***********WobbleWnd.c*****************/ #include <windows.h> #define DLG_MAIN 1 #define ICO_MAIN 0X1000 LRESULT CALLBACK DialogProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam) { HINSTANCE hInstance = GetModuleHandle(NULL); HICON hIcon; HWND hDestWnd; RECT rc; int i=0; switch(uMsg) { case WM_INITDIALOG: hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(ICO_MAIN)); SendMessage(hWnd,WM_SETICON,ICON_BIG,(long)hIcon); SetTimer(hWnd,0,10,NULL); return TRUE; case WM_TIMER: i=(rand()-rand())/1000; if((hDestWnd=GetForegroundWindow())!=NULL){ GetWindowRect(hDestWnd,&rc); if(rc.top<0||rc.bottom>800) rc.top=100; if(rc.left<0||rc.right>1000) rc.left=100; MoveWindow(hDestWnd,rc.left+i,rc.top+i,rc.right-rc.left,rc.bottom-rc.top,TRUE); } //SendWindowMessage( return TRUE; case WM_CLOSE: KillTimer(hWnd,0);//取消定時器 EndDialog(hWnd,0); return TRUE; } return FALSE; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdline, int nShowCmd) { DialogBoxParam(hInstance,MAKEINTRESOURCE(DLG_MAIN),0,DialogProc,0); srand((unsigned)time(NULL)); return 1; } 代碼是相當簡單的 沒有任何要學習的東西 純粹是為了吸引大家的興趣 讓大家看看自己的學的東西到底能用到什 么地方。當我完成這個之后 把代碼給我同學看 同學們都不相信這么少的代碼就能做到。這個程序如果不要窗口和 資源 寫成控制台的程序 代碼會更少。由於我沒有很好的想這個程序那個隨機的算法 可能窗口會有向左上角移動 的趨勢 而且最終窗口會變得的小。這個問題大家是會解決的 不會編程都可以解決的 何況會編程的你呢。 總結 這樣吧 這章就這樣結束了 當然窗口類程序的編寫 這里說的只是一點皮毛而已 但這些都是基礎 學好了對以后 就會很有幫助的 而且你現在已經可以做出窗口的程序了 是不 說明你已經可以脫離菜鳥級了 Come on吧 很快 你就會成為高手的。俗話說 師傅領進門 修行靠個人 我以前把你領到這里了 你想去哪里就去哪里吧。 |