慢羊羊的空间

无为,无我,无欲,居下,清虚,自然

窗口技巧:利用窗体句柄实现圆形窗口 铜牌收录

该篇文章讲述如何做一个圆形窗口。根据该思路,可以实现各种形状的窗口。

阅读该文章前,请先阅读

  1. “VC绘图/游戏简易教程-10:用鼠标控制绘图/游戏程序”
  2. “VC绘图/游戏简易教程-15:窗体句柄(Windows 编程入门)”

基础程序

先写一个基础程序,实现按鼠标右键退出,完整代码如下:

#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,里面有函数的详细用法,以及更多有趣的窗体控制函数。

评论 (2) -

  • 第一个例子在vs2019里面  error C4996: 'GetMouseMsg': This function is deprecated. Instead, use this new function: getmessage.
    • 文章已经更新,相关函数已经适配新版本 EasyX,谢谢提醒。

添加评论