Windows API编程04-你的第一个Windows窗口

联系WeChat:i-xiaodi,交流,付费课程学习

窗口是由窗口类来管理的

typedef struct tagWNDCLASSA {
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCSTR    lpszMenuName;
  LPCSTR    lpszClassName;
} WNDCLASSA, *PWNDCLASSA, *NPWNDCLASSA, *LPWNDCLASSA;

完整代码:

#include <Windows.h>

//窗口默认回调函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	//过滤消息进行处理
	switch (uMsg)
	{
		//窗口创建
	case WM_CREATE:
		MessageBox(hwnd, "窗口创建了", "标题", MB_OK);
		return 0;
		//窗口关闭
	case WM_CLOSE:
		MessageBox(hwnd, "窗口关闭了", "标题", MB_OK);
		PostQuitMessage(0);//发送退出消息
		return 0;
	
	default:
		break;
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPSTR lpCmdLine,
	_In_ int nShowCmd)
{
	//1.初始化窗口类
	WNDCLASS wnd = {};

	wnd.style = CS_HREDRAW | CS_VREDRAW;
	wnd.lpfnWndProc = WindowProc;
	wnd.hInstance = hInstance;
	wnd.hbrBackground = (HBRUSH)COLOR_WINDOW;
	wnd.lpszClassName = "Class_xiaodi";

	//2.注册窗口类
	RegisterClass(&wnd);

	//3.创建窗口
	HWND hWnd = CreateWindow(wnd.lpszClassName, 
		"窗口标题-我的第一个程序", 
		WS_OVERLAPPEDWINDOW | WS_VISIBLE, 
		100, 100, 500, 500, 
		NULL,
		NULL, 
		hInstance, 
		NULL);

	//4.显示和更新窗口
	ShowWindow(hWnd, SW_SHOW);
	UpdateWindow(hWnd);

	MSG msg = {};
	//5.消息循环
	while (GetMessage(&msg,0,NULL,NULL))
	{
		//将消息调度到窗口过程。 它通常用于调度 GetMessage 函数检索的消息。
		DispatchMessage(&msg);
	}
	

	return 0;
}


 

在屏幕上显示一个窗口的过程一般包括以下步骤,也就是入口函数WinMain的执行流程。

(1)注册窗口类(RegisterClassEx),在注册之前,要先填写RegisterClassEx函数的参数WNDCLASSEX结构的各个字段。

(2)创建窗口(CreateWindowEx)。

(3)显示窗口(ShowWindow)、刷新窗口客户区(UpdateWindow)。

(4)进入无限的消息获取、分发的循环:获取消息(GetMessage),转换消息(TranslateMessage),将消息分发到回调函数WindowProc进行处理(DispatchMessage)。

窗口类:

style指定窗口类样式,也就是用这个窗口类创建的窗口具有的样式,常见的窗口类样式

 

 CS_NOCLOSE表示禁用关闭按钮,系统菜单的关闭菜单项也会消失。大家把设置窗口类样式一行改为wndclass.style = CS_HREDRAW | CS_VREDRAW |CS_NOCLOSE;看看会发生什么现象,为了关闭程序是不是只能打开任务管理器结束进程呢?

lpfnWndProc指定窗口过程,所有基于这个窗口类创建的窗口都使用这个窗口过程。窗口过程的概念稍后讲解,现在只需要知道程序运行以后会发生很多事件,比如窗口创建、窗口重绘、窗口尺寸改变、鼠标双击、程序关闭等事件,操作系统会把这些事件通知应用程序,应用程序在lpfnWndProc字段指定的窗口过程(就是函数)中处理这些事件,WNDPROC是窗口过程指针类型。

cbClsExtra指定紧跟在WNDCLASSEX结构后面的窗口类附加数据字节数,用来存放自定义数据,可以通过调用GetClassLong或GetClassLongPtr函数来获取这些数据。窗口类附加数据字节数不能超过40字节。

cbWndExtra指定紧跟在窗口实例后面的的窗口附加数据字节数,用来存放自定义数据,可以通过调用GetWindowLong或GetWindowLongPtr函数来获取这些数据。窗口附加数据字节数不能超过40字节。

以上两个字段,以后用到的时候再去理解其含义,没有特别需求,这两个字段设置为0即可

hInstance指定窗口类的窗口过程所属的实例句柄,也就是所属的模块

hIcon指定图标资源句柄,这个图标用于生成可执行文件图标。Windows已经预定义了一些图标,程序也可以使用在资源文件中自定义的图标。这些图标的句柄可以通过调用LoadIcon或LoadImage函数获取(LoadImage函数的用法在以后学习资源文件的时候再讲解)。资源文件在编译的时候会被打包到可执行文件中,LoadIcon函数用于从应用程序实例(模块)中加载指定的图标资源:

资源文件在编译的时候会被打包到可执行文件中,LoadIcon函数用于从应用程序实例(模块)中加载指定的图标资源:

HICON WINAPI LoadIcon(
    _In_opt_ HINSTANCE hInstance,   // 程序实例句柄(模块句柄)
    _In_     LPCTSTR   lpIconName); // 要加载的图标资源的名称

HICON是Windows定义的图标句柄数据类型,Windows为不同的对象定义了不同名称的句柄类型,但是句柄只不过是一个数值,不需要深究。如果函数执行成功,则返回值是所加载图标的句柄;如果函数执行失败,则返回值为NULL。也可以使用一个系统预定义的图标,将hInstance参数设置为NULL,将lpIconName参数设置为

 Cursor指定用这个窗口类创建的窗口所用的光标资源句柄,就是当鼠标在客户区中时的光标形状。Windows预定义了一些光标,程序也可以使用在资源文件中自定义的光标。这些光标的句柄可以通过调用LoadCursor或LoadImage函数获取。LoadCursor函数用于从应用程序实例(模块)中加载指定的光标资源:

LoadCursor函数用于从应用程序实例(模块)中加载指定的光标资源:

HCURSOR WINAPI LoadCursor(
    _In_opt_ HINSTANCE hInstance,       // 程序实例句柄(模块句柄)
    _In_     LPCTSTR   lpCursorName);   // 要加载的光标资源的名称

HCURSOR是Windows定义的光标句柄数据类型

typedef HICON  HCURSOR;

如果函数执行成功,则返回值是所加载光标的句柄;如果函数执行失败,则返回值为NULL。也可以使用一个系统预定义的光标,将hInstance参数设置为NULL,将lpCursorName参数设置为

 

hbrBackground指定用这个窗口类创建的窗口所用的背景画刷句柄,也可以使用标准系统颜色。系统用指定的背景画刷或颜色填充客户区背景。

HBRUSH是Windows定义的画刷句柄类型。GetStockObject函数用于获取备用(或者说库存,实际上就是系统预定义的)画笔、画刷、字体等的句柄:

HGDIOBJ GetStockObject(_In_ int fnObject);

fnObject参数指定备用对象的类型,对于画刷来说,可以是

 

 如果函数执行成功,则返回指定备用对象的HGDIOBJ类型句柄;如果函数执行失败,则返回值为NULL。也可以使用标准系统颜色:

 

 使用标准系统颜色的时候,需要加1,例如wndclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);

不加1其实也没什么区别。

lpszMenuName指定窗口类的菜单资源名称。菜单通常在资源文件中定义,也可以在创建窗口函数CreateWindowEx的参数中指定。如果在这两个地方都没有指定,那么程序就没有菜单。本程序没有使用菜单,所以设置为NULL。

lpszClassName指定窗口类的名称,最大字符个数为256。调用CreateWindowEx函数创建窗口时需要使用这个窗口类名。如果需要获取指定窗口的窗口类名,可以调用GetClassName函数。

应用程序在创建窗口时,必须首先注册窗口类。

窗口类包含了一个窗口的重要信息,例如窗口样式、窗口过程、显示和绘制窗口所需要的信息等,每一个窗口都是一个窗口类的实例。

一个程序可以基于同一个窗口类创建多个窗口实例,同一窗口类的窗口使用同一个窗口过程WindowProc

创建窗口:

	//3.创建窗口
	HWND hWnd = CreateWindow(wnd.lpszClassName, 
		"窗口标题-我的第一个程序", 
		WS_OVERLAPPEDWINDOW | WS_VISIBLE, 
		100, 100, 500, 500, 
		NULL,
		NULL, 
		hInstance, 
		NULL);

1.类名,不用说了

2.窗口标题

3.窗口样式: 

4.参数x和参数y分别指定窗口左上角相对于屏幕左上角的初始水平和垂直位置,以像素为单位,设置为CW_USEDEFAULT(表示由Windows设置为默认值)。

5.窗口宽和高,也可以设置为CW_USEDEFAULT(表示由Windows设置为默认值)

6.hWndParent指定窗口的父窗口。如果是创建子窗口,则需要指定父窗口句柄,以便父子窗口之间进行通信。本程序是顶级重叠窗口,设置为NULL即可。

重叠窗口是指具有标题栏、边框和客户区的顶级窗口,另外还可以有菜单、最小化和最大化按钮以及滚动条等,作为应用程序的主窗口;弹出窗口是一种特殊类型的重叠窗口,通常用于显示对话框、消息框和其他临时窗口。弹出窗口和重叠窗口的主要区别在于弹出窗口的标题栏是可选的,而重叠窗口必须具有标题栏(9.4节将创建一个没有标题栏的弹出窗口)。顶级窗口是指没有WS_CHILD属性的窗口,顶级窗口的父窗口为桌面窗口。重叠窗口和弹出窗口都可以是顶级窗口,它们的坐标定位相对于屏幕左上角,顶级窗口作为一个程序的主窗口。子窗口必须具有父窗口。父窗口可以是重叠窗口、弹出窗口,甚至可以是其他子窗口。子窗口从父窗口的客户区左上角定位,而不是从屏幕左上角定位。可以为子窗口设置标题栏、最小化和最大化按钮、边框和滚动条等,但不能设置菜单。

7.hMenu指定菜单句柄。本程序没有菜单,设置为NULL。如果创建的是子窗口,则该参数设置为子窗口的ID。

8.pParam可以指定为指向某些数据或数据结构的指针。如果没有特别需求,则设置为NULL即可。

如果函数执行成功,则返回新创建窗口的窗口句柄;如果函数执行失败,则返回值为NULL。在Windows系统中,每一个窗口都有一个句柄,在程序中可以使用句柄对窗口进行引用。许多Windows API都以窗口句柄作为参数,通过窗口句柄Windows就可以知道该函数要对哪个窗口进行操作。如果一个程序创建了多个窗口,那么每个窗口都具有不同的窗口句柄。

显示窗口(ShowWindow)和刷新窗口客户区(UpdateWindow)

第一次调用ShowWindow时,nCmdShow参数可以指定为WinMain函数nCmdShow的值(通常为SW_SHOWDEFAULT),表示激活并显示窗口。ShowWindow函数的作用是设置指定窗口的显示状态,如果程序以后需要设置窗口的显示状态,nCmdShow参数可以指定为

如果窗口以前可见,则返回值为非零;如果窗口以前隐藏,则返回值为0

接下来谈一下UpdateWindow函数。UpdateWindow函数通过向窗口发送WM_PAINT消息来更新指定窗口的客户区。该函数将WM_PAINT消息直接发送到窗口的窗口过程,绕过应用程序的消息队列。

消息循环 

程序运行以后会发生很多事件,比如窗口创建、窗口重绘、窗口尺寸改变、鼠标双击、程序关闭等事件,Windows会把这些事件通知应用程序。那么Windows是如何把这些事件通知应用程序的呢?Windows为每个应用程序维护消息队列,事件发生以后,Windows会自动将其转换为消息,并放置在应用程序的消息队列中;应用程序通过调用GetMessage函数从消息队列中获取消息;调用TranslateMessage函数转换消息;调用DispatchMessage函数分发消息到窗口过程,实际上DispatchMessage函数的处理机制是把消息传递给Windows,然后Windows去调用窗口过程;窗口过程处理完一个消息以后,将控制权返回给Windows,然后DispatchMessage函数返回。这一轮操作完成以后,又会进行下一轮的消息获取、转换和分发。

GetMessage函数用于从调用线程的消息队列中获取消息:

BOOL WINAPI GetMessage(
    _Out_    LPMSG lpMsg,           // MSG结构用于存放消息的具体信息
    _In_opt_ HWND  hWnd,            // 要获取哪个窗口的消息
    _In_     UINT  wMsgFilterMin,   // 要获取的消息的最小值
    _In_     UINT  wMsgFilterMax);  // 要获取的消息的最大值

● 第1个参数lpMsg是一个指向MSG结构的指针,用于存放消息的具体信息,即函数在获取一个消息以后,会把这个消息的具体信息存放在这个结构中:

typedef struct tagMSG {
    HWND        hwnd;   // 哪个窗口发生的消息
    UINT        message;// 消息类型,以WM_开头(Windows Message)
    WPARAM      wParam; // 消息参数,其含义取决于具体的消息类型
    LPARAM      lParam; // 消息参数,其含义取决于具体的消息类型
    DWORD       time;   // 消息发生时的时间
    POINT       pt;     // 消息发生时的光标位置,屏幕坐标
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

pt字段是一个POINT结构,表示消息发生时的光标位置,该结构在windef.h头文件中定义如下:

typedef struct tagPOINT
{
    LONG  x;
    LONG  y;
} POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT;

关于WPARAM和LPARAM数据类型的定义如下所示:

typedef UINT_PTR            WPARAM;
typedef LONG_PTR            LPARAM;

#if defined(_WIN64)
    typedef unsigned __int64 UINT_PTR, * PUINT_PTR;
#else
    typedef _W64 unsigned int UINT_PTR, * PUINT_PTR;
#endif

#if defined(_WIN64)
    typedef __int64 LONG_PTR, * PLONG_PTR;
#else
    typedef _W64 long LONG_PTR, * PLONG_PTR;
#endif

如果程序编译为32位,则WPARAM和LPARAM都是一个32位的数值;如果程序编译为64位,则WPARAM和LPARAM都是一个64位的数值。

● 第2个参数hWnd指定要获取哪个窗口的消息。如果hWnd设置为NULL,则函数将获取属于当前线程的所有窗口的消息。

● 第3个参数wMsgFilterMin和第4个参数wMsgFilterMax指定要获取的消息的最小值和最大值。例如WM_PAINT消息实际上就是一个数值,设置要获取的消息的最小值和最大值是为了过滤消息,只对感兴趣的消息进行处理。如果wMsgFilterMin和wMsgFilterMax都设置为0,则函数将获取所有的可用消息,不执行范围筛选。不过,不管如何设置范围,WM_QUIT(程序退出)消息都是可以获取到的。如果获取到的消息不是WM_QUIT(程序退出),则函数返回值为非零;如果获取到的消息是WM_QUIT,则返回值为0;如果函数执行失败,则返回−1。

DispatchMessage函数用于把GetMessage函数获取到的消息分发送到窗口过程:

	MSG msg = {};
	//5.消息循环
	while (GetMessage(&msg,0,NULL,NULL))
	{
		//将消息调度到窗口过程。 它通常用于调度 GetMessage 函数检索的消息。
		DispatchMessage(&msg);
	}

函数返回为窗口过程处理完消息以后的返回值,在窗口过程中处理完一条消息以后通常都是返回0。

窗口过程

消息既可以是(消息)队列消息,也可以是非(消息)队列消息。队列消息是指那些由Windows放入程序的消息队列中的消息。在程序的消息循环中,队列消息被获取,并投递到窗口过程中。非队列消息则是由Windows对窗口过程的直接调用而产生的。队列消息被投递(Post)到消息队列中,而非队列消息则是被发送(Send)到窗口过程。无论在哪种情况下,窗口过程都会为程序处理所有消息(无论是队列消息还是非队列消息)。窗口过程是程序的消息处理中心。

● 队列消息主要由用户的输入产生,主要为按键消息(例如WM_KEYDOWN和WM_KEYUP消息)、由按键产生的字符消息(WM_CHAR)、鼠标移动(WM_MOUSEMOVE)、鼠标单击(WM_LBUTT0ND0WN)等。此外,队列消息还包括计时器消息(WM_TIMER)、重绘消息(WM_PAINT)和退出消息(WM_QUIT)等。

● 非队列消息则包括除队列消息以外的其他所有消息,通常由调用特定的Windows函数引起。例如,当WinMain调用CreateWindowEx 函数时,Windows就会创建窗口,并在创建过程中向窗口过程发送一条WM_CREATE消息;当WinMain调用ShowWindow函数时,Windows又会将WM_SIZE消息和WM_SH0WWIND0W消息发送给窗口过程;接下来,WinMain又对UpdateWindow函数进行调用,这便促使Windows向窗口过程发送一条WM_PAINT消息。另外,一些表示键盘或鼠标输入的队列消息也能够产生非队列消息。例如,当用键盘或鼠标选择某个菜单项时,键盘或鼠标消息会进入消息队列,而最终表明有某菜单项被选择的WM_COMMAND消息却是一个非队列消息。对于各种类型的消息,大家先大致有一个印象即可,后面都会详细介绍。

窗口过程是Windows回调函数(Windows进行调用),它是通过注册窗口类时使用WNDCLASSEX结构的lpfnWndProc字段指定的。窗口过程的定义形式如下:

LRESULT CALLBACK WindowProc(
    _In_ HWND   hwnd,
    _In_ UINT   uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam);

窗口过程的名称可以任意命名,只要不与其他函数名称冲突即可。WNDPROC是窗口过程指针类型:

typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);

