Win32 使用 SetLayeredWindowAttributes 制作透明窗体

举报
福州司马懿 发表于 2022/11/22 15:34:08 2022/11/22
【摘要】 函数原型SetLayeredWindowAttributes 用来设置分层窗口的透明度BOOL SetLayeredWindowAttributes( HWND hwnd, // 指定分层窗口句柄 COLORREF crKey, // 指定需要透明的背景颜色值,可用RGB()宏 BYTE bAlpha, // 设置透明度,0表示完全透明,255表示不透明 DWORD dwFlags // ...

函数原型

SetLayeredWindowAttributes 用来设置分层窗口的透明度

BOOL SetLayeredWindowAttributes(
	HWND hwnd, // 指定分层窗口句柄
	COLORREF crKey, // 指定需要透明的背景颜色值,可用RGB()宏
	BYTE bAlpha, // 设置透明度,0表示完全透明,255表示不透明
	DWORD dwFlags // 透明方式
);

dwFlags

dwFlags参数用来指明窗口的透明方式,可取以下值

  • LWA_ALPHA crKey参数无效,bAlpha参数有效
  • LWA_COLORKEY 窗体中的所有颜色为crKey的地方将变为透明,bAlpha参数无效。其常量值为1
  • LWA_ALPHA | LWA_COLORKEY crKey的地方将变为全透明,而其它地方根据bAlpha参数确定透明度。

窗口居中

要实现窗口居中,要先获取屏幕宽高

   //获取屏幕宽高
   int scWidth = GetSystemMetrics(SM_CXSCREEN);
   int scHeight = GetSystemMetrics(SM_CYSCREEN);

窗体宽高使用图片宽高

   //从项目目录下,加载图片文件
   HBITMAP hBmp = (HBITMAP)LoadImageA(NULL, IMG_PATH, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
   //创建并获取图片信息
   BITMAP bmp;
   GetObject(hBmp, sizeof(BITMAP), &bmp);
   //获取图片宽高
   int width = bmp.bmWidth;
   int height = bmp.bmHeight;

然后计算窗口居中时的xy坐标

   //计算窗口居中时,左上角的xy坐标
   int x = (scWidth - width) / 2;
   int y = (scHeight - height) / 2;

去掉窗口标题栏

要去掉窗口标题栏,只需将默认的窗口样式WS_OVERLAPPEDWINDOW改为WS_POPUP,即可。同时要填入居中时,窗口的xy坐标和窗口宽高

   HWND hWnd = CreateWindowW(szWindowClass, szTitle,
       //WS_OVERLAPPEDWINDOW,
       WS_POPUP,
       x, y, width, height, nullptr, nullptr, hInstance, nullptr);

将图片绘制到内存句柄

首先要创建一个与指定图片大小相等的内存设备上下文环境,然后将窗口显示出来,最后进行绘制。用完后,释放之前创建的上下文环境

注意:如果在showWindow之前调用BitBlt,则绘制不会生效

 //指定窗口客户端区域的DC的句柄。若hWnd=NULL,则提取整个屏幕的DC
   HDC hdc = GetDC(hWnd);
   //该函数创建一个与指定设备兼容的内存设备上下文环境(DC)
   HDC hdcMem = CreateCompatibleDC(hdc);
   //这里把hbmp的位图选择到兼容DC memdc,之后这个兼容DC就拥有和hbmp同样大小的绘图区域,注意超出位图返回的GDI输出都是无效的
   SelectObject(hdcMem, hBmp);

   //显示窗口
   ShowWindow(hWnd, nCmdShow);

   //绘制一定要在ShowWindow之后调用,否则绘图无效
   BitBlt(hdc, 0, 0, width, height, hdcMem, 0, 0, SRCCOPY);

   //函数释放设备上下文环境(DC)供其他应用程序使用
   ReleaseDC(hWnd, hdc);
   ReleaseDC(hWnd, hdcMem);

为窗口添加透明样式

为样式添加附加层,用来支持透明样式,dwFlags 为变量,可以填入下面三个参数,下面分别来看下实际的效果

   //为样式添加附加层,用来支持透明样式
   LONG nExStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
   nExStyle |= WS_EX_LAYERED;
   SetWindowLong(hWnd, GWL_EXSTYLE, nExStyle);
   //设置窗体属性
   SetLayeredWindowAttributes(hWnd, RGB(0, 0, 0), 128, dwFlags);

LWA_ALPHA

crKey参数无效,bAlpha参数有效

图片.png

LWA_COLORKEY

窗体中的所有颜色为crKey的地方将变为透明,bAlpha参数无效。其常量值为1

图片.png

LWA_ALPHA | LWA_COLORKEY

crKey的地方将变为全透明,而其它地方根据bAlpha参数确定透明度

图片.png

让窗口允许被拖动

首先普及一个知识,哪里是客户区域,哪里又算是非客户区?

NC的英文全称叫Not ClientRect,即非客户区。在C++窗体中,标题栏(Caption那栏)就是非客户区,标题栏以外的窗体就叫客户区。

注意:超出窗体的区域,既不是“客户区域”,也不是“非客户区域”

这个,可以在WndProc中打印WM_MOUSEMOVEWM_NCMOUSEMOVE进行测试

首先,还是打开控制台

#include <stdio.h>
void ShowConsole()
{
    AllocConsole();
    FILE* stream;
    freopen_s(&stream, "CON", "r", stdin);//重定向输入流
    freopen_s(&stream, "CON", "w", stdout);//重定向输入流
}

然后在监听事件处,添加打印。可以看到,鼠标移出窗体时不打印,窗体标题栏处打印NC事件,窗体内打印WM_MOUSEMOVE事件

//
//  函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目标: 处理主窗口的消息。
//
//  WM_COMMAND  - 处理应用程序菜单
//  WM_PAINT    - 绘制主窗口
//  WM_DESTROY  - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE:
        ShowConsole();
        break;
    case WM_MOUSEMOVE:
        printf("WM_MOUSEMOVE\n");
        break;
    case WM_NCMOUSEMOVE:
        printf("WM_NCMOUSEMOVE\n");
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 在此处添加使用 hdc 的任何绘图代码...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

因此,让WS_POPUP风格的窗口支持拖动,可以在鼠标按下时,发送一个非客户区事件WM_NCLBUTTONDOWN,事件的内容是 —— 标题栏被按下HTCAPTION。事件通过SendMessageA发送

//
//  函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目标: 处理主窗口的消息。
//
//  WM_COMMAND  - 处理应用程序菜单
//  WM_PAINT    - 绘制主窗口
//  WM_DESTROY  - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_LBUTTONDOWN:
        //模拟按下标题栏区域,让窗口可移动
        SendMessageA(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 在此处添加使用 hdc 的任何绘图代码...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

完整代码

// test.cpp : 定义应用程序的入口点。
//

#include "framework.h"
#include "test.h"
#include <stdio.h>

#define MAX_LOADSTRING 100
#define IMG_PATH "alpha32_black.bmp"

// 全局变量:
HINSTANCE hInst;                                // 当前实例
WCHAR szTitle[MAX_LOADSTRING];                  // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING];            // 主窗口类名

// 此代码模块中包含的函数的前向声明:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: 在此处放置代码。

    // 初始化全局字符串
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_TEST, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // 执行应用程序初始化:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TEST));

    MSG msg;

    // 主消息循环:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}



//
//  函数: MyRegisterClass()
//
//  目标: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TEST));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

void ShowConsole()
{
    AllocConsole();
    FILE* stream;
    freopen_s(&stream, "CON", "r", stdin);//重定向输入流
    freopen_s(&stream, "CON", "w", stdout);//重定向输入流
}

//
//   函数: InitInstance(HINSTANCE, int)
//
//   目标: 保存实例句柄并创建主窗口
//
//   注释:
//
//        在此函数中,我们在全局变量中保存实例句柄并
//        创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // 将实例句柄存储在全局变量中

   //从项目目录下,加载图片文件
   HBITMAP hBmp = (HBITMAP)LoadImageA(NULL, IMG_PATH, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
   //创建并获取图片信息
   BITMAP bmp;
   GetObject(hBmp, sizeof(BITMAP), &bmp);
   //获取图片宽高
   int width = bmp.bmWidth;
   int height = bmp.bmHeight;
   //获取屏幕宽高
   int scWidth = GetSystemMetrics(SM_CXSCREEN);
   int scHeight = GetSystemMetrics(SM_CYSCREEN);
   //计算窗口居中时,左上角的xy坐标
   int x = (scWidth - width) / 2;
   int y = (scHeight - height) / 2;

   //创建窗口,并将窗体样式设置为无标题栏的弹窗
   HWND hWnd = CreateWindowW(szWindowClass, szTitle,
       //WS_OVERLAPPEDWINDOW,
       WS_POPUP,
       x, y, 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);

   //显示窗口
   ShowWindow(hWnd, nCmdShow);

   //绘制一定要在ShowWindow之后调用,否则绘图无效
   BitBlt(hdc, 0, 0, width, height, hdcMem, 0, 0, SRCCOPY);

   //为样式添加附加层,用来支持透明样式
   LONG nExStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
   nExStyle |= WS_EX_LAYERED;
   SetWindowLong(hWnd, GWL_EXSTYLE, nExStyle);
   //设置窗体属性
   SetLayeredWindowAttributes(hWnd, RGB(0, 0, 0), 128, LWA_COLORKEY);

   //函数释放设备上下文环境(DC)供其他应用程序使用
   ReleaseDC(hWnd, hdc);
   ReleaseDC(hWnd, hdcMem);

   UpdateWindow(hWnd);

   return TRUE;
}

//
//  函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目标: 处理主窗口的消息。
//
//  WM_COMMAND  - 处理应用程序菜单
//  WM_PAINT    - 绘制主窗口
//  WM_DESTROY  - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE:
        ShowConsole();
    case WM_MOUSEMOVE:
        printf("WM_MOUSEMOVE\n");
        break;
    case WM_NCMOUSEMOVE:
        printf("WM_NCMOUSEMOVE\n");
        break;
    case WM_LBUTTONDOWN:
        //模拟按下标题栏区域,让窗口可移动
        SendMessageA(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 在此处添加使用 hdc 的任何绘图代码...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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