创建一个Win32窗口


1. GUI入口函数

GUI应用程序的入口函数是 WinMain , 这是一个自定义的入口函数。WinMain函数采用的是windows标准调用方式。下面是简单的windows程序:

#include <windows.h>
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
{
	::MessageBox(nullptr, TEXT("Hello, Win32 Application"), TEXT("First Win32 Window"), MB_OK);
	return 0;
}

系统传给WinMain函数几个参数定义如下:

  • hInstance: 制定当前模块的实例句柄。在win32下,模块的实例句柄和模块句柄是一样的,只是说法不同。所以可以通过下面语句获取当前执行模块的实例句柄。
hInsatnce = (HINSTANCE)GetModuleHandle(nullptr);

GetModuleHandle 函数的唯一参数是模块名称,函数返回这个模块句柄。如果传递nullptr的话函数返回可执行文件所在模块的模块句柄。

  • lpCmdLine 是命令行参数。其值是由CreateProcess函数的第二个参数指定。
  • nCmdShow 指定窗口初始化的显示方式。

当Windows向程序发送消息时,他调用程序的一个函数,这个函数的参数精确的描述了Windows发送的消息。在程序中称为窗口函数或消息处理函数。它是一个自定义的回掉函数,原型如下:

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
hwnd 标识了消息的到达窗口
uMsg 参数时一个被命名的常量(消息ID)

下面是一个简单示例

#include <windows.h>
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND hWnd = ::FindWindow(nullptr, TEXT("无标题 - 记事本"));
	if (hWnd != nullptr)
		::SendMessage(hWnd, WM_CLOSE, 0, 0);
	return 0;
}

FindWindow 函数会查找窗口类名和窗口标题与指定字符串相匹配的窗口。返回找到的窗口句柄;SendMessage 用于向窗口中发送消息。

2. 创建窗口

下面是创建一个窗口的完整代码

#include <windows.h>
#include <iostream>

LRESULT CALLBACK MainWindProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
{
	WNDCLASSEX wndClass;
	wndClass.cbSize = sizeof(wndClass);
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = MainWindProc;
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = ::LoadIcon(nullptr, IDI_APPLICATION);
	wndClass.hCursor = ::LoadCursor(nullptr, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);
	wndClass.lpszMenuName = nullptr;
	wndClass.lpszClassName = TEXT("MainWClass");
	wndClass.hIconSm = nullptr;

	// 注册窗口类
	::RegisterClassEx(&wndClass);
	
	// 创建窗口
	HWND hwnd = ::CreateWindowEx(\
			0, \
			TEXT("MainWClass"), \
			TEXT("My First Window"), \
			WS_OVERLAPPEDWINDOW, \
			CW_USEDEFAULT, \
			CW_USEDEFAULT, \
			CW_USEDEFAULT, \
			CW_USEDEFAULT, \
			nullptr, \
			nullptr, \
			hInstance, \
			nullptr);

	if (hwnd == nullptr)
	{
		std::cout << "Create Window Error!" << std::endl;
		return -1;
	}

	// 显示窗口
	::ShowWindow(hwnd, nCmdShow);
	// 刷新窗口客户区
	::UpdateWindow(hwnd);

	MSG msg;
	while (::GetMessage(&msg, nullptr, 0, 0))
	{
		::TranslateMessage(&msg);
		::DispatchMessage(&msg);
	}

	return msg.wParam;
}

LRESULT CALLBACK MainWindProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_PAINT:
	{
		PAINTSTRUCT ps;
		HDC hdc = ::BeginPaint(hwnd, &ps);
		std::wstring str = L"This is Test Text!";
		::TextOut(hdc, 0, 0, str.c_str(), str.size());
		::EndPaint(hwnd, &ps);
		return 0;
	}
	case WM_DESTROY:
		::PostQuitMessage(0);
		return 0;
	}

	return ::DefWindowProc(hwnd, message, wParam, lParam);
}

