随着应用程序的复杂性越来越高,业务需求也越来越多,如何展现出交互性、视觉效果良好的UI界面是必须要解决的一个问题。其中,窗口的创建是其中非常关键的一个组成部分。在Win32编程中,创建窗口的API是CreateWindow。通过本文,你将掌握自定义窗口的技巧,让你的应用程序变得更加精彩。
一、了解CreateWindow函数
CreateWindow函数是Win32编程中的一个API函数,它用于创建一个新的窗口。
WINDPROC CreateWindow(
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwSyle,
int x, int y,
int nWidth, int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID IpParam
);
参数解释:
· lpClassName:窗口类名。可以通过WNDCLASSEX结构体创建一个窗口类。
· lpWindowName:窗口名。可以设置为 NULL 或者字符串指针。
· dwStyle:窗口样式,它包括窗口边框、标题栏和窗口的外观和行为。
· x,y:窗口左上角的坐标。
· nWidth、nHeight:窗口的大小。
· hWndParent:父窗口句柄。可以是其他窗口的句柄,也可以是桌面窗口的句柄。
· hMenu:窗口菜单的句柄。通常设置为 NULL表示没有菜单。
· hInstance:应用程序实例的句柄,使用GetModuleHandle(NULL)获取。
· IpParam:传递给窗口过程的参数。多数情况下,可以设置为NULL。
二、自定义窗口
通过CreateWindow函数创建的窗口具有标准的外观和相应样式,但并不能满足较为复杂的业务需求,因此我们需要自定义窗口。
1. 设置窗口样式
dwStyle参数定义了窗口的行为和外观,利用它可以改变窗口的样式。下面是一些比较常用的样式:
· WS_CAPTION:具有标题栏和窗口边框,按下关闭按钮时将发送WM_CLOSE消息。
· WS_CHILD:指定父窗口的子窗口,它的坐标相对于父窗口的客户区。
· WS_CLIPCHILDREN:指定如果有子窗口,那么当这些子窗口被绘制时,不会将它们绘制到父窗口所在区域。
· WS_DISABLED:指定窗口不能接收输入焦点。
· WS_EX_TOOLWINDOW:指定窗口是工具窗口,其标题栏和窗口边框将远比标准窗口边框更窄。
· WS_THICKFRAME:指定窗口有大小调整和三个标准的标题栏按钮。(最小化、最大化、关闭)
2. 创建窗口类
在 Win32 程序中,窗口不是通过直接调用 CreateWindow 函数进行创建,而是首先需要定义一个窗口类,再调用 CreateWindowEx 函数来创建。
下面是创建窗口类的步骤:
· 创建WNDCLASSEX结构体实例,该结构体用于定义窗口类的属性
WNDCLASSEX wndClass = {};
wndClass.cbSize = sizeof(WNDCLASSEX);
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = WndProc;
wndClass.hInstance = hInstance;
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wndClass.lpszClassName = L"MainWndClass";
注意,WNDCLASSEX结构体中必须指定 lpfnWndProc 成员,它指向窗口过程的地址。
· 注册窗口类
如果窗口类的成员 lpfnWndProc 未被设置,此时 RegisterClassEx 将调用失败。如果 lpfnWndProc 成员已被设置,则 RegisterClassEx 成功注册窗口类。
if (!RegisterClassEx(&wndClass))
{
MessageBox(NULL, L"窗口类注册失败!", L"错误", MB_OK);
return 1;
}
· 创建并显示窗口
接下来,我们便可以使用 CreateWindowEx 函数创建窗口并进行显示:
hwnd = CreateWindowEx(0,
L"MainWndClass",
L"自定义窗口",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
三、绘制自定义窗口
除了定义窗口的样式外,它的外观也需要做一个定制,这里我们通过处理 WM_PAINT 消息为例,实现窗口绘制。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
//TODO: 绘制代码
EndPaint(hWnd, &ps);
}
break;
case WM_CLOSE:
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
在 WM_PAINT 消息中,通过 BeginPaint 函数获取客户区的设备上下文(HDC),接着我们就可以在该上下文中绘制了,绘制完成后再通过 EndPaint 函数结束绘制。
四、自定义边框与标题栏
对于窗口的标题栏和边框,约定俗成的做法是使用一个带有标题栏和边框的容器窗口。而对于方案两,我们一般使用 DWM(Desktop Windows Manager)来完成。
1. DWM 的使用
DWM 是 Vista 以及以后版本中引入的新特性。在 Windows 7 中,DWM 可以让我们做到脱离 GDI+ 使用 WPF 一样的 Vista 平台的扩展颜色和渐变效果。
DWM 的核心 API 是 DwmExtendFrameIntoClientArea 和 DwmEnableBlurBehindWindow。它们可以让我们实现一些高级特效。
2. 使用DWM实现自定义窗口
首先,需要在窗口类中注册 DWM 发送的消息:
case WM_DWMCOMPOSITIONCHANGED:
//当函数来自桌面组合时,警告DWM正在进行广泛修改
DwmIsCompositionEnabled(&bGlass);
InvalidateRect(hwnd, NULL, FALSE);
return 0;
这里,我们可以获取 DWM 是否可用,并在 ValidateRect 函数中向 Windows 更新整个窗口:
DwmExtendFrameIntoClientArea(hwnd, &margins);
UpdateWindow(hwnd);
最后就是在 WM_PAINT 消息中使用 DWM 绘制窗口:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HRESULT hr = S_OK;
if (bGlass)
{
RECT rcClient;
GetClientRect(hwnd, &rcClient);
hr = pDwmExtendFrameIntoClientArea(hdc, &margins);
if (FAILED(hr))
{
DeleteDC(hdc);
EndPaint(hwnd, &ps);
return 0;
}
FillRect(hdc, &rcClient, (HBRUSH)COLOR_WINDOW);
DrawText(hdc, L"Hello World!", -1, &rcClient,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
else
{
RECT rcWindow;
GetWindowRect(hwnd, &rcWindow);
int cx = rcWindow.right - rcWindow.left;
int cy = rcWindow.bottom - rcWindow.top;
int w = cx - 2 * BORDER;
int h = cy - CAPTION - BORDER;
FillRect(hdc, &ps.rcPaint, (HBRUSH)COLOR_WINDOW);
DrawCaption(hwnd, hdc, &ps.rcPaint, DC_SMALL|DC_ICON|DC_TEXT|DC_INBUTTON);
HRGN hrgnInside = CreateRectRgn(BORDER, CAPTION, w + BORDER, h + CAPTION);
SelectClipRgn(hdc, hrgnInside);
DeleteObject(hrgnInside);
HBRUSH hBrush = CreateSolidBrush(RGB(255, 0, 0));
RECT rBorder = { BORDER, CAPTION, w + BORDER, h + CAPTION };
FrameRect(hdc, &rBorder, hBrush);
}
EndPaint(hwnd, &ps);
}
break;
这里,我们使用了 DrawCaption 函数绘制标题栏,然后使用 CreateRectRgn 函数创建客户区域内的矩形区域,调用 SelectClipRgn 函数进行剪切,最后在该区域内绘制边框即可。
五、总结
本文向你展示了如何使用 CreateWindow 函数创建窗口,以及如何自定义窗口的样式和外观。通过学习窗口面向的知识,你可以对窗口进行自由控制和更自由的美化,有助于提升应用的可用性。