1)简单介绍创建应用程序的步骤
1.设计一个窗口类
2.注册这个窗口类
3.创建应用程序窗口
4.更新显示窗口
5.应用程序消息循环
2)下面根据这个步骤进行创建一个应用程序窗口吧(编译器为VS2017)
/* 头文件 */
#include <windows.h>
/* 全局变量 */
WCHAR g_lpszClassName[] = L"CLASSNAME";
WCHAR g_lpszWindowName[] = L"哈喽,新的征程";
/* 函数声明 */
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
/* 应用程序主函数 */
INT APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, INT nCmdShow)
{
/* 1.设计一个窗口类 */
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.lpfnWndProc = WndProc;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = g_lpszClassName;
wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
/* 2.注册这个窗口类 */
if (RegisterClassEx(&wcex) == ((ATOM)0))
{
MessageBox(NULL, L"注册窗口类失败!", L"错误", MB_YESNO | MB_ICONERROR);
exit(-1);
}
/* 3.创建窗口 */
HWND hWnd = CreateWindowEx(NULL, g_lpszClassName, g_lpszWindowName, WS_OVERLAPPEDWINDOW, 10, 10, 800, 800, NULL, NULL, hInstance, NULL);
if (hWnd == NULL)
{
MessageBox(NULL, L"创建窗口失败!", L"错误", MB_YESNO | MB_ICONERROR);
exit(-1);
}
/* 4.更新显示窗口 */
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
/* 5.应用程序消息循环 */
MSG msg = { 0 };
BOOL bRet;
/* GetMessage 发生错误会返回-1 */
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
/* 应用程序消息处理回调函数 */
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
下面进行讲解上面的程序:
2.注册这个设计的窗口类调用RegisterClassEx函数进行注册
3.进行创建窗口,调用CreateWindowEx函数进行创建
4.更新显示此窗口,分别调用ShowWindow(显示窗口)和UpdateWindow(更新窗口)
5.应用程序消息循环,通过while循环
6.窗口消息处理过程
1.设计一个窗口类(...)
2.注册这个设计的窗口类
①RegisterClassEx函数讲解(注册窗口类函数,winuser.h中提供的函数)
函数原型:(代码来源于VS2017)
ATOM WINAPI RegisterClassExW(_In_ CONST WNDCLASSEXW *);
_In_ CONST WNDCLASSEXW *: 为我们设计的窗口类
返回值(ATOM也就是原子的意思):成功返回注册的原子,失败返回0
②示例中的使用
所以我们只需要通过返回值判断是否为注册失败,注册失败返回0,0也就是false
if (RegisterClassEx(&wcex) == ((ATOM)0)) { 提示注册失败信息 }
3.进行创建窗口
①CreateWindowEx函数讲解(注册窗口类函数,winuser.h中提供的函数)
函数原型:(代码来源于VS2017)(用点多,希望认真看完)
HWND WINAPI CreateWindowExW(
_In_ DWORD dwExStyle,
_In_opt_ LPCWSTR lpClassName,
_In_opt_ LPCWSTR 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);
dwExStyle: 窗口扩展风格
lpClassName: 我们注册的窗口类的名称
lpWindowName: 窗口的名称(说白了就是窗口的标题)
dwStyle: 窗口创建的风格(跟dwExStyle不同哦)
X: 窗口左上角的点位于屏幕的横坐标
Y: 窗口左上角的点位于屏幕的纵坐标
nWidth: 窗口的宽度
nHeight: 窗口的高度
hWndParent: 窗口所有者的句柄
hMenu: 菜单句柄
hInstance: 所在模块的实例句柄
lpParam: 在WM_CREATE中进行传递的参数
返回值: 创建成功返回创建的窗口句柄,失败返回NULL
②示例中的使用
HWND hWnd = CreateWindowEx(NULL, g_lpszClassName, g_lpszWindowName, WS_OVERLAPPEDWINDOW,
10, 10, 800, 800, NULL, NULL, hInstance, NULL);
窗口的扩展风格为NULL,也就是没有扩展风格
窗口类的名称是g_lpszClassName("CLASSNAME")
窗口名称是g_lpszClassName("哈喽,新的征程")
窗口的风格为WS_OVERLAPPEDWINDOW
窗口的位于屏幕的横纵坐标为(10, 10)
窗口的宽和高为(800, 800)
窗口的所有者为NULL,也就是没有所有者
窗口的菜单句柄为NULL,也就是没有菜单
窗口的所在模块的实例句柄,为操作系统在WinMain中传递过来的示例句柄
在WM_CREATE中传递的参数为NULL,也就是传递一个NULL的参数
最后通过返回值进行判断是否创建成功:
if (hWnd == NULL) { 创建窗口失败的提示信息 }
4.更新显示此窗口
①ShowWindow函数讲解(显示窗口,winuser.h中提供的函数)
函数原型:(代码来源于VS2017)
BOOL WINAPI ShowWindow(
_In_ HWND hWnd,
_In_ int nCmdShow);
hWnd:显示的窗口句柄
nCmdShow:窗口显示的方式(全屏,最大化窗口,最小化窗口,······)
返回值:成功返回非0,失败返回0
②UpdateWindow函数讲解(显示窗口,winuser.h中提供的函数)
函数原型:(代码来源于VS2017)
BOOL WINAPI UpdateWindow(
_In_ HWND hWnd);
hWnd:更新的窗口
返回值:成功返回非0,失败返回0
③示例中的使用
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
hWnd为CreateWindowEx中创建的窗口
nCmdShow为窗口的显示的方式(在WinMain由操作系统传入)
创建窗口后,还没有显示,所以需要调用ShowWindow进行显示窗口。
可是显示过后为何还要更新?
这是因为显示后还要通过更新窗口进行显示窗口中的东西(比如:图片,文字,······等)
5.应用程序消息循环
①GetMessage函数讲解(从调用线程的消息队列中取得一个消息,winuser.h中提供的函数)
函数原型:(代码来源于VS2017)
BOOL WINAPI GetMessageW(
_Out_ LPMSG lpMsg,
_In_opt_ HWND hWnd,
_In_ UINT wMsgFilterMin,
_In_ UINT wMsgFilterMax);
lpMsg:接收从消息队列中获取的消息
hWnd:接收消息的窗口句柄
wMsgFilterMin:指定被检测的最小消息值
wMsgFilterMax:指定被检测的最大消息值
返回值:获取错误返回-1,消息为WM_QUIT返回0,其他为非0
②TranslateMessage函数讲解(将虚拟键消息转换为字符消息,winuser.h中提供的函数)
函数原型:(代码来源于VS2017)
BOOL WINAPI TranslateMessage(
_In_ CONST MSG *lpMsg);
lpMsg:接收的消息
返回值:成功返回非0,失败返回0
③DispatchMessage函数讲解(将消息分发到窗口处理,winuser.h中提供的函数)
函数原型:(代码来源于VS2017)
LRESULT WINAPI DispatchMessageW(
_In_ CONST MSG *lpMsg);
lpMsg:分发的消息
返回值:窗口处理过程的返回值
④示例中的使用
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
while循环的结束,当GetMessage获取到的消息为WM_QUIT窗口退出,则返回0(false)
当GetMessage的返回值不为-1(获取消息失败)的时候,进行虚拟键转换和分发消息
6.窗口消息处理过程
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
参数:
hWnd: 窗口处理过程的窗口句柄
message:消息ID
wParam: 附加消息
lParam: 附加消息
①PostQuitMessage(线程终止请求,winuser.h中提供的函数)
函数原型:(代码来源于VS2017)
VOID WINAPI PostQuitMessage(
_In_ int nExitCode);
nExitCode:程序退出代码
②DefWindowProc(缺省的窗口处理过程,winuser.h中提供的函数)
函数原型:(代码来源于VS2017)
LRESULT CALLBACK DefWindowProcW(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam);
参数和返回值跟我们的窗口处理过程一样
这个函数的意义就是我们不想处理的消息就交给他吧
小插曲:
return msg.wParam
其实这个跟main的return是一样的,但对于初学者会有疑问,为什么不是return 0
解释部分:
当GetMessage获取到的消息为WM_DESTROY(销毁窗口)的时候调用我们的处理:
case WM_DESTROY:
PostQuitMessage(0);
break;
当我们调用PostQuitMessage传入的nExitCode(程序退出代码,0为正常退出,非0为异常)
调用了PostQuitMessage后会向消息队列中添加一个WM_QUIT(窗口退出消息)
然后GetMessage接收到WM_QUIT消息的MSG
MSG的wParam为PostQuitMessage中的nExitCode
所以最后return的值也就是PostQuitMessage的nExitCode