窗口技巧:利用窗体句柄实现圆形窗口
2010-8-26 ~ 2023-10-31
(2)
该篇文章讲述如何做一个圆形窗口。根据该思路,可以实现各种形状的窗口。
阅读该文章前,请先阅读
基础程序
先写一个基础程序,实现按鼠标右键退出,完整代码如下:
#include <graphics.h>
#include <conio.h>
int main()
{
initgraph(640, 480); // 初始化图形窗口
ExMessage m; // 定义鼠标消息
while (true)
{
m = getmessage(EX_MOUSE); // 获取一条鼠标消息
switch (m.message)
{
// 按鼠标右键退出程序
case WM_RBUTTONUP:
closegraph();
return 0;
}
}
}
实现圆形窗体
通过 EasyX 库函数 GetHWnd() 获取绘图窗口句柄,然后通过 Windows API 中的 CreateEllipticRgn 创建圆形区域,再通过 SetWindowRgn 将创建的圆形区域应用到窗体上,实现圆型窗体。局部代码如下:
……
// 初始化图形窗口
initgraph(200, 200);
// 获取窗口句柄
HWND hWnd = GetHWnd();
// 设置圆形区域
HRGN rgn = CreateEllipticRgn(0, 0, 200, 200);
SetWindowRgn(hWnd, rgn, true);
……
效果:
修正圆形窗体的位置
根据上图可以看到,圆形窗体包括了标题栏和边框,这是我们不希望的,我们需要指定多边形的时候加上这个边框区域,但是不同的 windows 皮肤,边框的大小是可变的,需要用相关的 API 函数计算出边框的尺寸,例如:
- GetClientRect(); // 获取客户区的尺寸(主要是判断宽高用)
- GetWindowRect(); // 获取窗口在屏幕上占用的矩形区域
- GetSystemMetrics(SM_CYCAPTION); // 获取标题栏的高度
只需要在源代码的区域中增加这些尺寸即可,设置区域的局部代码修改如下:
……
// 获取窗口边框宽高
RECT rcClient, rcWind;
GetClientRect(hWnd, &rcClient);
GetWindowRect(hWnd, &rcWind);
int cx = ((rcWind.right - rcWind.left) - rcClient.right) / 2;
int cy = ((rcWind.bottom - rcWind.top + GetSystemMetrics(SM_CYCAPTION)) - rcClient.bottom) / 2;
// 设置圆形区域
HRGN rgn = CreateEllipticRgn(0 + cx, 0 + cy, 200 + cx, 200 + cy);
SetWindowRgn(hWnd, rgn, true);
……
效果:
拖动窗体
现在的问题是:窗体没有标题栏,无法拖动。
实现拖动异形窗体的方法很多,这里采用的是:当鼠标点击时,通过 PostMessage 发消息给 Windows 告诉他点在了标题栏上,这个实现很简单,只需在鼠标左键事件中增加如下语句:
PostMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(m.x, m.y));
最终程序
再增加一点绘图效果(我通过 HSL 颜色模型绘制了一个渐变色的圆),最终的圆形窗口程序如下:
#include <graphics.h>
#include <conio.h>
int main()
{
initgraph(200, 200); // 初始化图形窗口
HWND hWnd = GetHWnd(); // 获取窗口句柄
// 获取窗口边框宽高
RECT rcClient, rcWind;
GetClientRect(hWnd, &rcClient);
GetWindowRect(hWnd, &rcWind);
int cx = ((rcWind.right - rcWind.left) - rcClient.right) / 2;
int cy = ((rcWind.bottom - rcWind.top + GetSystemMetrics(SM_CYCAPTION)) - rcClient.bottom) / 2;
// 设置圆形区域
HRGN rgn = CreateEllipticRgn(0 + cx, 0 + cy, 200 + cx, 200 + cy);
SetWindowRgn(hWnd, rgn, true);
// 画彩虹球
setlinestyle(PS_SOLID, 2);
for (int r = 99; r > 0; r--)
{
setlinecolor(HSLtoRGB(360 - r * 3.6f, 1, 0.5));
circle(99, 99, r);
}
ExMessage m; // 定义鼠标消息
while (true)
{
m = getmessage(EX_MOUSE); // 获取一条鼠标消息
switch (m.message)
{
case WM_LBUTTONDOWN:
// 如果左键按下,欺骗 windows 点在了标题栏上
PostMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(m.x, m.y));
break;
case WM_RBUTTONUP: // 按鼠标右键退出程序
closegraph();
return 0;
}
}
}
效果:
拓展
Windows SDK 有许多关于窗体控制的 API,比如,还可以将窗体设置变为半透明的:
SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED);
SetLayeredWindowAttributes(hWnd, 0, 192, LWA_ALPHA); // 设置窗体透明度为 192(0 为全透明,255 为不透明)
SetLayeredWindowAttributes 函数还可以设置某个颜色为透明区域,有兴趣的可以参考一下 MSDN,里面有函数的详细用法,以及更多有趣的窗体控制函数。