Win32 通过 SetWindowRgn 创建透明窗体

举报
福州司马懿 发表于 2022/11/16 18:01:45 2022/11/16
【摘要】 函数介绍Window 支持使用 SetWindowRgn 创建各种形状的透明窗口,函数原型如下int SetWindowRgn( HWND hWnd, // 要调整的窗口的句柄 HRGN hRgn, // 区域的句柄 BOOL bRedraw // 是否立即重画窗口); //返回非0值如果成功,否则返回0Win32 API提供了许多现成的函数可以调用。只不过简比较简单的方法只能创建...

函数介绍

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);

图片.png

代码实战(进阶)

调用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之前

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。