上面的代码是创建一个窗口程序的一般步骤,具体流程如下:

  1. 注册窗口类(RegisterClassEx
  2. 创建窗口(CreateWindowEx
  3. 在桌面显示窗口(ShowWindow
  4. 更新窗口客户端(UpdateWindow
  5. 进入消息处理和获取循环。首先获取消息(GetMessage),如果有消息到达,则消息分派给回调函数进行出路(DispatchMessage)。如果消息是MW_QUIT,则GetMessage函数返回False,整体消息循环结束。具体的处理过程在MainWndProc函数中进行。

一.注册窗口类

注册窗口类使用函数RegisterClassEx,一个窗口类定义了窗口的一些主要属性,如图标、光标、背景色和负责处理消息的窗口函数等。这些属性定义在WNDCLASSEX结构中。

typedef struct _WNDCLASSEX{
	UINT        cbSize;				// WNDCLASSEX结构的大小
    UINT        style;				// 从窗口派生的窗口具有的风格
    WNDPROC     lpfnWndProc;		// 消息处理函数指针
    int         cbClsExtra;			// 指定紧跟在窗口类结构后的附加字节数
    int         cbWndExtra;			// 指定紧跟在窗口示例后的附加字节数
    HINSTANCE   hInstance;			// 本模块的实例句柄
    HICON       hIcon;				// 窗口左上角图标的句柄
    HCURSOR     hCursor;			// 光标的句柄
    HBRUSH      hbrBackground;		// 背景画刷的句柄
    LPCSTR      lpszMenuName;		// 菜单名
    LPCSTR      lpszClassName;		// 该窗口类的名称
    HICON       hIconSm;			// 小图标句柄
}WNDCLASSEX;

(1) 指定窗口类的风格

wndClass.style = CS_HREDRAW | CS_VREDRAW;	// 指定如果大小改变就重画

前缀CS_意为class style,在WINUSER.H中定义了全部可选样式。

#define CS_VREDRAW          0x0001
#define CS_HREDRAW          0x0002
#define CS_DBLCLKS          0x0008
#define CS_OWNDC            0x0020
#define CS_CLASSDC          0x0040
#define CS_PARENTDC         0x0080
#define CS_NOCLOSE          0x0200
#define CS_SAVEBITS         0x0800
#define CS_BYTEALIGNCLIENT  0x1000
#define CS_BYTEALIGNWINDOW  0x2000
#define CS_GLOBALCLASS      0x4000

(2)指定窗口处理函数地址

wndClass.lpfnWndProc = MainWindProc;

WNDCLASSEX 结构成员lpfnWndProc 指定了基于此类窗口的窗口函数。当窗口收到消息时Windows即自动调用这个函数通知应用程序。

(3) 本程序的实例句柄传给hInstance成员

wndClass.hInstance = hInstance;

(4) 设置图标和光标

wndClass.hIcon = ::LoadIcon(nullptr, IDI_APPLICATION);
wndClass.hCursor = ::LoadCursor(nullptr, IDC_ARROW);

LoadIcon 函数装载了一个预定义图标,命名为 IDI_APPLICATION
LoadCursor 函数装载了一个预定义的光标,命名为 IDC_ARROW

(5)指定窗口重画客户区画刷

wndClass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);	// 使用白色画刷

(6)指定窗口类名称

wndClass.lpszClassName = TEXT("MainWClass");  // 窗口类名称

填充完WNDCLASSEX 结构,就可以进行注册了。RegisterClassEx 函数调用失败将返回0

::RegisterClassEx(&wndClass);

二、创建窗口

要创建窗口,使用函数 CreateWindowEx

HWND hwnd = ::CreateWindowEx(\					
			0, \								// dwExStyle, 扩展样式
			TEXT("MainWClass"), \				// 类名
			TEXT("My First Window"), \			// 标题
			WS_OVERLAPPEDWINDOW, \				// 窗口风格
			CW_USEDEFAULT, \					// X, 初始X坐标
			CW_USEDEFAULT, \					// Y, 初始Y坐标
			CW_USEDEFAULT, \					// nWidth, 宽度
			CW_USEDEFAULT, \					// nHeight, 高度
			nullptr, \							// hWndParent, 父窗口句柄
			nullptr, \							// hMenu,菜单句柄
			hInstance, \						// hInstance, 程序实例句柄
			nullptr);							// lpParam, 用户数据

函数调用成功将返回窗口句柄,失败返回nullptr。
第四个参数dwStyle的值是 WS_OVERLAPPEDWINDOW,即重叠窗口。由他指定的窗口有标题栏、系统菜单、可以改变大小的边框,以及最大化、最小化和关闭按钮。这是一个标准的窗口样式。下面是一些常见风格定于:

  • WS_BORDER 创建一个单边框窗口
  • WS_CAPTION 创建一个有标题框的窗口
  • WS_CHID 创建一个子窗口。这个风格不能与WS_POPVP合用
  • WS_DISABLED 创建一个初始状态为禁止的子窗口。
  • WS_DLGFRAME 创建一个带对话框边框风格的窗口,这种风格的窗口不能带标题条
  • WS_HSCROLL 创建一个有水平滚动条的窗口
  • WS_VSCROLL 创建一个有垂直滚动条的窗口
  • WS_ICONIC 创建一个初始状态为最小化状态的窗口,与WS_MINIMIZE风格相同
  • WS_MAXIMIZE 创建一个具有最大化按钮的窗口。
  • WS_OVERLAPPED 产生一个层叠的窗口。
  • WS_OVERLAPPEDWINDOW 创建一个具有WS_OVERLAPPEDWS_CAPTIONWS_SYSMENUWS_THICKFRAMEWS_MINIMZEBOXWS_MAXMIZEBOX 风格的层叠窗口。
  • WS_POPUP 创建一个弹出式窗口
  • WS_SIZEBOX 创建一个可调边框的窗口。
  • WS_SYSMENU 创建一个在标题条上带有窗口菜单的窗口。
  • WS_THICKFRAME 创建一个具有可调边框的窗口
  • WS_VISIBLE 创建一个初始状态为可见的窗口

三、在桌面显示窗口

::ShowWindow(hwnd, nCmdShow);

ShowWindow 函数用于指定窗口的显示状态。nCmdShow 取值可以是 SW_SHOWSW_HIDESW_MINIMIZE

四、更新窗口客户区

::UpdateWindow(hwnd);

如果指定窗口的更新区域不为空的话,UpdateWindow 函数通过向这个窗口发送一个WM_PAINT 消息更新它的客户区。当窗口显示在屏幕上时,窗口的客户区被 WNDCLASSEX 中指定的刷子擦去,调用 UpdateWindow 函数将促使客户区重画,以显示其内容。

五、进入无限的消息循环

Windows为每一个线程维护一个消息队列,每当有一个输入发生,Windows就把用户输入翻译成消息放在消息队列中。GetMessage 函数可以从消息队列中取一个消息填充MSG结构。
如果消息队列中没有消息,这个函数会一直等下去,直到有消息进入消息队列为止。

typedef struct tagMSG {
    HWND        hwnd;		// 消息要发向的窗口句柄
    UINT        message;	// 消息标识符,以WM_开头的预定义值(Windows Message)
    WPARAM      wParam;		// 消息的参数之一
    LPARAM      lParam;		// 消息的参数之二
    DWORD       time;		// 消息放入消息队列的时间
    POINT       pt;			// 这是一个POINT数据结构,表示消息放入消息队列时鼠标的位置
} MSG,

GetMessage 函数从消息队列中取得的消息如果不是WM_QUIT ,则返回非零值。一个WM_QUIT 消息会使GetMessage 函数返回0,从而消息循环结束。

::TranslateMessage(&msg);

此调用把键盘输入翻译成可调用的消息。

::DispatchMessage(&msg);

DispatchMessage 函数分发一个消息到对应窗口的窗口函数。MainWndProc 处理消息后把控制权交给Windows,此时DispatchMessage 函数仍然继续工作,当他返回时,消息队列从调用GetMessage 函数开始进入下一轮。

3. 处理消息代码

所有消息不做处理的消息都必须返回一个名为DefWindowProc的函数让Windows做默认处理,从DefWindowProc 函数返回的值也必须从消息处理函数返回。

每当客户区变为无效,消息处理函数就会收到一个新的WM_PAINT 消息。
WM_DESTORY 是窗口必须处理的一个消息,当用户关闭窗口时,消息处理函数就会收到一 个 WM_DESTORY消息。当接收到这个消息的时候,说明窗口正在销毁。

::PostQuitMessage(0);

PostQuitMessage函数会会向消息队列中插入一个 WM_QUIT 消息。GetMessage 函数如果从消息队列中获得到消息时 WM_QUIT ,它将返回0。从而退出消息循环。如果不使用函数 PostQuitMessage 发送WM_QUIT 消息,则界面虽然被销毁了,但是消息循环还在继续,程序还没有结束。

发布了88 篇原创文章 · 获赞 79 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/douzhq/article/details/93360509