YangW

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

窗口技巧:利用窗体句柄实现圆形窗口

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

阅读该文章前,请先阅读

  1. “VC绘图/游戏简易教程-10:用鼠标控制绘图/游戏程序”http://tieba.baidu.com/f?kz=778075014
  2. “VC绘图/游戏简易教程-15:窗体句柄(Windows 编程入门)”http://tieba.baidu.com/f?kz=871437280

【基础程序】

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

#include <graphics.h>
#include <conio.h>

void main()
{
	initgraph(640, 480);	// 初始化图形窗口
	MOUSEMSG m;				// 定义鼠标消息

	while(true)
	{
		m = GetMouseMsg();	// 获取一条鼠标消息
		switch(m.uMsg)
		{
			// 按鼠标右键退出程序
			case WM_RBUTTONUP:
				closegraph();
				exit(0);
		}
	}
}

【实现圆形窗体】

通过 EasyX 库函数 GetHWnd() 获取句柄,然后通过 Windows SDK 中的 CreateEllipticRgn 创建圆形区域,再通过 SetWindowRgn 设置窗口为圆形,局部代码如下:

	……

	// 初始化图形窗口
	initgraph(200,200);	

	// 获取窗口句柄
	HWND hWnd = GetHWnd();

	// 设置圆形区域
	HRGN rgn = CreateEllipticRgn(0, 0, 200, 200);
	SetWindowRgn(hWnd, rgn, true);

	……

效果:

【修正圆形窗体的位置】

根据上图可以看到,圆形窗体包括了标题栏和边框,这是我们不希望的,我们需要指定多边形的时候加上这个边框区域,但是不同的 windows 皮肤,边框的大小是可变的,所以需要用 API 函数 GetSystemMetrics 配合不同的参数获取不同的尺寸,例如:

  • GetSystemMetrics(SM_CXFIXEDFRAME);   // 返回边框的宽度
  • GetSystemMetrics(SM_CYFIXEDFRAME);   // 返回边框的高度
  • GetSystemMetrics(SM_CYCAPTION);          // 返回标题栏的高度
  • (更多的选项,请参考 MSDN)

只需要在源代码的区域中增加这些尺寸即可,设置区域的局部代码修改如下:

	……

	// 获取窗口边框宽高
	int cx = GetSystemMetrics(SM_CXFIXEDFRAME);
	int cy = GetSystemMetrics(SM_CYFIXEDFRAME) + GetSystemMetrics(SM_CYCAPTION);

	// 设置圆形区域
	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>

void main()
{
	initgraph(200,200);			// 初始化图形窗口
	HWND hWnd = GetHWnd();		// 获取窗口句柄
	
	// 获取窗口边框宽高
	int cx = GetSystemMetrics(SM_CXFIXEDFRAME);
	int cy = GetSystemMetrics(SM_CYFIXEDFRAME) + GetSystemMetrics(SM_CYCAPTION);

	// 设置圆形区域
	HRGN rgn = CreateEllipticRgn(0 + cx, 0 + cy, 200 + cx, 200 + cy);
	SetWindowRgn(hWnd, rgn, true);
	
	// 画彩虹球
	setlinestyle(PS_SOLID, NULL, 2);
	for(int r = 100; r>0; r--)
	{
		setcolor( HSLtoRGB(360-r*3.6, 1, 0.5) );
		circle(100, 100, r);
	}
	
	MOUSEMSG m;						// 定义鼠标消息
	
	while(true)
	{
		m = GetMouseMsg();			// 获取一条鼠标消息
		
		switch(m.uMsg)
		{
			case WM_LBUTTONDOWN:
				// 如果左键按下,欺骗 windows 点在了标题栏上
				PostMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(m.x, m.y));
				break;
			
			case WM_RBUTTONUP:			// 按鼠标右键退出程序
				closegraph();
				exit(0);
		}
	}
}

效果:

【拓展】

Windows API 有一系列的区域函数,可以生成各种各样的窗体,甚至可以通过图片的某个颜色来生成透明区域,有兴趣的可以参考一下 MSDN。