1.按钮是什么
在win32窗口中,经常可以看到按钮,点击按钮可以触发各种事件;
创建按钮的函数:
void CreateButton(HWND hwnd) //参数为父窗口句柄,按钮必须属于一个父窗口,因此该函数只要在父窗口创建完成即父窗口的CreateWindow函数调用完成之后就能调用; { HWND hwndPushButton; hwndPushButton = CreateWindow ( TEXT("button"), TEXT("普通按钮"), //WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON, WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON, 10, 10, 80, 20, hwnd, (HMENU)1001, //子窗口ID hAppInstance, //应用程序的句柄,也就是winmian中的参数hinstance NULL); }
可以看到创建按钮和创建普通窗口一样,都是调用CreateWindow函数来实现;
创建窗口需要的步骤:1、创建窗口类;2、注册窗口类;3、创建窗口;
创建按钮直接CreateWindow,省去了前两步;
总结:
按钮本质上就是一个窗口;
按钮是系统定义好的窗口,创建按钮时只需在CreateWindow的第一个参数也就是窗口类名设为TEXT("button")即可;
单选框和多选框也是按钮;
不过是调用CreateWindow时传入的窗口外观样式不同而已;
按钮的窗口外观样式可以在msdn文档中查得到;
hwndCheckBox = CreateWindow ( TEXT("button"), TEXT("复选框"), //WS_CHILD | WS_VISIBLE | BS_CHECKBOX | BS_AUTOCHECKBOX, WS_CHILD | WS_VISIBLE | BS_CHECKBOX |BS_AUTOCHECKBOX , 10, 40, 80, 20, hwnd, (HMENU)1002, //子窗口ID hAppInstance, NULL); hwndRadio = CreateWindow ( TEXT("button"), TEXT("单选按钮"), //WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON | BS_AUTORADIOBUTTON, WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON , 10, 70, 80, 20, hwnd, (HMENU)1003, //子窗口ID hAppInstance, NULL);
子窗口id:
在winmain中创建的窗口CreateWindow函数的倒数第三个参数为菜单句柄;
菜单句柄的作用是给窗口添加菜单;
如果是子窗口,则该参数表示子窗口id,用来在程序中唯一标识该窗口;
将一个整数强转为HMENU类型来传入;
2.按钮事件的处理
1)关于按钮消息处理的原理
按钮本质上是一个窗口;
窗口通过注册的WNDCLASS结构中回调函数来处理事件;
按钮用来点击触发一些事件,就是通过回调函数来实现的;
按钮是系统创建的窗口,其回调函数也是系统创建的,因此并不需要给它提供回调函数;
按钮的WNDCLASS不是我们定义的,是系统预定义好的。
如果我们想知道,系统预定义的WNDCLASS都包含什么样的信息怎么做?
TCHAR szBuffer[0x20]; GetClassName(hwndPushButton,szBuffer,0x20); //获取wndclass结构的类名;参数:1.目标按钮的句柄、2.存结果的缓冲区、3.缓冲区字节数 WNDCLASS wc; GetClassInfo(hAppInstance,szBuffer,&wc); //这个函数可以获取wndclass结构的所有属性; 参数:1.应用程序句柄、2.目标wndclass结构的类名,用上一个函数获取、3.存结果的缓冲区 OutputDebugStringF("-->%s\n",wc.lpszClassName); OutputDebugStringF("-->%x\n",wc.lpfnWndProc);
2)按钮消息处理机制
按钮是一种特殊的窗体,并不需要提供单独的窗口回调函数.
当按钮有事件产生时,会给父窗口消息处理程序发送一个WM_COMMAND消息
也就是说当点击按钮时:
因为点击的是按钮这个窗口,会触发按钮的回调函数,也就是系统提供的WinProc;
系统回调函数处理该消息的方式为:将点击事件转换为WM_COMMAND消息;
父窗口的接收到WM_COMMAND消息后,调用父窗口回调函数来处理;
如果有多个按钮,父窗口的回调函数在处理WM_COMMAND消息时,需要区分到底是哪个按钮被点击;
WM_COMMAND消息会在消息附加信息wParam的低2字节中带上按钮的Id;
按钮的id用来唯一标识程序中的按钮,为按钮创建时的createwindow函数的倒数第三个参数;
WM_COMMAND消息处理:
case WM_COMMAND: { switch(LOWORD(wParam)) { case 1001: MessageBox(hwnd,"Hello Button 1","Demo",MB_OK); return 0; case 1002: MessageBox(hwnd,"Hello Button 2","Demo",MB_OK); return 0; case 1003: MessageBox(hwnd,"Hello Button 3","Demo",MB_OK); return 0; } return DefWindowProc(hwnd,uMsg,wParam,lParam); }
3)带按钮的窗口程序实例
代码:
#include<stdio.h> #include<windows.h> #include "windebug.h" HINSTANCE hAppInstance; //应用程序句柄,因为要使用的地方太多需要定义成全局变量;在winmain函数中给它赋值 //创建按钮 void CreateButton(HWND hwnd) { HWND hwndPushButton; HWND hwndCheckBox; HWND hwndRadio; hwndPushButton = CreateWindow ( TEXT("button"), TEXT("普通按钮"), //WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON, WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON, 10, 10, 80, 20, hwnd, (HMENU)1001, //子窗口ID hAppInstance, //应用程序的句柄, NULL); hwndCheckBox = CreateWindow ( TEXT("button"), TEXT("复选框"), //WS_CHILD | WS_VISIBLE | BS_CHECKBOX | BS_AUTOCHECKBOX, WS_CHILD | WS_VISIBLE | BS_CHECKBOX |BS_AUTOCHECKBOX , 10, 40, 80, 20, hwnd, (HMENU)1002, //子窗口ID hAppInstance, NULL); hwndRadio = CreateWindow ( TEXT("button"), TEXT("单选按钮"), //WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON | BS_AUTORADIOBUTTON, WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON , 10, 70, 80, 20, hwnd, (HMENU)1003, //子窗口ID hAppInstance, NULL); } //声明回调函数 LRESULT CALLBACK WindowProc( IN HWND hwnd, IN UINT uMsg, IN WPARAM wParam, IN LPARAM lParam ); int CALLBACK WinMain( //CALLBACK是一个宏,表示__stdcall,也就是内平栈,win32所有api函数都是该调用约定 HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ){ hAppInstance = hInstance; //给全局变量赋值 //1.告诉window要画一个什么样的窗口 TCHAR className[] = "My First Window"; //窗口的类名 // 创建窗口类的对象 WNDCLASS wndclass = {0}; //一定要先将所有值赋值,否则RegisterClass函数无法起作用; wndclass.hbrBackground = (HBRUSH)COLOR_MENU; //窗口的背景色 wndclass.lpfnWndProc = WindowProc; //窗口过程函数 wndclass.lpszClassName = className; //窗口类的名字 wndclass.hInstance = hInstance; //定义窗口类的应用程序的实例句柄 //2.注册窗口类 RegisterClass(&wndclass); //3.创建窗口类 HWND hwnd = CreateWindow( className, //类名,可以用自己定义的窗口My First Window,也可用系统定义好的窗口例如按钮button TEXT("我的第一个窗口"), //窗口标题 WS_OVERLAPPEDWINDOW, //窗口外观样式 10, //相对于父窗口的X坐标 10, //相对于父窗口的Y坐标 600, //窗口的宽度 300, //窗口的高度 NULL, //父窗口句柄,为NULL NULL, //菜单句柄,为NULL hInstance, //当前应用程序的句柄 NULL); //附加数据一般为NULL if(hwnd == NULL) //是否创建成功 return 0; CreateButton(hwnd); //创建按钮,需要在父窗口创建成功后调用 //4.显示窗口 ShowWindow(hwnd, SW_SHOW); //5.消息循环 MSG msg; while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } //6.回调函数,由操作系统来调用 /* 窗口消息处理程序 窗口回调函数: 1、窗口回调函数处理过的消息,必须传回0. 2、窗口回调不处理的消息,由DefWindowProc来处理. */ LRESULT CALLBACK WindowProc( IN HWND hwnd, IN UINT uMsg, IN WPARAM wParam, IN LPARAM lParam ) { switch(uMsg) { //窗口消息 case WM_CREATE: { DbgPrintf("WM_CREATE %d %d\n",wParam,lParam); CREATESTRUCT* createst = (CREATESTRUCT*)lParam; DbgPrintf("CREATESTRUCT %s\n",createst->lpszClass); return 0; } case WM_MOVE: { DbgPrintf("WM_MOVE %d %d\n",wParam,lParam); POINTS points = MAKEPOINTS(lParam); DbgPrintf("X Y %d %d\n",points.x,points.y); return 0; } case WM_SIZE: { DbgPrintf("WM_SIZE %d %d\n",wParam,lParam); int newWidth = (int)(short) LOWORD(lParam); int newHeight = (int)(short) HIWORD(lParam); DbgPrintf("WM_SIZE %d %d\n",newWidth,newHeight); return 0; } case WM_DESTROY: { DbgPrintf("WM_DESTROY %d %d\n",wParam,lParam); PostQuitMessage(0); return 0; } //键盘消息 case WM_KEYUP: { DbgPrintf("WM_KEYUP %d %d\n",wParam,lParam); return 0; } case WM_KEYDOWN: { DbgPrintf("WM_KEYDOWN %d %d\n",wParam,lParam); return 0; } //鼠标消息 case WM_LBUTTONDOWN: { DbgPrintf("WM_LBUTTONDOWN %d %d\n",wParam,lParam); POINTS points = MAKEPOINTS(lParam); DbgPrintf("WM_LBUTTONDOWN %d %d\n",points.x,points.y); return 0; } //按钮消息 case WM_COMMAND: { switch(LOWORD(wParam)) { case 1001: MessageBox(hwnd,"Hello Button 1","Demo",MB_OK); return 0; case 1002: MessageBox(hwnd,"Hello Button 2","Demo",MB_OK); return 0; case 1003: MessageBox(hwnd,"Hello Button 3","Demo",MB_OK); return 0; } return DefWindowProc(hwnd,uMsg,wParam,lParam); } } return DefWindowProc(hwnd,uMsg,wParam,lParam); }
窗口:
3.分析按钮的事件处理
对于有多个按钮的窗口,有时需要只分析一个按钮事件处理;
例如:自分析上图窗口中的普通按钮,单选按钮和复选按钮不分析;
首先要找到程序入口winmain;
然后找回调函数,在回调函数开始处下断点;
因为回调函数可能需要处理很多消息,需要筛选按钮事件的消息WM_COMMAND;
也就是给断点添加条件;
条件为消息类型为WM_COMMAND,并且wParam所带的窗口id为普通按钮的id;
断点处回调函数的堆栈:
然后确定普通按钮的id:
在od中点W可以查看子窗口信息
然后确定断点条件: