Win32 通过 SetWindowRgn 创建透明窗体
函数介绍
Window 支持使用 SetWindowRgn
创建各种形状的透明窗口,函数原型如下
int SetWindowRgn(
HWND hWnd, // 要调整的窗口的句柄
HRGN hRgn, // 区域的句柄
BOOL bRedraw // 是否立即重画窗口
); //返回非0值如果成功,否则返回0
Win32 API提供了许多现成的函数可以调用。只不过简比较简单的方法只能创建出比较简单的区域。
这样的函数有:
1.创建椭圆的区域
HRGN CreateEllipticRgn(
int nLeftRect, // 边界矩形左上角的x坐标
int nTopRect, // 边界矩形左上角的y坐标
int nRightRect, // 边界矩形右下角的x坐标
int nBottomRect // 边界矩形右下角的y坐标
);
2.创建矩形区域
HRGN CreateRectRgn(
int nLeftRect, // 边界矩形左上角的x坐标
int nTopRect, // 边界矩形左上角的y坐标
int nRightRect, // 边界矩形右下角的x坐标
int nBottomRect // 边界矩形右下角的y坐标
);
3.创建圆角椭圆区域
HRGN CreateRoundRectRgn(
int nLeftRect, // 边界矩形左上角的x坐标
int nTopRect, // 边界矩形左上角的y坐标
int nRightRect, // 边界矩形右下角的x坐标
int nBottomRect // 边界矩形右下角的y坐标
int nWidthEllipse, // 圆角椭圆的高度
int nHeightEllipse //圆角椭圆的宽度
);
4.创建任意形状的区域
HRGN ExtCreateRegion(
CONST XFORM *lpXform, // 变形数据的指针
DWORD nCount, // 数据在大小,以字节计
CONST RGNDATA *lpRgnData // 数据的指针
);
如果创建失败,所有的函数都返回NULL
代码实战(基础)
首先,创建一个项目,然后修改 InitInstance
函数,原始的函数如下
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 将实例句柄存储在全局变量中
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
直接在 UpdateWindow
函数下面,加上如下内容即可。这个代码很简单,就是调用API创建一个椭圆形形状,然后利用 SetWindowRgn
将这个窗口变成椭圆
HRGN hRgn = CreateEllipticRgn(100, 100, 600, 400);
SetWindowRgn(hWnd, hRgn, TRUE);
代码实战(进阶)
调用API只能做一些简单的形状,实际场景可能会复杂很多,因此我们常常用带透明度的图片来定制透明窗口
原理是遍历图片的每个像素点,如果这个点是透明的,则将其加入到指定区域中,最后调用 setWindowRgn
函数,将窗口限制在该区域中
首先,将菜单栏去掉,手动注释掉这段代码 wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_TEST2);
为了使得窗口能够移动,需要修改事件监听函数 WndProc
,加入如下代码
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
SendMessageA(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);
break;
(略)
完整代码如下,依旧是修改 InitInstance
函数
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 将实例句柄存储在全局变量中
//bmp文件是带alpha通道的32位深度图片,可由png图片转换得到
HBITMAP hBmp = (HBITMAP)LoadImageA(NULL, "alpha32_black.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
BITMAP bmp;
GetObject(hBmp, sizeof(BITMAP), &bmp);
int width = bmp.bmWidth;
int height = bmp.bmHeight;
//这里要将正常窗口的样式,改为对话框,用来去掉标题栏
HWND hWnd = CreateWindowW(szWindowClass, szTitle,
//WS_OVERLAPPEDWINDOW,
WS_POPUP,
CW_USEDEFAULT, 0, width, height, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
//指定窗口客户端区域的DC的句柄。若hWnd=NULL,则提取整个屏幕的DC
HDC hdc = GetDC(hWnd);
//该函数创建一个与指定设备兼容的内存设备上下文环境(DC)
HDC hdcMem = CreateCompatibleDC(hdc);
//这里把hbmp的位图选择到兼容DC memdc,之后这个兼容DC就拥有和hbmp同样大小的绘图区域,注意超出位图返回的GDI输出都是无效的
SelectObject(hdcMem, hBmp);
//创建矩形区域
HRGN hRgn = CreateRectRgn(0, 0, width, height);
COLORREF col;
HRGN rgnTemp;
//由于是逐像素遍历,所以非常的慢
for (int x = 0; x <= width; x++)
{
for (int y = 0; y <= height; y++)
{
//其中第一字节为 0 而且始终为 0,其它三个字节分别表示蓝色、绿色和红色,刚好和 RGB 的次序相反。
//这个结构体用起来挺别扭。对于COLORREF,我们通常使用宏RGB对其进行赋值。
col = GetPixel(hdcMem, x, y);
if ((col & 0x0000ff00) == 0)
{
// 创建一个矩形裁剪区域
rgnTemp = CreateRectRgn(x, y, x + 1, y + 1);
//合并两个区域
CombineRgn(hRgn, hRgn, rgnTemp, RGN_XOR);
//删除区域对象
DeleteObject(rgnTemp);
}
}
}
//设置指定窗口的显示状态。如果窗口之前可见,则返回值为非零。如果窗口之前被隐藏,则返回值为零
ShowWindow(hWnd, nCmdShow);
//更新指定窗口的客户区
UpdateWindow(hWnd);
//必须先展示出窗口showWindow,然后再对窗口上色,否则裁剪出来是白的(该函数既可以在updateWIndow之前,也可以在其之后)
BitBlt(hdc, 0, 0, width, height, hdcMem, 0, 0, SRCCOPY);
//函数释放设备上下文环境(DC)供其他应用程序使用
ReleaseDC(hWnd, hdc);
//设置重绘区域,该函数只能在UpdateWindow之后调用
SetWindowRgn(hWnd, hRgn, TRUE);
ReleaseDC(hWnd, hdcMem);
return TRUE;
}
注意事项
图片大小
由于要遍历每个像素,因此尽量将图片做的小点,不然会很慢
- 1.2MB的图片耗时4秒
- 800KB需要2秒左右
- 260KB基本能秒出
代码位置
众所周知,遍历图片中的每一个像素是一个非常耗时的操作,因此需要将其放置于 ShowWindow
函数之前,在窗口还没出现时去遍历。然后在窗口更新UpdateWindow
之后,去设置区域 SetWindowRgn
否则你会看到先展示未裁剪前的窗口,然后过个几秒,才变成裁剪后的窗口
因此,一定要将遍历像素GetPixel
放在显示窗口ShowWindow
之前
- 点赞
- 收藏
- 关注作者
评论(0)