C语言编写分层透明窗口时遇到的四个问题

我在尝试写C语言分层透明窗口的时候,遇到了下面几个问题

代码:

#include <windows.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_CREATE: {
            // 创建窗口的设备上下文
            HDC hdc = GetDC(hWnd);

            // 创建透明位图
            int width = 400;
            int height = 300;
            HBITMAP hBitmap = CreateCompatibleBitmap(hdc, width, height);

            // 创建与位图相关联的设备上下文
            HDC hdcMem = CreateCompatibleDC(hdc);
            SelectObject(hdcMem, hBitmap);

            // 绘制透明背景
            BLENDFUNCTION blend = { 0 };
            blend.BlendOp = AC_SRC_OVER;
            blend.BlendFlags = 0;
            blend.AlphaFormat = AC_SRC_ALPHA;
            blend.SourceConstantAlpha = 255; // 透明度(0-255,255为完全不透明)

            RECT rcClient;
            GetClientRect(hWnd, &rcClient);
            RECT rec = {10, 10, 100, 100};
            FillRect(hdcMem, &rcClient, CreateSolidBrush(RGB(255, 0, 0)));

            // 使用UpdateLayeredWindow函数设置窗口为透明窗口
            POINT ptDst = { 0, 0 };
            SIZE sizeWnd = { width, height };
            POINT ptSrc = { 0, 0 };
            UpdateLayeredWindow(hWnd,
                                hdc,
                                NULL,
                                &sizeWnd,
                                hdcMem,
                                &ptSrc,
                                0,
                                &blend,
                                ULW_ALPHA);

            // 释放资源
            DeleteDC(hdcMem);
            DeleteObject(hBitmap);
            ReleaseDC(hWnd, hdc);
        }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    const char* CLASS_NAME = "MyClass";

    WNDCLASSEX wc = { 0 };
    wc.cbSize = sizeof(wc);
    wc.lpfnWndProc = WndProc;
    wc.lpszClassName = "Name";
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.cbWndExtra = sizeof(LONG_PTR);
    wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wc.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.cbClsExtra = 0;
    wc.hbrBackground = CreateSolidBrush(RGB(255, 0, 0));

    // 注册窗口
    RegisterClassEx(&wc);

    // 创建窗口
    HWND hWnd = CreateWindowEx(
            WS_EX_LAYERED,
            "Name",
            "argptr->title",
            WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
            250,
            250,
            50 + 800,
            50 + 900,
            NULL,
            NULL,
            hInstance,
            NULL
    );

    ShowWindow(hWnd, nCmdShow);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}
上面的代码用UpdateLayeredWindow函数实现透明效果,但运行结果如下图:

img

