第一个Windows窗口
首先,自定义窗口类型名和窗口标题,后面要用到,一定不能缺少。
wchar_t szAppClassName[] = L"ChengZiEDU_GUI"; //窗口类型名
wchar_t szWindowName[] = L"这是我的第一个Windows程序"; //窗口标题
接着,是设计窗口类的常规操作,按步骤操作即可。
WNDCLASS wc; //W : Unicode
wc.style = CS_HREDRAW | CS_VREDRAW; //窗口类风格
wc.lpfnWndProc = WindowProc; //窗口处理函数
wc.cbClsExtra = 0; //窗口类的额外扩展空间大小(字节)
wc.cbWndExtra = 0; //窗口的额外扩展空间大小(字节)
wc.hInstance = hInstance; //当前应用程序实例句柄
// NULL 表示使用默认窗口图标和光标图标
wc.hIcon = NULL; //窗口图标句柄
wc.hCursor = NULL; //光标图标句柄(鼠标)
//使用自定义图标
//wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));
//wc.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR));
wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 255)); //背景画刷句柄
wc.lpszMenuName = NULL; //菜单名
wc.lpszClassName = szAppClassName; //窗口类型名
上述程序先设计了一个窗口类 wc,接着对这个窗口的所有参数进行初始化。
我们来看看WNDCLASS的参数:
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;
下面我们来仔细说一下每个参数
1、窗口类样式(style)有,可自行选择,多个样式之间用 “|” 连接
一般设置为: CS_HREDRAW | CS_VREDRAW (水平和竖直发生变化时重绘 )
常量/值 | 说明 |
---|---|
CS_BYTEALIGNCLIENT | 在 (x 方向) 的字节边界上对齐窗口的工作区 。 此样式会影响窗口的宽度及其在显示器上的水平位置。 |
CS_BYTEALIGNWINDOW | 在 (x 方向) 的字节边界上对齐窗口 。 此样式会影响窗口的宽度及其在显示器上的水平位置。 |
CS_CLASSDC | 分配一个设备上下文,供类中的所有窗口共享。 由于窗口类是特定于进程的,因此应用程序多个线程可以创建同一类的窗口。 线程还可以尝试同时使用设备上下文。 发生这种情况时,系统只允许一个线程成功完成其绘图操作。 |
CS_DBLCLKS | 当用户双击鼠标位于属于该类的窗口中时,将双击消息发送到窗口过程。 |
CS_DROPSHADOW | 对窗口启用阴影效果。 通过 SPISETDROPSHADOW_ 打开和关闭效果。 通常,这为小型生存期窗口(例如菜单)启用,以强调它们与其他窗口的 Z 顺序关系。 从具有此样式的类创建的Windows必须是顶级窗口;它们可能不是子窗口。 |
CS_GLOBALCLASS | 指示窗口类是应用程序全局类。 |
CS_HREDRAW | 如果移动或大小调整更改了工作区的宽度,则重新绘制整个窗口。 |
CS_NOCLOSE | 禁用窗口菜单上的 "关闭 "。 |
CS_OWNDC | 为类中的每个窗口分配唯一的设备上下文。 |
CS_PARENTDC | 将子窗口的剪裁矩形设置为父窗口的剪裁矩形,以便子窗口可以绘制父窗口。 具有 CSPARENTDC_ 样式位的窗口从系统的设备上下文缓存接收常规设备上下文。 它不会为子级提供父级的设备上下文或设备上下文设置。 指定 CSPARENTDC_ 可增强应用程序的性能。 |
CS_SAVEBITS | 以位图形式保存此类窗口遮盖的屏幕图像部分。 删除窗口后,系统使用保存的位图还原屏幕图像,包括遮盖的其他窗口。 因此,如果位图使用的内存尚未丢弃,并且其他屏幕操作未使存储的图像失效,则系统不会将 WMPAINT_ 消息发送到隐藏的窗口。 此样式适用于小型窗口 (例如,菜单或对话框) 短暂显示,然后在其他屏幕活动发生前删除。 此样式增加了显示窗口所需的时间,因为系统必须首先分配内存来存储位图。 |
CS_VREDRAW | 如果移动或大小调整更改工作区的高度,则重新绘制整个窗口。 |
2、窗口处理函数
我们要自定义一个窗口处理函数。
LRESULT 的处理结果 long
参数 | 描述 | 类型 |
---|---|---|
hWnd | 窗口句柄(当前窗口的窗口句柄) | HWND (结构体指针) |
uMsg | 消息编号(比如鼠标点击、移动) | UINT (usigned int) |
wParam | 窗口附加信息(比如鼠标坐标) | WPARAM (usigned int) |
lParam | 窗口附加信息 | LPARAM (long) |
例如:
//窗口处理函数
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE: //窗口关闭消息
DestroyWindow(hWnd); //销毁窗口 (调用下面的WM_DESTROY)
break;
case WM_DESTROY: //窗口销毁消息
PostQuitMessage(0); //发出一个退出消息 :WM_QUIT
break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam); //调用操作系统默认消息处理函数
}
LRESULT CALLBACK 函数名(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 是固定格式,下面函数体要自己写,其中WM_CLOSE 和 WM_DESTROY 是一定要有的,记得最后调用一下操作系统默认消息处理函数
3、窗口类的额外扩展空间大小(字节)
设置为 0
4、窗口的额外扩展空间大小(字节)
设置为 0
5、窗口句柄
设置为当前应用程序实例句柄,也就是 WinMain 函数的第一个参数 hInstance。
6、窗口图标句柄
使用默认窗口图标句柄,例如:
wc.hIcon = NULL;
使用自定义窗口图标句柄,例如:
wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));
LoadIcon:加载一个图标文件
第一个参数:应用程序实例句柄
如果是系统资源,传递 NULL
如果是自定义资源,传递 hInstance
第二个参数:资源ID(要和 resource.h 头文件里面的一致),参数类型为 LPCWSTR
MAKEINTRESOURCE() 函数可以将参数类型转换为 LPCWSTR
LoadCursor:加载一个光标文件
第一个参数:应用程序实例句柄
如果是系统资源,传递NULL
如果是自定义资源,传递hInstance
第二个参数:资源ID(要和 resource.h 头文件里面的一致),参数类型为 LPCWSTR
MAKEINTRESOURCE() 函数可以将参数类型转换为 LPCWSTR
7、光标图标句柄
使用默认光标图标句柄,例如:
wc.hCursor = NULL;
使用自定义光标图标句柄,例如:
wc.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR));
8、背景画刷句柄
功能:设置窗口背景颜色,颜色采用 RGB模式
例如:
wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
9、菜单名
wc.lpszMenuName = NULL;
此阶段设置为 NULL
10、窗口类型名
为一开始自定义的窗口类型名
例如:
wc.lpszClassName = szAppClassName;
tips: 很多API都是以 W 开头,这表示它的字符集是 Unicode
注册窗口类使用的是 RegisterClass 函数,我们只需要将前面设计的窗口类名字取地址传进来,然后进行一个简单的条件判断是否注册成功即可。
例如:
//注册窗口类
if (0 == RegisterClass(&wc))
{
MessageBox(NULL, L"此程序不能运行在Windows NT上", L"温馨提示", MB_OK);
return 0;
}
创建窗口使用的是 CreateWindow 函数,再简单初始化一下参数,最后判断一下窗口是否创建成功即可。
例如:
HWND hWnd = CreateWindow(
szAppClassName, //窗口类型名(一开始自定义的类型名)
szWindowName, //窗口标题(一开始自定义的标题)
WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX, //窗口风格
200, //窗口放置坐标的横坐标
200, //窗口放置坐标的纵坐标
800, //窗口的宽度
600, //窗口的高度
NULL, //父窗口,没有的话写 NULL
NULL, //菜单句柄,没有的话写 NULL
hInstance, //应用程序句柄
NULL //创建窗口需要传递的信息,没有的话写 NULL
);
if (hWnd == NULL)
{
MessageBox(NULL, L"创建窗口失败", L"温馨提示", MB_OK);
return 0;
}
用 HWND hWnd 来接收创建的窗口,窗口名可自定义,不一定要为 hWnd 。
下面为系统中 CreateWindow 函数的定义
HWND WINAPI CreateWindow(
In_opt LPCTSTR lpClassName,
In_opt LPCTSTR lpWindowName,
In DWORD dwStyle,
In int x,
In int y,
In int nWidth,
In int nHeight,
In_opt HWND hWndParent,
In_opt HMENU hMenu,
In_opt HINSTANCE hInstance,
In_opt LPVOID lpParam
);
窗口常用风格
风格 | 描述 |
---|---|
WS_CAPTION | 该窗口有一个标题栏 |
WS_SYSMENU | 窗口的标题栏上有一个窗口菜单 |
WS_MAXIMIZEBOX | 窗口有一个最大化按钮,不能与 WS_EX_CONTEXTHELP 样式组合 |
WS_MINIMIZEBOX | 窗口有一个最小化按钮,不能与 WS_EX_CONTEXTHELP 样式组合 |
WS_MAXIMIZE | 窗口最初是最大化的 |
WS_MINIMIZE | 窗口最初是最小化的 |
WS_CHILD | 窗口是一个子窗口,具有这种样式的窗口不能有菜单栏 |
WS_DISABLED | 窗口最初被禁用,禁用的窗口无法接收用户的输入。要在创建窗口后更改此设置,请使用EnableWindow函数。 |
WS_HSCROLL | 窗口有一个水平滚动条 |
WS_VSCROLL | 窗口有一个垂直滚动条 |
WS_ICONIC | 窗口最初是最小化的 |
使用 ShowWindow 函数,将创建的窗口传进来
ShowWindow(hWnd, SW_SHOW); //正常显示
一般设置为正常显示模式 :SW_SHOW
使用 UpdateWindow 函数,将创建的窗口传进来
UpdateWindow(hWnd);
有小伙伴问为什么需要消息循环呢?
因为没有消息循环的话窗口就会一闪而过了
//这里只要收到了 WM_QUIT 的消息,函数就会返回 0 ,窗口和进程都会销毁
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) //NULL获取当前窗口及其子窗口 0,0获取所有消息
{
//将虚拟键消息转为字符消息
TranslateMessage(&msg);
//将虚拟键消息分发给窗口处理函数
DispatchMessage(&msg);
}
这里,我们先定义一个接收窗口消息的变量,然后使用 GetMessage、TranslateMessage、DispatchMessage 函数对消息进行处理,它们缺一不可。
#include //头文件
#include "resource.h" //资源头文件
//窗口处理函数声明
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
{
//设计窗口类
wchar_t szAppClassName[] = L"ChengZiEDU_GUI";
wchar_t szWindowName[] = L"这是我的第一个Windows程序";
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW; //窗口类风格 CS_HREDRAW | CS_VREDRAW 水平和竖直发生变化时重绘
wc.lpfnWndProc = WindowProc; //窗口处理函数
wc.cbClsExtra = 0; //窗口类的额外扩展空间大小(字节)
wc.cbWndExtra = 0; //窗口的额外扩展空间大小(字节)
wc.hInstance = hInstance; //当前应用程序实例句柄
wc.hIcon = NULL; //窗口图标句柄
wc.hCursor = NULL; //光标图标句柄(默认鼠标)
//wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));
//wc.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR));
wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 255)); //背景画刷句柄
wc.lpszMenuName = NULL; //菜单名
wc.lpszClassName = szAppClassName; //窗口类型名
//注册窗口类
if (0 == RegisterClass(&wc))
{
MessageBox(NULL, L"此程序不能运行在Windows NT上", L"温馨提示", MB_OK);
return 0;
}
//创建窗口
HWND hWnd = CreateWindow(
szAppClassName, //窗口类型名(一开始自定义的类型名)
szWindowName, //窗口标题(一开始自定义的标题)
WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX, //窗口风格
200, //窗口放置坐标的横坐标
200, //窗口放置坐标的纵坐标
800, //窗口的宽度
600, //窗口的高度
NULL, //父窗口,没有的话写 NULL
NULL, //菜单句柄,没有的话写 NULL
hInstance, //应用程序句柄
NULL //创建窗口需要传递的信息,没有的话写 NULL
);
if (hWnd == NULL)
{
MessageBox(NULL, L"创建窗口失败", L"温馨提示", MB_OK);
return 0;
}
//显示窗口
ShowWindow(hWnd, SW_SHOW); //正常显示
//更新窗口
UpdateWindow(hWnd);
//消息循环
//这里只要收到了 WM_QUIT 的消息,函数就会返回 0 ,窗口和进程都会销毁
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) //NULL获取当前窗口及其子窗口 0,0获取所有消息
{
//将虚拟键消息转为字符消息
TranslateMessage(&msg);
//将虚拟键消息分发给窗口处理函数
DispatchMessage(&msg);
}
return 0;
}
//窗口处理函数定义
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE: //窗口关闭消息
DestroyWindow(hWnd); //销毁窗口 (调用下面的WM_DESTROY)
break;
case WM_DESTROY: //窗口销毁消息
PostQuitMessage(0); //发出一个退出消息 :WM_QUIT
break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam); //调用操作系统默认消息处理函数
}
可能有的小伙伴不知道怎么自己制作图标和光标,这可不是简单地将一张图片的后缀改为 .ico 和 .cur 就可以做到的,下面我给大家分享一个图标制作软件和一个有着大量免费图标的网站。
icofx
下面是这个软件的网盘链接
链接:https://pan.baidu.com/s/1GMt7uA1k9GbRkJOFJKQFmQ?pwd=czjy
提取码:czjy
下载完解压缩后,将箭头所指的快捷方式复制到桌面即可使用
使用方法:
将图片拖到 icofx 的界面中,然后选择创建图标或光标,接着 CTRL + S 保存即可
首先,将要添加的资源拷贝到工程的文件夹中,如下图
接着,在工程中找到资源文件,按下图操作
然后,选择导入图标(Icon) 或光标 (Cursor)
https://www.iconfont.cn/