● 第1个参数hwnd表示接收消息的窗口的句柄。如果程序基于同一个窗口类创建了多个窗口 (这些窗口的窗口过程相同),hwnd参数将标识这个消息属于哪一个窗口。

● 第2个参数uMsg表示具体的消息类型,例如WM_CREATE、WM_PAINT等。

● 在Win32程序中,最后两个参数wParam和lParam都是32位的消息参数,用于提供关于该消息的更丰富的信息。wParam和lParam参数中所包含的内容依赖于具体的消息类型,例如对于字符消息WM_CHAR,wParam和lParam参数包含按下了哪个字符以及按下次数等信息。对于鼠标移动消息WM_MOUSEMOVE,wParam和lParam参数包含鼠标光标坐标等信息。

WM_CREATE消息

当WinMain调用CreateWindow函数时,Windows就会创建窗口,并在创建过程中向窗口过程发送一条WM_CREATE消息。WM_CREATE消息是窗口过程较早收到的消息之一,程序通常会在这里做一些初始化的工作。

程序处理完WM_CREATE消息以后,应该返回0,表示继续创建窗口;如果返回−1,则窗口将被销毁,程序退出。实际上,大部分消息在处理完以后返回0,有的消息在处理完以后可能需要返回其他值,例如TRUE

WM_CLOSE消息

窗口关闭过程用户单击程序窗口右上角的关闭按钮以后,窗口过程会收到WM_CLOSE消息

PostQuitMessage函数

通常用于处理WM_CLOSE等退出的消息

nExitCode参数指定退出代码,这个参数会用作WM_QUIT消息的wParam参数

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值