问题一:这个窗口为什么没有菜单栏,且不能拖动呢?后来我又用SetLayeredWindowAttributes函数写了一个透明窗口,代码如下:
#include <Windows.h>

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            break;
        }
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow)
{
    // 定义窗口类
    const char CLASS_NAME[] = "MyWindowClass";
    WNDCLASSEX wc = { 0 };
    wc.cbSize = sizeof(wc);
    wc.lpfnWndProc = WindowProc;
    wc.lpszClassName = CLASS_NAME;
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.cbWndExtra = sizeof(LONG_PTR);
    wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wc.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.cbClsExtra = 0;
    wc.hbrBackground = CreateSolidBrush(RGB(255, 0, 0));

    // 注册窗口
    RegisterClassEx(&wc);

    // 创建窗口
    HWND hWnd = CreateWindowEx(
            WS_EX_LAYERED,
            CLASS_NAME,
            "透明窗口",
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
            NULL,
            NULL,
            hInstance,
            NULL
    );
    // 创建失败
    if (hWnd == NULL)
    {
        return -1;
    }

    // 设置窗口透明度
    SetLayeredWindowAttributes(
            hWnd,       // 窗口句柄
            0,          // 颜色键
            128,        // 透明度,取值为0-255
            LWA_ALPHA   //
    );

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

    // 消息循环
    MSG msg = { 0 };
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

运行结果:

img

可以看到这一次的窗口有菜单栏且可以拖动,那么UpdateLayeredWindow是否也可以实现这个效果呢?
问题二:这个窗口我明明使用的是WS_EX_LAYERED扩展,没有使用WS_EX_TRANSPARENT,但是却可以透过窗口点到下面的内容,这是为什么呢?
问题三:这个窗口仔细看的话,左上角与右上角不是一个直角而是圆弧:

img

看着有点难受,请问怎样解决?
问题四:

img

如上图,窗口透明度我已经设置为255了,却还是一个透明的窗口,255不应该是不透明了吗?为什么这个窗口还是透明的呢?
上面四个问题就是我遇到的所有问题了,望解答,感谢!

没看懂你最终想要实现什么效果

C/C++实现透明窗口程序
可以参考下

上述代码运行不成功?

首先,关于你问的第一个问题,UpdateLayeredWindow函数是用于更新一个已存在的分层窗口的透明度和颜色。但是,UpdateLayeredWindow函数并不负责处理窗口的菜单栏和拖动事件,如果窗口没有单独处理菜单栏和拖动事件,就会出现菜单栏不可见且不能拖动的情况。
第二个问题的话:尽管你没有明确使用WS_EX_TRANSPARENT扩展,但是在调用UpdateLayeredWindow函数时,如果设置了透明度和颜色,仍然会自动将窗口设置为透明。
然后第三个问题和第四个问题,应该是其他哪个地方样式设置的问题,建议对照下官方文档看看。

UpdateLayeredWindow函数是更新透明度和颜色的,没有拖动功能

引用chatgpt内容作答:
问题一:这个窗口为什么没有菜单栏,且不能拖动呢?

在代码中创建窗口的部分,你使用的窗口样式是WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,这些样式设置了窗口的标题栏、系统菜单和最小化按钮。但是,由于你使用了WS_EX_LAYERED扩展样式,该样式将窗口设置为分层窗口,从而可能会影响窗口的默认行为。

对于分层窗口,窗口管理器会处理鼠标事件,并将其传递给底层窗口或其他窗口。因此,在分层窗口上单击标题栏或拖动窗口的行为会被窗口管理器处理,而不会直接传递给窗口过程(WndProc)。这就解释了为什么你的窗口没有菜单栏且不能拖动。

如果你想要实现一个透明窗口并具有菜单栏和拖动功能,可以考虑使用其他方法,例如使用SetWindowLongPtr函数来设置窗口的样式和扩展样式,或者自定义绘制窗口的标题栏和边框。

问题二:这个窗口我明明使用的是WS_EX_LAYERED扩展,没有使用WS_EX_TRANSPARENT,但是却可以透过窗口点到下面的内容,这是为什么呢?

在使用UpdateLayeredWindow函数创建透明窗口时,使用的是ULW_ALPHA参数来指定透明度。这意味着窗口区域内的内容将根据指定的透明度进行绘制,但窗口本身并不会阻止鼠标事件穿透。

如果你希望窗口不可穿透,即鼠标事件不会透过窗口传递到下面的内容,可以考虑使用WS_EX_TRANSPARENT扩展样式。将CreateWindowEx函数的第一个参数中的WS_EX_LAYERED改为WS_EX_LAYERED | WS_EX_TRANSPARENT,然后使用UpdateLayeredWindow函数设置透明窗口的透明度。

问题三:这个窗口仔细看的话,左上角与右上角不是一个直角而是圆弧,看着有点难受,请问怎样解决?

这个问题可能与窗口的边框样式有关。你可以尝试使用SetWindowRgn函数来设置窗口的区域,以获得直角的边框。

首先,创建一个矩形区域,代表窗口的客户区域(不包括标题栏和边框)。然后,使用SetWindowRgn函数将该区域应用到窗口上。

以下是修改后的示例代码片段,演示如何使用SetWindowRgn函数来设置窗口的区域:

// 在 WM_CREATE 处理程序中添加以下代码
HRGN hRgn = CreateRectRgn(0, 0, width, height);
SetWindowRgn(hWnd, hRgn, TRUE);

请注意,这只是一个示例,你可以根据自己的需求调整区域的形状和位置。

问题四:如上图,窗口透明度我已经设置为255了,却还是一个透明的窗口,255不应该是不透明了吗?为什么这个窗口还是透明的呢?

在代码中,你正确地使用了UpdateLayeredWindow函数来设置窗口的透明度。将blend.SourceConstantAlpha设置为255确实表示完全不透明。

然而,窗口的透明度还受到窗口的背景颜色和绘制方式的影响。在代码中,你使用了CreateSolidBrush函数来创建红色的背景刷子,并将其应用于窗口的背景。

如果你希望窗口完全不透明,可以将blend.SourceConstantAlpha设置为255,并确保窗口的背景色也是完全不透明的。

如果你想要改变窗口的背景颜色,可以修改wc.hbrBackground中的颜色值或使用其他绘制方式来绘制窗口的背景。

回答部分参考、引用ChatGpt以便为您提供更准确的答案:

问题一:这个窗口为什么没有菜单栏,且不能拖动呢? 根据您提供的代码,创建窗口时,您使用了WS_OVERLAPPED样式,这样创建的窗口会显示标题栏、边框和关闭按钮,但不包括菜单栏和可拖动区域。如果您希望窗口具有菜单栏和可拖动功能,可以考虑使用其他样式,例如WS_POPUP、WS_CAPTION等。

问题二:这个窗口我明明使用的是WS_EX_LAYERED扩展,没有使用WS_EX_TRANSPARENT,但是却可以透过窗口点到下面的内容,这是为什么呢? 使用WS_EX_LAYERED样式创建的窗口是一个分层窗口,可以通过SetLayeredWindowAttributes函数设置窗口的透明度。在您的代码中,您设置了透明度为128(即半透明),因此窗口会显示下面的内容。如果您想要窗口完全不透明,可以将透明度设置为255。

问题三:这个窗口仔细看的话,左上角与右上角不是一个直角而是圆弧,看着有点难受,请问怎样解决? 这种情况可能是由于窗口的样式或区域设置引起的。您可以尝试在创建窗口时指定WS_EX_LAYERED样式的同时,使用WS_CLIPCHILDREN样式。另外,您还可以使用SetWindowRgn函数设置窗口的区域,以确保窗口的形状是直角而不是圆弧。

问题四:如上图,窗口透明度我已经设置为255了,却还是一个透明的窗口,255不应该是不透明了吗?为什么这个窗口还是透明的呢? 在使用UpdateLayeredWindow函数设置窗口透明度时,您需要将要显示的区域像素的Alpha通道值设置为非零值。根据您提供的代码,FillRect函数用于绘制窗口的透明背景,但您并未设置透明背景的Alpha值。您可以在绘制透明背景前,使用SetBkMode函数将背景模式设置为TRANSPARENT,以便绘制透明背景。

这些问题的解决方法可能需要结合具体的代码和窗口样式进行调整和测试。请根据您的需求和实际情况尝试上述建议,以获得您期望的窗口效果。