目录
Windows 窗口
窗口的基本概念
创建一个窗口的流程
句柄
创建窗口
设计注册窗口类
创建窗口
显示和更新窗口
创建消息循环
消息循环
建立消息循环
窗口过程函数
窗口程序模板(多字节)
窗口程序模板(Unicode)
Windows 窗口
Winodws这个操作系统就是由窗口命名的,可见窗口的重要性
窗口的基本概念
通过这篇微软官方文档可以了解到窗口的基本概念:什么是窗口 - Win32 apps | Microsoft Learn
接下来了解一些更深层次的东西
Windows的窗口是由窗口类定义的,窗口类定义了窗口的外观和行为,并将其注册到系统中。每个窗口都是窗口类的一个实例,它们具有相同的属性,但可以拥有不同的内容和状态。
Windows 窗口通常包括以下几个部分:
- 标题栏(Title Bar):位于窗口的顶部,用于显示窗口的标题和一些控制按钮(例如最小化、最大化、关闭等)。
- 菜单栏(Menu Bar):可选的,位于窗口的顶部或程序的主窗口下方,用于显示应用程序的菜单和子菜单。
- 工具栏(Tool Bar):可选的,通常位于窗口的顶部或菜单栏下方,用于提供快捷方式和工具按钮。
- 客户区(Client Area):窗口的主要部分,用于显示应用程序的内容、控件和用户界面。
- 状态栏(Status Bar):位于窗口的底部,用于显示应用程序的状态信息、提示信息等。
除此之外,窗口还可以包含各种控件,例如按钮、文本框、列表框、复选框等,以及自定义的绘图和布局逻辑。
创建一个窗口的流程
- 要有一个窗口类,并且得到一个实例
- 调用系统API注册窗口类,需要指定回调函数
- 再调用系统API创建窗口,会返回一个窗口的句柄
句柄
程序员是不能直接操作窗口的,需要调用系统的API;
当应用程序请求创建一个新的句柄时,操作系统内核会为该句柄分配一段内存空间来存储句柄所引用的对象或资源的相关信息,并返回该句柄给应用程序。对于不同类型的句柄,内核会使用不同的数据结构来管理它们。
句柄(Handle)是在计算机编程中用于表示对象或资源的标识符。在操作系统中,句柄可以被认为是对对象或资源的引用,程序可以通过句柄来访问和操作这些对象或资源。句柄是操作系统提供的一种标识和访问对象或资源的机制,它由操作系统内核来管理
想要访问这些系统资源,需要API+句柄来实现
下面是一些常见的句柄类型及其含义:
- 窗口句柄(HWND):窗口句柄是对窗口对象的引用,可以用于标识和操作特定的窗口。通过窗口句柄,程序可以改变窗口的属性、位置、大小,发送消息到窗口,以及获取窗口的属性和状态等。
- 文件句柄(File Handle):文件句柄是对文件对象的引用,用于表示打开的文件。通过文件句柄,程序可以读取、写入、关闭文件,以及获取文件的属性和状态等。
- 设备句柄(Device Handle):设备句柄是对设备对象的引用,用于表示打开的设备。通过设备句柄,程序可以与设备进行通信,执行设备操作,例如读写设备数据、控制设备行为等。
- 内存句柄(Memory Handle):内存句柄是对内存块的引用,用于表示分配的内存。通过内存句柄,程序可以读写分配的内存区域,释放内存,以及获取内存的相关信息。
创建窗口
创建一个窗口的基本流程:
- 设计注册窗口类
- 常见窗口实例
- 显示窗口
- 更新窗口
- 消息循环
- 窗口注册函数
这里演示的是Unicode的窗口类创建
设计注册窗口类
窗口类
设计窗口
WNDCLASS wc; //创建一个类名为 wc 的窗口类
wc.style = CS_VREDRAW | CS_HREDRAW; //默认填CS_VREDRAM | CS_HREDRAM 的组合,可通过位运算增加风格或删除风格
wc.lfnWndProc = MyWindowProc; //窗口过程函数(窗口回调函数)
wc.cbClsExtra = 0; //默认填0
wc.cbWndExtra = 0; //默认填0
wc.hInstance = hInstance; //标注这个窗口类属于哪个进程,便于从消息队列获取该进程的消息
wc.hIcon = NUll; //图标
wc.hCursor = NULL; //光标
wc.hbrBackground = (HBRUSH)COLOR_HIGHLIGHT;//由于是msdn上预定义的,如果出现类型不匹配直接强转即可。
wc.lpszMenuName = NULL; //菜单
wc.lpszClassName = "MyWindow"; //窗口的类名
参数解释:
- wc.style:成员style控制窗口的某些重要特性,在WINDOWS.H中定义了一些前缀为CS的常量,在程序中可组合使用这些常量.也可把sytle设为0.
- wc.style = CS_HREDRAW | CS_VREDRAW它表示当窗口的纵横坐标发生变化时要重画整个窗口。
- eg:无论你怎样拉动窗口的大小,那行字都会停留在窗口的正中部,而假如把这个参数设为0的话,当改动窗口的大小时,那行字则不一定处于中部了。
- wc.lfnWndProc:窗口过程函数,它将接收Windows发送给窗口的消息,并执行相应的任务。并且必须在模快定义中回调它。WndProc是一个回调函数(详见消息循环)
- wc.cbClsExtra:指定用本窗口类建立的所有窗口结构分配的额外字节数。当有两个以上的窗口属于同一窗口类时,如果想将不同的数据和每个窗口分别相对应。则使用该域很有用。这般来讲,你只要把它们设为0就行了,不必过多考虑。
- wc.hInstance:标识应用程序的实例hInstance,当然,实例名是可以改变的。
- wc.hInstance = MyhInstance ;这一成员可使Windows连接到正确的程序(自己的程序)。
- wc.hIcon:成员hIcon被设置成应用程序所使用图标的句柄,图标是将应用程序最小化时出现在任务栏里的的图标,用以表示程序仍驻留在内存中。Windows提供了一些默认图标,我们也可定义自己的图标,VC里面专有一个制作图标的工具。
- wc.hCursor:定义该窗口产生的光标形状。LoadCursor可返回固有光标句柄或者应用程序定义的光标句柄。IDC_ARROW表示箭头光标.
- wc.hbrBackground:决定Windows用于着色窗口背景的刷子颜色,函数GetStockObject返回窗口的颜色,本程序中返回的是白色,你也可以把它改变为红色等其他颜色.试试看
- wc.lpszMenuName:用来指定菜单名,本程序中没有定义菜单,所以为NULL。
- wc.lpszClassName:指定了本窗口的类名。类名是操作系统识别类的唯一ID
- wc.lpszClassName = "MyWindow";窗口类名必须自定义。
注册窗口类
ATOM Ret = RegisterClass(&ws); //告知操作系统
if (Ret == 0)
{
MessageBox(NULL,"注册窗口类失败","提示",MB_OK);
}
创建窗口
创建窗口函数
HWND CreateWindow(
LPCTSTR lpClassName, // 登记的窗口类名
LPCTSTR lpWindowName, // 用来表明窗口的标题
DWORD dwStyle, // 用来表明窗口的风格,如有无最大化,最小化按纽啊什么的
int x, // 用来表明程序运行后窗口在屏幕中的坐标值。
int y, // 用来表明程序运行后窗口在屏幕中的坐标值。
int nWidth, // 用来表明窗口初始化时(即程序初运行时)窗口的大小,即长度与宽度。
int nHeight, // 用来表明窗口初始化时(即程序初运行时)窗口的大小,即长度与宽度。
HWND hWndParent, // 在创建窗口时可以指定其父窗口,这里没有父窗口则参数值为0。
HMENU hMenu, // 用以指明窗口的菜单,菜单以后会讲,这里暂时为0。
HANDLE hInstance, // 应用程序实例句柄,与程序绑定
LPVOID lpParam // 最后一个参数是附加数据,一般都是0。
);
显示和更新窗口
显示窗口
BOOL ShowWindow( HWND hWnd, // handle to window
int nCmdShow // show state);
更新窗口
BOOL UpdateWindow( HWND hWnd // handle to window);
参数1:窗口句柄,告诉ShowWindow()显示哪一个窗口
参数2:第二个参数则告诉它如何显示这个窗口:最小化(SW_MINIMIZE),普通(SW_SHOWNORMAL),还是最大化 (SW_SHOWMAXIMIZED)。
WinMain在创建完窗口后就调用ShowWindow函数,并把iCmdShow参数传送给这个窗口。
WinMain()调用完ShowWindow后,还需要调用函数UpdateWindow,最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新.且不通过消息循环
创建消息循环
消息循环
Windows为每个正在运行的应用程序都保持一个消息队列。当你按下鼠标或者键盘时,Windows并不是把这个输入事件直接送给应用程序,而是将输入的事件先翻译成一个消息,然后把这个消息放入到这个应用程序的消息队列中去。
消息结构体定义:
typedef struct tagMSG {
HWND hwnd;
UINT message; //消息的类别,也叫消息的ID,编号
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
操作系统将消息封装成MSG结构体投递到消息队列。
消息循环:应用程序的WinMain函数通过执行一段代码从她的队列中来检索Windows送往她的消息。然后WinMain就把这些消息分配给相应的窗口函数以便处理它们,这段代码是一段循环代码,故称为”消息循环”。
拿到消息以后,在通过获取的消息对应处理调用窗口回调函数
建立消息循环
消息循环以GetMessage调用开始,它从消息队列中取出一个消息:
MSG msg; //定义消息名
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ; //翻译消息
DispatchMessage (&msg) ; //
}
return msg.wParam ;
GetMessage函数
BOOL GetMessage(
LPMSG lpMsg, //接收消息的MSG结构的地址
HWND hWnd, //窗口句柄,NULL则表示要获取该应用程序创建的所有窗口的消息;
UINT wMsgFilterMin, //指定消息范围。后面三个参数被设置为默认值
UINT wMsgFilterMax //指定消息范围。后面三个参数被设置为默认值
);
后面三个参数被设置为默认值这就是说你打算接收发送到属于这个应用程序的任何一个窗口的所有消息。
返回值:
- 在接收到除WM_QUIT之外的任何一个消息后,GetMessage()都返回TRUE。
- 如果GetMessage收到一个WM_QUIT消息,则返回FALSE
- 如收到其他消息,则返回TRUE。
TranslateMessage:
消息用GetMessage读入后(注意这个消息可不是WM_QUIT消息),它首先要经过函数TranslateMessage()进行翻译,这个函数会转换成一些键盘消息,它检索匹配的WM_KEYDOWN和WM_KEYUP消息,并为窗口产生相应的ASCII字符消息(WM_CHAR),它包含指定键的ANSI字符.但对大多数消息来说它并不起什么作用,所以现在没有必要考虑它。
DispatchMessage:
要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程。我们在讲到登记窗口类时曾提到过,登记窗口类时,我们曾指定Windows把函数WindosProc作为咱们这个窗口的窗口过程(就是指处理这个消息的东东)。就是说,Windows会调用函数WindowsProc()来处理这个消息。
窗口过程函数
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter);
WM_DESTROY消息是在窗口被销毁时发送的。 在窗口从屏幕上删除后,它被发送到正在被销毁的窗口的窗口过程。此消息首先发送到被销毁的窗口,然后发送到被销毁的子窗口(如果有的话)。 在处理消息期间,可以假定所有子窗口仍然存在。
LRESULT CALLBACK WndProc
(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{
switch (uMsg)
{
case WM_DESTROY: //销毁窗口的消息
PostQuitMessage(0);//退出消息循环
break;
default:
break;
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
窗口程序模板(多字节)
#include <windows.h>
#pragma comment(lib, "winmm") //PlaySound在Winmm.lib这个库中,当然需要链接它。
LRESULT CALLBACK MyWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static wchar_t szAppName[] = L"Hello Win";
//设计窗口类
WNDCLASS ws;
ws.style = CS_HREDRAW | CS_VREDRAW;
ws.lpfnWndProc = &MyWndProc;
ws.cbClsExtra = NULL;
ws.cbWndExtra = NULL;
ws.hInstance = hInstance;
ws.hIcon = LoadIcon(NULL, IDI_APPLICATION);
ws.hCursor = LoadCursor(NULL, IDC_ARROW);
ws.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
ws.lpszMenuName = nullptr;
ws.lpszClassName = szAppName;
//注册窗口类
if (!RegisterClass(&ws))
{
MessageBox(NULL, TEXT("注册窗口失败"), TEXT("错误提示:"), MB_OK);
return 0;
}
//创建窗口
HWND HelloHwnd = CreateWindow(
szAppName,
TEXT("The Hello Program"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL,);
//显示窗口
ShowWindow(HelloHwnd, iCmdShow);
//更新窗口,调用Begin使得画面有效
UpdateWindow(HelloHwnd);
//建立消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); //将msg结构传给Windows,进行一些键盘转换
DispatchMessage(&msg); //使得自动调用消息窗口句柄所属的窗口过程函数
}
return msg.wParam; //结构的wParam字段是传递给PostQuitMessage函数的值(通常是0)。然后return叙述将退出WinMain并终止程序。
}
//实现消息过程函数
LRESULT CALLBACK MyWndProc(
HWND hwnd, //
UINT message, //
WPARAM wParam, //参数wParam表明窗口是非最小化还是非最大化,是最小化、最大化,还是隐藏
LPARAM lParam //lParam参数包含了新窗口的大小,新宽度和新高度均为16位值,合在一起成为32位的lParam。
)
{
PAINTSTRUCT ps; //PAINTSTRUCT结构中包含一些窗口消息处理程序,可以用来更新显示区域的内容。
RECT rect; //矩形结构
HDC hdc; //绘图结构
switch (message)
{
case WM_CREATE:
PlaySound(TEXT("G:\\CR40C++程序设计\\二阶段\\SDK\\Windows程序设计实例\\HELLOWIN\\胜利.wav"), NULL, SND_FILENAME | SND_ASYNC);
return 0;
//当窗口改变时
//对WM_PAINT的处理几乎总是从一个BeginPaint呼叫开始:
//一旦显示区域变得无效(正如在改变大小时所发生的情况一样),WndProc就接收到一个新的WM_PAINT消息。
//WndProc通过呼叫GetClientRect取得变化后的窗口大小,并在新窗口的中央显示文字。
case WM_PAINT:
{
//在设备客户区中央绘制文本
hdc = BeginPaint(hwnd, &ps); //返回用于在客户端区域中绘制的显示设备上下文的句柄,在BeginPaint呼叫中,如果显示区域的背景还未被删除,则由Windows来删除
GetClientRect(hwnd, &rect); //接受窗口改变后的尺寸信息。【x,y,高,宽】
DrawText(
hdc, //BeginPaint传回的设备内容句柄
TEXT("Hello ,Windows 10!"), //要输出的文字
-1, //第三个参数是 -1,指示字符串是以字节0终结的
&rect, //要输出的坐标
DT_SINGLELINE | DT_CENTER | DT_VCENTER //系列位旗标,旗标指示了文字必须显示在一行上,水平方向和垂直方向都位于第四个参数指定的矩形中央
);
EndPaint(hwnd, &ps); //释放ps函数资源,结束绘制请求并释放设备上下文。
}
//不处理:如果窗口消息处理程序不处理WM_PAINT消息(这是很罕见的),
//它们必须被传送给DefWindowProc。DefWindowProc只是依次呼叫BeginPaint和EndPaint,以使显示区域有效。
return 0;
case WM_DESTROY:
PostQuitMessage(0); //发送WM_QUIT以结束消息循环
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam); //操作系统待处理我们不处理的消息
}
窗口程序模板(Unicode)
#include <windows.h>
#pragma comment(lib, "winmm") //PlaySound在Winmm.lib这个库中,当然需要链接它。
LRESULT CALLBACK MyWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "Hello Win";
//设计窗口类
WNDCLASS ws;
ws.style = CS_HREDRAW | CS_VREDRAW;
ws.lpfnWndProc = &MyWndProc;
ws.cbClsExtra = NULL;
ws.cbWndExtra = NULL;
ws.hInstance = hInstance;
ws.hIcon = LoadIcon(NULL, IDI_APPLICATION);
ws.hCursor = LoadCursor(NULL, IDC_ARROW);
ws.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
ws.lpszMenuName = nullptr;
ws.lpszClassName = szAppName;
//注册窗口类
if (!RegisterClass(&ws))
{
MessageBox(NULL, "注册窗口失败", "错误提示:", MB_OK);
return 0;
}
//创建窗口
HWND HelloHwnd = CreateWindow(
szAppName,
"The Hello Program",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL,
);
//显示窗口
ShowWindow(HelloHwnd, iCmdShow);
//更新窗口,调用Begin使得画面有效
UpdateWindow(HelloHwnd);
//建立消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); //将msg结构传给Windows,进行一些键盘转换
DispatchMessage(&msg); //使得自动调用消息窗口句柄所属的窗口过程函数
}
return msg.wParam; //结构的wParam字段是传递给PostQuitMessage函数的值(通常是0)。然后return叙述将退出WinMain并终止程序。
}
//实现消息过程函数
LRESULT CALLBACK MyWndProc(
HWND hwnd, //
UINT message, //
WPARAM wParam, //参数wParam表明窗口是非最小化还是非最大化,是最小化、最大化,还是隐藏
LPARAM lParam //lParam参数包含了新窗口的大小,新宽度和新高度均为16位值,合在一起成为32位的lParam。
)
{
PAINTSTRUCT ps; //PAINTSTRUCT结构中包含一些窗口消息处理程序,可以用来更新显示区域的内容。
RECT rect; //矩形结构
HDC hdc; //绘图结构
switch (message)
{
case WM_CREATE:
PlaySound("G:\\CR40C++程序设计\\二阶段\\SDK\\Windows程序设计实例\\HELLOWIN\\胜利.wav", NULL, SND_FILENAME | SND_ASYNC);
return 0;
//当窗口改变时
//对WM_PAINT的处理几乎总是从一个BeginPaint呼叫开始:
//一旦显示区域变得无效(正如在改变大小时所发生的情况一样),WndProc就接收到一个新的WM_PAINT消息。
//WndProc通过呼叫GetClientRect取得变化后的窗口大小,并在新窗口的中央显示文字。
case WM_PAINT:
{
//在设备客户区中央绘制文本
hdc = BeginPaint(hwnd, &ps); //返回用于在客户端区域中绘制的显示设备上下文的句柄,在BeginPaint呼叫中,如果显示区域的背景还未被删除,则由Windows来删除
GetClientRect(hwnd, &rect); //接受窗口改变后的尺寸信息。【x,y,高,宽】
DrawText(
hdc, //BeginPaint传回的设备内容句柄
"Hello ,Windows 10!", //要输出的文字
-1, //第三个参数是 -1,指示字符串是以字节0终结的
&rect, //要输出的坐标
DT_SINGLELINE | DT_CENTER | DT_VCENTER //系列位旗标,旗标指示了文字必须显示在一行上,水平方向和垂直方向都位于第四个参数指定的矩形中央
);
EndPaint(hwnd, &ps); //释放ps函数资源,结束绘制请求并释放设备上下文。
}
//不处理:如果窗口消息处理程序不处理WM_PAINT消息(这是很罕见的),
//它们必须被传送给DefWindowProc。DefWindowProc只是依次呼叫BeginPaint和EndPaint,以使显示区域有效。
return 0;
case WM_DESTROY:
PostQuitMessage(0); //发送WM_QUIT以结束消息循环
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam); //操作系统待处理我们不处理的消息
}