[SDK]-窗口创建
前言
各位师傅大家好,我是qmx_07,今天给大家讲解窗口创建的流程,以及console和windows的区别,下一章节会更新GDI绘制,将会持续更新
控制台编程与Windows程序在流程上的区别
- console控制台:使用顺序的过程驱动设计方式,程序有明显的开始与结束
- windows程序:消息驱动,按照事件的发生来控制,并不清楚用户要怎样操作,比如 我点击了文件,编写,退出,各种类型的操作,我们要对这些操作进行排序和管理
- 事件驱动程序设计是密切围绕消息的产生与处理而展开的
控制台和windows入口的区别
windows窗口的入口程序叫做 WinMain
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow // show state
);
hinstance:表示 当前程序当前运行的实例句柄
hPrevInstance 这个一般设置为NULL,用来兼容16位程序,在win32环境下,没有作用
lpCmdLine:表示 传递给控制台的命令行参数,可以理解为类似 main()函数的[]argv
nCmdShow:表示 显示方式,最大化,最小化,隐藏
窗口的创建主要分为六步骤
设计窗口注册类
- 设计窗口种类就是定义窗口属性的模板,这些属性包括窗口式样,鼠标形状,菜单等
- 窗口种类也指定处理该类中所有窗口消息的窗口函数.只有先建立窗口种类,才能根据窗口种类来创建Windows应用程序的一个或多个窗口.创建窗口时,还可以指定窗口独有的附加特性
- 窗口类具有唯一性,不能够重新注册
窗口类介绍
注册窗口类的参数:
代码实例:
WNDCLASS wc = {};//创建类名为wc的窗口类
wc.style = CS_VREDRAW | CS_HREDRAW;//这个组合,可以通过位运算增加风格或者减少风格
wc.lpfnWndProc = WndProc;//窗口的过程函数
wc.cbWndExtra = 0;
wc.cbClsExtra = 0;
wc.hInstance = hInstance;//句柄
wc.hIcon = NULL;//图标
wc.hCursor = NULL;//光标
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;//菜
wc.lpszClassName = L"窗口";//窗口类名
Register和Messagebox
Messagebox:会弹出对话框窗口
int MessageBox( HWND hWnd, //
LPCTSTR lpText, //C风格字符串,以 0 结尾的字符串,文本
LPCTSTR lpCaption, //标题
UINT uType); //方式
Register:如果注册失败,会返回0,利用Messagebox去响应失败内容
创建窗口
创建窗口类的参数:
代码实例:
HWND hwnd = CreateWindow(
L"窗口",//窗口类名
L"标题",//窗口标题
WS_OVERLAPPEDWINDOW,//重叠窗口、带标题、系统菜单
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,//x,y,宽,高
NULL,//父窗口
NULL,//菜单
hInstance,//实例句柄
NULL//消息参数
);
注意:必选三种基础风格之一(WS_POPUP, WS_CHILD【子窗口】,WS_OVERLSPPED【重叠窗口】)必须有其中之一,并且不能互相混用,是互斥的。
显示窗口
API函数CreateWindow创建完窗口后,要想把它显示出现,还必须调用另一个API函数ShowWindows.
参数1:窗口句柄,告诉ShowWindow()显示哪一个窗口
参数2:最小化(SW_MINIMIZE),普通(SW_SHOWNORMAL),还是最大化 (SW_SHOWMAXIMIZED)。
WinMain在创建完窗口后就调用ShowWindow函数,并把iCmdShow参数传送给这个窗口
WinMain()调用完ShowWindow后,还需要调用函数UpdateWindow,最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新.且不通过消息循环。
创建消息循环
Windows为每个正在运行的应用程序都保持一个消息队列。当你按下鼠标或者键盘时,Windows并不是把这个输入事件直接送给应用程序,而是将输入的事件先翻译成一个消息,然后把这个消息放入到这个应用程序的消息队列中去。
BOOL GetMessage(
LPMSG lpMsg, // message information
HWND hWnd, // handle to window
UINT wMsgFilterMin, // first message UINT wMsgFilterMax // last message);
操作系统将消息封装成MSG结构体投递到消息队列
消息循环
应用程序的WinMain函数通过执行一段代码从队列中来检索Windows消息。然后WinMain就把这些消息分配给相应的窗口函数以便处理它们,这段代码是一段循环代码,故称为”消息循环”。
拿到消息以后,在通过获取的消息对应处理调用窗口回调函数.
封装消息的结构体
typedef struct tagMSG { // msg
HWND hwnd; //要发送的窗口句柄。如果是在一个有多个窗口的应用程序中,用这个参数就可决定让哪个窗口接收消息。
UINT message; //消息编号
WPARAM wParam; //两个参数,不同的消息,一个32位的消息参数,这个值的确切意义取决于消息本身
LPARAM lParam; //两个参数,不同的消息,一个32位的消息参数,这个值的确切意义取决于消息本身
DWORD time; //消息放入消息队列中的时间(消息发生时间),在这个域中写入的并不是日期,而是从Windows启动后所测量的时间值。Windows用这个域来使用消息保持正确的顺序。
POINT pt; //消息放入消息队列时的鼠标坐标.
} MSG;
建立消息循环
消息循环以GetMessage调用开始,它从消息队列中取出一个消息:
MSG msg; //定义消息名
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ; //翻译消息
DispatchMessage (&msg) ; //
}
return msg.wParam ;
BOOL GetMessage(
LPMSG lpMsg, //接收消息的MSG结构的地址
HWND hWnd, //窗口句柄,NULL则表示要获取该应用程序创建的所有窗口的消息;
UINT wMsgFilterMin, //指定消息范围。后面三个参数被设置为默认值
UINT wMsgFilterMax //指定消息范围。后面三个参数被设置为默认值
);
后面三个参数被设置为默认值这就是说你打算接收发送到属于这个应用程序的任何一个窗口的所有消息。
返回值:
● 在接收到除WM_QUIT之外的任何一个消息后,GetMessage()都返回TRUE。
● 如果GetMessage收到一个WM_QUIT消息,则返回FALSE
● 如收到其他消息,则返回TRUE。
窗口过程函数
LRESULT CALLBACK WindowProc( HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter);
要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程,也就是调用WindowProc来处理消息
在WindowProc()处理完消息后,代码又循环到开始去接收另一个消息,这样就完成了一个消息循环。
代码实例:
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>
void ShowError();
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);
}
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow // show state
)
{
//设计注册窗口类函数
WNDCLASS wc = { };
wc.style = CS_VREDRAW | CS_HREDRAW;//默认,当尺寸修改时,通知函数
wc.lpfnWndProc = WndProc;//窗口过程函数
wc.cbClsExtra = 0;//默认给0
wc.cbWndExtra = 0;//默认给0
wc.hInstance = hInstance;//实例句柄
wc.hIcon = NULL;//图标
wc.hCursor = NULL;//光标
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);//背景色
wc.lpszMenuName = NULL;//菜单
wc.lpszClassName = L"窗口";//窗口类名
if (!RegisterClass(&wc))
{
MessageBox(NULL, L"注册窗口失败", L"提示", MB_OK);
}
/*if (!RegisterClass(&wc))
{
DWORD nRet = GetLastError();
ShowError();
}*/
//2、创建窗口实例
HWND hWnd = CreateWindow(L"窗口", // 窗口类类名
L"第一个窗口", // 窗口标题
WS_OVERLAPPEDWINDOW,//重叠窗口、带标题、系统菜单 WS_VISIBLE显示窗口
CW_USEDEFAULT,//默认位置
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,//父窗口
NULL,//菜单
hInstance,//实例句柄
NULL //消息参数
);
if (hWnd == NULL)
{
return 0;
}
//显示窗口
ShowWindow(hWnd, SW_SHOW);
//更新窗口
UpdateWindow(hWnd); //立即绘制
//消息循环
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0))
{
DispatchMessage(&msg);
}
return 0;
}
结语
我们学习了窗口的处理流程,大家可以手敲感受一下