1. Windows应用程序,操作系统,计算机硬件之间的相互关系
- 1箭头表示操作系统控制输出设备
- 2箭头表示操作系统可以得到输入设备信息
- 3箭头表示应用程序通知操作系统执行具体操作 操作系统提供给应用程序的接口 API
- 4箭头表示输入设备变化告诉应用程序
Windows操作系统提供了1000多种API函数
CreateWindow为程序创建一个窗口的。
ShowWindow:用于显示窗口。
LoadIcon:用于加载图标。
SendMessage:用于发送消息。
句柄:图标句柄(HICON)光标句柄(HCURSOR)画刷句柄(HBRUSH)
2. 消息与消息队列
- MSG结构体:
//消息是由MSG结构体来表示的。
LRESULT CALLBACK WinSunProc(
HWND hwnd, // handle to window表示消息所属的窗口
UINT uMsg, // message identifier指定了消息的标识符
WPARAM wParam, // first message parameter指定消息的附加信息
LPARAM lParam // second message parameter指定消息的附加信息
);
3. WinMain函数的定义:
//WinMain是Windows程序的入口点函数。
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance该程序当前运行的实例的句柄
HINSTANCE hPrevInstance, // handle to previous instance当前实例的前一个实例的句柄
LPSTR lpCmdLine, // command line一个以空终止的字符串,指定传递给应用程序的命令行参数。
int nCmdShow // show state指定程序的窗口应该如何显示
)
4. 窗口的创建
创建一个完整的窗口,需要经过下面几个操作步骤:
1.设计一个窗口类;
2.注册窗口类;
3.创建窗口;
4.显示及更新窗口。
4.1 设计一个窗口类:
typedef struct _WNDCLASS //窗口类
{
//窗口的特征就是由WNDCLASS结构体来定义的。
//设计一个窗口类
WNDCLASS wndcls;
wndcls.cbClsExtra = 0; //为系统中的每一个窗口类管理一个 WNDCLASS 结构。不应使用(应为零)
wndcls.cbWndExtra = 0; //为每一个窗口管理一个内部数据结构,不应使用(应为零)
wndcls.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH); //hbrBackground指定窗口类的背景画刷句柄。背景色
wndcls.hCursor = LoadCursor(NULL, IDC_ARROW); //hCursor 指定窗口类的光标句柄。当鼠标悬停在窗口上时的游标
wndcls.hIcon = LoadIcon(NULL, IDI_QUESTION); //hIcon指定窗口类的图标句柄。框架窗口的图标
wndcls.hInstance = hInstance; //hInstance指定包含窗口过程的程序的实例句柄。自动填充 AfxGetInstanceHandle,应用程序实例句柄由WinMain函数传进来
wndcls.lpfnWndProc = WinSunProc; //函数指针,指向窗口过程函数。窗口进程,必须是 AfxWndProc
wndcls.lpszClassName = "have a happy day"; // lpszClassName 是一个以空终止的字符串,指定窗口类的名字。类名
wndcls.lpszMenuName = NULL; //lpszMenuName是一个以空终止的字符串,指定菜单资源的名字。不应使用(应为NULL)
wndcls.style = CS_HREDRAW | CS_VREDRAW;//style指定这一类型窗口的样式,当窗口水平方向垂直方向上的高度发生变化时,将重新绘制整个窗口。
} WNDCLASS, *PWNDCLASS;
第一个成员变量style指定这一类型窗口的样式,常用的样式如下:
■ CS_HREDRAW
当窗口水平方向上的宽度发生变化时,将重新绘制整个窗口。当窗口发生重绘时,窗口中的文字和图形将被擦除。如果没有指定这一样式,那么在水平方向上调整窗口宽度时,将不会重绘窗口。
■ CS_VREDRAW
当窗口垂直方向上的高度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,那么在垂直方向上调整窗口高度时,将不会重绘窗口。
■ CS_NOCLOSE
禁用系统菜单的Close命令,这将导致窗口没有关闭按钮。
■ CS_DBLCLKS
当用户在窗口中双击鼠标时,向窗口过程发送鼠标双击消息。
4.2 注册窗口类:
//在设计完窗口类(WNDCLASS)后,需要调用 RegisterClass 函数对其进行注册,在注册成功后,才可以创建该类型的窗口。
RegisterClass(&wndcls);
4.3 创建窗口:
//创建窗口,定义一个变量用来保存成功创建窗口后返回的句柄
HWND hwnd;
hwnd = CreateWindow("have a happy day", "hello",WS_OVERLAPPEDWINDOW, 10, 10, 500, 500, NULL, NULL, hInstance, NULL);
- lpClassName指定窗口类的名称。
- lpWindowName指定窗口的名字。
- dwStyle 指定创建的窗口的样式。
- dwStyle参数:
/*
* Common Window Styles
*/
#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | \
WS_CAPTION | \
WS_SYSMENU | \
WS_THICKFRAME | \
WS_MINIMIZEBOX | \
WS_MAXIMIZEBOX)
#define WS_POPUPWINDOW (WS_POPUP | \
WS_BORDER | \
WS_SYSMENU)
#define WS_CHILDWINDOW (WS_CHILD)
要注意区分 WNDCLASS 中的 style 成员与CreateWindow函数的dwStyle参数,前者是指定窗口类的样式,基于该窗口类创建的窗口都具有这些样式,后者是指定某个具体的窗口的样式。WS_OVERLAPPEDWINDOW是多种窗口类型的组合。
-
窗口类型:
■ WS_OVERLAPPED:产生一个层叠的窗口,一个层叠的窗口有一个标题栏和一个边框。
■ WS_CAPTION:创建一个有标题栏的窗口。
■ WS_SYSMENU:创建一个在标题栏上带有系统菜单的窗口,要和 WS_CAPTION类型一起使用。
■ WS_THICKFRAME:创建一个具有可调边框的窗口。
■ WS_MINIMIZEBOX: 创 建 一 个 具 有 最 小 化 按 钮 的 窗 口, 必 须 同 时 设 定WS_SYSMENU类型。
■ WS_MAXIMIZEBOX: 创 建 一 个 具 有 最 大 化 按 钮 的 窗 口, 必 须 同 时 设 定WS_SYSMENU类型。 -
x、y、nWidth、nHeight :分别指定窗口左上角的 x 坐标、y坐标、窗口的宽度和高度。
如果参数x 被设为 CW_USEDEFAULT,那么系统为窗口选择默认的左上角坐标并忽略y参数。如果参数nWidth被设为CW_USEDEFAULT,那么系统为窗口选择默认的宽度和高度,参数nHeight被忽略。 -
参数hWndParent指定被创建窗口的父窗口句柄。
对父窗口的操作对子窗口的影响 -
参数hMenu指定窗口菜单的句柄。
-
参数hInstance指定窗口所属的应用程序实例的句柄。
-
参数lpParam:作为WM_CREATE消息的附加参数lParam传入的数据指针。
-
如果窗口创建成功,CreateWindow函数将返回系统为该窗口分配的句柄,否则,返回NULL。注意,在创建窗口之前应先定义一个窗口句柄变量来接收创建窗口之后返回的句柄值。
4.4 显示及更新窗口
//显示及刷新窗口
ShowWindow(hwnd, SW_SHOWNORMAL);
UpdateWindow(hwnd);
1. 显示窗口
调用函数ShowWindow来显示窗口:
- hWnd就是在上一步骤中成功创建窗口后返回的那个窗口句柄。
- nCmdShow指定了窗口显示的状态:
■ SW_HIDE:隐藏窗口并激活其他窗口。
■ SW_SHOW:在窗口原来的位置以原来的尺寸激活和显示窗口。
■ SW_SHOWMAXIMIZED:激活窗口并将其最大化显示。
■ SW_SHOWMINIMIZED:激活窗口并将其最小化显示。
■ SW_SHOWNORMAL:激活并显示窗口。如果窗口是最小化或最大化的状态,则系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口的时候应该指定此标志。
2. 更新窗口
在调用ShowWindow函数之后,紧接着调用UpdateWindow来刷新窗口。
UpdateWindow函数的原型声明如下:
BOOL UpdateWindow(
HWND hWnd // CreateWindow 返回值
);
5. 消息循环
在创建窗口、显示窗口、更新窗口后,需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。
从消息队列中取出消息,需要调用GetMessage()函数,函数的原型声明如下:
BOOL GetMessage(
LPMSG lpMsg, // 指向一个消息结构体
HWND hWnd, // 接收消息的windows NULL表示所有窗口
UINT wMsgFilterMin, // 过滤消息
UINT wMsgFilterMax
);
/*
- lpMsg 指向一个消息(MSG)结构体。
- hWnd指定接收属于哪一个窗口的消息。
- wMsgFilterMin指定要获取的消息的最小值,通常设置为0。
- wMsgFilterMax指定要获取的消息的最大值。
如果wMsgFilterMin wMsgFilter Max都设置为0,则接收所有消息。
*/
GetMessage函数接收到除WM_QUIT外的消息均返回非零值。对于WM_QUIT消息,该函数返回0。如果出现了错误,该函数返回-1,例如,当参数hWnd是无效的窗口句柄或lpMsg是无效的指针时,该函数返回-1。
通常编写的消息循环代码如下:
//定义消息结构体,开始消息循环
MSG msg;
BOOL bRet;
//while (GetMessage(&msg, NULL, 0, 0))
while ((bRet = GetMessage(&msg, hwnd, 0, 0)) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
return -1;
}
else
{
TranslateMessage(&msg);//用于将虚拟键消息转换为字符消息。
DispatchMessage(&msg);//分派一个消息到窗口过程,由窗口过程函数对消息进行处理。
}
}
return msg.wParam;
TranslateMessage 函数用于将虚拟键消息转换为字符消息。
字符消息被投递到调用线程的消息队列中,当下一次调用GetMessage 函数时被取出。当我们敲击键盘上的某个字符键时,系统将产生 WM_KEYDOWN 和 WM_KEYUP 消息。这两个消息的附加参数(wParam和lParam)包含的是虚拟键代码和扫描码等信息,而我们在程序中往往需要得到某个字符的ASCII 码,TranslateMessage 这个函数就可以将WM_KEYDOWN 和 WM_KEYUP消息的组合转换为一条WM_CHAR消息(该消息的wParam附加参数包含了字符的ASCII码),并将转换后的新消息投递到调用线程的消息队列中。(注意,TranslateMessage函数并不会修改原有的消息,它只产生新的消息并投递到消息队列中。)
DispatchMessage 函数分派一个消息到窗口过程,由窗口过程函数对消息进行处理。
DispachMessage实际上是将消息回传给操作系统,由操作系统调用窗口过程函数对消息进行处理(响应)。
Windows应用程序,操作系统,计算机硬件之间的相互关系:
Windows应用程序的消息处理机制:
(1)操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中。
(2)应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息。在取出消息后,应用程序可以对消息进行一些预处理,例如,放弃对某些消息的响应,或者调用TranslateMessage产生新的消息。
(3)应用程序调用DispatchMessage,将消息回传给操作系统。消息是由MSG结构体对象来表示的,其中就包含了接收消息的窗口的句柄。因此,DispatchMessage函数总能进行正确的传递。
(4)系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理(即“系统给应用程序发送了消息”)。
从消息队列中获取消息还可以调用PeekMessage函数,该函数的原型声明如下所示:
BOOL PeekMessage(
LPMSG lpMsg, // 指向一个消息结构体
HWND hWnd, // 接收消息的windows NULL表示所有窗口
UINT wMsgFilterMin, // 过滤消息
UINT wMsgFilterMax,
UINT wRemoveMsg
);
/*
lpMsg 指向一个消息(MSG)结构体。
hWnd指定接收属于哪一个窗口的消息。
wMsgFilterMin指定要获取的消息的最小值,通常设置为0。
wMsgFilterMax指定要获取的消息的最大值。如果wMsgFilterMin和wMsgFilter Max都设置为0,则接收所有消息。
最后1个参数指定消息获取的方式:
PM_NOREMOVE:消息将不会从消息队列中被移除。
PM_REMOVE:消息将从消息队列中被移除(与GetMessage 函数的行为一致)。
*/
发送消息可以使用 SendMessage 和 PostMessage 函数。SendMessage将消息直接发送给窗口,并调用该窗口的窗口过程进行处理。在窗口过程对消息处理完毕后,该函数才返(SendMessage 发送的消息为不进队消息)。PostMessage函数将消息放入与创建窗口的线程相关联的消息队列后立即返回。除了这两个函数外,还有一个PostThreadMessage函数,用于向线程发送消息,对于线程消息,MSG结构体中的hwnd成员为NULL。
6. 编写窗口过程函数
一个Windows应用程序的主要代码部分就集中在窗口过程函数中。
编写一个窗口过程函数,用于处理发送给窗口的消息。
窗口过程函数的声明形式如下:
int MessageBoxA(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
提示: 系统通过窗口过程函数的地址(指针)来调用窗口过程函数,而不是名字。
//编写窗口过程函数
LRESULT CALLBACK WinProc(
HWND hwnd, // handle to window,消息的窗口句柄
UINT uMsg, // message identifier,消息代码
WPARAM wParam, // first message parameter,消息代码的附加参数
LPARAM lParam // second message parameter,消息代码的附加参数
)
窗口过程函数的名字可以随便取,如WinProc,但函数定义的形式必须和上述声明的形式相同。
在窗口过程函数内部使用switch/case语句来确定窗口过程接收的是什么消息,以及如何对这个消息进行处理:
{
switch (uMsg)
{
case WM_CHAR://当用户在窗口中按下一个字符键,程序将得到一条WM_CHAR消息
char szChar[20];
sprintf_s(szChar, sizeof(szChar), "char code is %d", wParam);
MessageBox(hwnd, szChar, "char", 0);//弹出一个包含了显示信息的消息框
break;
case WM_LBUTTONDOWN://当用户在窗口中按下鼠标左键时,将产生WM_LBUTTONDOWN消息调用MessageBox,函数弹出一个提示信息,告诉用户“点击了鼠标”
MessageBox(hwnd, "mouse clicked", "message", 0);
HDC hdc;//定义了一个类型为HDC的变量hdc
hdc = GetDC(hwnd);//用hdc保存GetDC函数返回的与特定窗口相关联的DC的句柄。不能在响应WM_PAINT消息时调用
TextOut(hdc, 0, 50, "HAVE A NICE DAY!", strlen("HAVE A NICE DAY!"));//TextOut函数利用得到的DC句柄在指定的位置(x坐标为0,y坐标为50)输出一行文字
ReleaseDC(hwnd, hdc);//如果使用GetDC函数来得到DC的句柄,那么在完成图形操作后,必须调用ReleaseDC函数来释放DC所占用的资源,否则会引起内存泄漏。
break;
case WM_PAINT://对WM_PAINT消息进行处理。当窗口客户区的一部分或者全部变为“无效”时,系统会发送WM_PAINT消息,通知应用程序重新绘制窗口。
HDC hDC;
PAINTSTRUCT ps;
hDC = BeginPaint(hwnd, &ps);//调用BeginPaint函数得到DC的句柄。BeginPaint函数的第1个参数是窗口的句柄,第二个参数是PAINTSTRUCT结构体的指针,该结构体对象用于接收绘制的信息。BeginPaint只能在响应WM_PAINT消息时调用
TextOut(hDC, 0, 0, "https://blog.csdn.net/qq_44974888/article/details/124137129?spm=1001.2014.3001.5501", strlen("https://blog.csdn.net/qq_44974888/article/details/124137129?spm=1001.2014.3001.5501"));//TextOut函数在(0,0)的位置输出一个网址
EndPaint(hwnd, &ps);//BeginPaint函数得到的DC,必须用EndPaint函数去释放
break;
case WM_CLOSE://当用户单击窗口上的关闭按钮时,系统将给应用程序发送一条WM_CLOSE 消息。
if (IDYES == MessageBox(hwnd, "是否退出程序?", "warning", MB_YESNO))
{
DestroyWindow(hwnd);//调用DestroyWindow函数销毁窗口
}//DestroyWindow 函数在销毁窗口后会向窗口过程发送 WM_DESTROY 消息。注意,此时窗口虽然销毁了,但应用程序并没有退出。
break;
case WM_DESTROY://DestroyWindow函数在销毁窗口后,会给窗口过程发送WM_DESTROY消息,在该消息的响应代码中调用PostQuitMessage函数
PostQuitMessage(0);//PostQuitMessage函数向应用程序的消息队列中投递一条WM_QUIT消息并返回
break;//GetMessage函数只有在收到WM_QUIT消息时才返回0,此时消息循环才结束,程序退出
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);//对WM_CLOSE消息的响应并不是必须的,如果应用程序没有对该消息进行响应,系统将把这条消息传给DefWindowProc函数,那么DefWindowProc函数则调用DestroyWindow函数来响应这条WM_CLOSE消息。
}
return 0;
}
DestroyWindow函数在销毁窗口后,会给窗口过程发送WM_DESTROY消息,我们在该消息的响应代码中调用PostQuitMessage函数。PostQuitMessage函数向应用程序的消息队列中投递一条WM_QUIT消息并返回。 GetMessage函数只有在收到WM_QUIT消息时才返回0,此时消息循环才结束,程序退出。要想让程序正常退出,我们必须响应 WM_DESTROY 消息,并在消息响应代码中调用PostQuitMessage,向应用程序的消息队列中投递WM_QUIT消息。传递给PostQuitMessage函数的参数值将作为WM_QUIT消息的wParam参数,这个值通常用作WinMain函数的返回值。
7. 总结(创建一个Win32应用程序的步骤)
(1)编写WinMain函数,可以在MSDN上查找并复制。
(2)设计窗口类(WNDCLASS)。
(3)注册窗口类。
(4)创建窗口。
(5)显示并更新窗口。
(6)编写消息循环。
(7)编写窗口过程函数。窗口过程函数的语法可通过 MSDN 查看 WNDCLASS 的lpfnWndProc成员变量,在这个成员的解释中可以查到。