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,
, ,
, ,
hwnd,
(HMENU), //子窗口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 ,
, ,
, ,
hwnd,
(HMENU), //子窗口ID
hAppInstance,
NULL); hwndRadio = CreateWindow (
TEXT("button"),
TEXT("单选按钮"),
//WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON | BS_AUTORADIOBUTTON,
WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON ,
, ,
, ,
hwnd,
(HMENU), //子窗口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 :
MessageBox(hwnd,"Hello Button 1","Demo",MB_OK);
return ;
case :
MessageBox(hwnd,"Hello Button 2","Demo",MB_OK);
return ;
case :
MessageBox(hwnd,"Hello Button 3","Demo",MB_OK);
return ;
}
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,
, ,
, ,
hwnd,
(HMENU), //子窗口ID
hAppInstance, //应用程序的句柄,
NULL); hwndCheckBox = CreateWindow (
TEXT("button"),
TEXT("复选框"),
//WS_CHILD | WS_VISIBLE | BS_CHECKBOX | BS_AUTOCHECKBOX,
WS_CHILD | WS_VISIBLE | BS_CHECKBOX |BS_AUTOCHECKBOX ,
, ,
, ,
hwnd,
(HMENU), //子窗口ID
hAppInstance,
NULL); hwndRadio = CreateWindow (
TEXT("button"),
TEXT("单选按钮"),
//WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON | BS_AUTORADIOBUTTON,
WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON ,
, ,
, ,
hwnd,
(HMENU), //子窗口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 = {}; //一定要先将所有值赋值,否则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, //窗口外观样式
, //相对于父窗口的X坐标
, //相对于父窗口的Y坐标
, //窗口的宽度
, //窗口的高度
NULL, //父窗口句柄,为NULL
NULL, //菜单句柄,为NULL
hInstance, //当前应用程序的句柄
NULL); //附加数据一般为NULL if(hwnd == NULL) //是否创建成功
return ; CreateButton(hwnd); //创建按钮,需要在父窗口创建成功后调用 //4.显示窗口
ShowWindow(hwnd, SW_SHOW); //5.消息循环
MSG msg;
while(GetMessage(&msg, NULL, , ))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return ;
} //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 ;
}
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 ;
}
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 ;
}
case WM_DESTROY:
{
DbgPrintf("WM_DESTROY %d %d\n",wParam,lParam);
PostQuitMessage(); return ;
}
//键盘消息
case WM_KEYUP:
{
DbgPrintf("WM_KEYUP %d %d\n",wParam,lParam); return ;
}
case WM_KEYDOWN:
{
DbgPrintf("WM_KEYDOWN %d %d\n",wParam,lParam); return ;
}
//鼠标消息
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 ;
}
//按钮消息
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
case :
MessageBox(hwnd,"Hello Button 1","Demo",MB_OK);
return ;
case :
MessageBox(hwnd,"Hello Button 2","Demo",MB_OK);
return ;
case :
MessageBox(hwnd,"Hello Button 3","Demo",MB_OK);
return ;
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
窗口:
3.分析按钮的事件处理
对于有多个按钮的窗口,有时需要只分析一个按钮事件处理;
例如:自分析上图窗口中的普通按钮,单选按钮和复选按钮不分析;
首先要找到程序入口winmain;
然后找回调函数,在回调函数开始处下断点;
因为回调函数可能需要处理很多消息,需要筛选按钮事件的消息WM_COMMAND;
也就是给断点添加条件;
条件为消息类型为WM_COMMAND,并且wParam所带的窗口id为普通按钮的id;
断点处回调函数的堆栈:
然后确定普通按钮的id:
在od中点W可以查看子窗口信息
然后确定断点条件: