以前会一点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) ; //执行缺省消息 }