分层窗口的说明与使用(1)-创新互联

本文章适合有一定win32基础的人,此项目在Windows7及以下系统运行时可能会失败

成都创新互联专注于金州网站建设服务及定制,我们拥有丰富的企业做网站经验。 热诚为您提供金州营销型网站建设,金州网站制作、金州网页设计、金州网站官网定制、微信小程序开发服务,打造金州网络公司原创品牌,更为您提供金州网站排名全网营销落地服务。前言

分层窗口虽然对于大部分人来说比较生僻,但它的功能却非常强大,能轻易实现一些比较漂亮的UI界面。

UpdateLayeredWindow函数

UpdateLayeredWindow 函数 的作用是更新一个分层窗口,是分层窗口的核心,它的定义如下

//更新分层窗口的位置、大小、形状、内容和透明度
BOOL UpdateLayeredWindow(
  [in]           HWND          hWnd,
  [in, optional] HDC           hdcDst,
  [in, optional] POINT         *pptDst,
  [in, optional] SIZE          *psize,
  [in, optional] HDC           hdcSrc,
  [in, optional] POINT         *pptSrc,
  [in]           COLORREF      crKey,
  [in, optional] BLENDFUNCTION *pblend,
  [in]           DWORD         dwFlags
);

hWnd:分层窗口的句柄

hdcDst:屏幕 DC 的句柄

pptDst:分层窗口的位置

psize:分层窗口的新大小

hdcSrc:分层窗口的图面 DC 的句柄

pptSrc:指定层在设备上下文中的位置

crKey:指定要在组合分层窗口时使用的颜色键

pblend:指定要在组合分层窗口时使用的透明度值

dwFlags:此参数的取值可为下列值之一:

含义

ULW_ALPHA

0x00000002

使用 pblend作为混合函数。 如果显示模式为 256 种或更少颜色,则此值的效果与 ULW_OPAQUE的效果相同。

ULW_COLORKEY

0x00000001

使用 crKey作为透明度颜色。

ULW_OPAQUE

0x00000004

绘制不透明的分层窗口。

ULW_EX_NORESIZE

0x00000008

如果当前窗口大小与 psize中指定的大小不匹配,则强制 UpdateLayeredWindowIndirect 函数失败。

如果没看懂没关系,下面举例子时就明白了。

我们不妨按试试去填写这个函数:首先需要一个分层窗口,为了方便我们可以先创建一个简单的win32窗口

#include//注册窗口
void RegWindow(HINSTANCE hInstance, LPCWSTR lpClassName, WNDPROC wndProc, DWORD dwColor)
{
    WNDCLASS wnd;
    wnd.cbClsExtra = 0;
    wnd.cbWndExtra = 0;
    wnd.hbrBackground = (HBRUSH)(GetStockObject(dwColor));
    wnd.hCursor = LoadCursor(NULL, IDC_ARROW);
    wnd.hIcon = LoadCursor(NULL, IDI_APPLICATION);
    wnd.lpfnWndProc = wndProc;
    wnd.lpszClassName = lpClassName;
    wnd.lpszMenuName = NULL;
    wnd.style = CS_HREDRAW;
    wnd.hInstance = hInstance;
    RegisterClass(&wnd);
}
//窗口过程函数
LRESULT CALLBACK WindowProc(HWND hWnd, UINT Msg,
    WPARAM wParam, LPARAM lParam)
{
    switch (Msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hWnd, Msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE PrevhInstance, LPSTR lpCmdLine, int nCmdShow)
{
    //注册窗口                                  指定背景为白色
    RegWindow(hInstance, L"WINDOW", WindowProc, WHITE_BRUSH);
    //创建窗口
    HWND hWnd = CreateWindow(L"WINDOW", 0,
        WS_OVERLAPPEDWINDOW, 100, 100, 300, 300, NULL, NULL, hInstance, NULL);
    //                                  宽   高
    //显示窗口
    ShowWindow(hWnd, SW_SHOW);
    //更新窗口
    UpdateWindow(hWnd);
    //消息循环
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return 0;
}

这样我们就创建了一个宽和高为300,背景为白色的普通窗口(左上角图标为本机鼠标)

接下来就是创建一个分层窗口,为了方便,可以直接在消息循环前创建。

创建分层窗口要用 CreateWindowExW 函数 ,比较简单,直接上代码

...
//更新窗口
UpdateWindow(hWnd);

//创建分层窗口//

//注册分层窗口
RegWindow(hInstance, L"LayeredWindow", WindowProc, BLACK_BRUSH);
//创建分层窗口
HWND hLayeredWindow = CreateWindowEx(WS_EX_LAYERED, L"LayeredWindow", 0,
    WS_POPUP | WS_BORDER, 0, 0, 100, 100, NULL, NULL, hInstance, NULL);
//将分层窗口设为本窗口的子窗口
SetParent(hLayeredWindow, hWnd);

//消息循环
MSG msg;
...

这样就创建了一个宽和高为100,背景为黑色的普通窗口。

但运行程序后会发现并没有什么黑色窗口,这是因为更新分层窗口需要用的函数是UpdateLayeredWindow函数,现在应该考虑怎么使用这个函数了。

...
//将分层窗口设为本窗口的子窗口
SetParent(hLayeredWindow, hWnd);

//更新分层窗口//

//创建分层窗口的DC
HDC hLayeredWindowDC = GetDC(hLayeredWindow);
HDC hCompatibleDC = CreateCompatibleDC(hLayeredWindowDC);
//填充BLENDFUNCTION结构
BLENDFUNCTION blend = { 0 };
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
//控制显示位置
POINT ptDst = { 0, 0 };
//控制窗口大小
SIZE sizeWnd = { 100, 100 };
//为0就行
POINT pSrc = { 0, 0 };
//更新分层窗口
UpdateLayeredWindow(hLayeredWindow, hLayeredWindowDC, &ptDst, &sizeWnd, hCompatibleDC, &pSrc, NULL, &blend, ULW_ALPHA);
//释放DC
DeleteDC(hLayeredWindowDC);
DeleteDC(hCompatibleDC);

//消息循环
MSG msg;
...

它的第一个和第二个参数很简单,只需要填分层窗口的句柄和对应的DC就行了,对应的DC可以用GetDC函数 获取。第三个参数控制分层窗口显示位置,第四个参数控制分层窗口大小,第五个参数为用 CreateCompatibleDC 函数 获取的DC,第六个一般情况下填为零的POINT结构,第七个为NULL就行,第八个填 BLENDFUNCTION 结构 比较简单,最后一个参数填ULW_ALPHA,使窗口能设置透明度。

运行程序后发现窗口上并未出现一个黑色窗口,我们可以用 GetLastError 函数 查看错误代码。

...
//为0就行
POINT pSrc = { 0, 0 };
//更新分层窗口
int e1 = GetLastError();
UpdateLayeredWindow(hLayeredWindow, hLayeredWindowDC, &ptDst, &sizeWnd, hCompatibleDC, &pSrc, NULL, &blend, ULW_ALPHA);
int e2 = GetLastError();
//释放DC
DeleteDC(hLayeredWindowDC);
...

如果编写代码用的是visual studio,可以按f9打断点从而查看,也可以使用printf打印错误代码,要在win32窗口程序中显示cmd可以参考 在Win32应用程序中显示命令提示符(CMD)

运行程序后可以发现e1的值为0,就可以排除在更新分层窗口前发生错误的可能,e2的值为31,意为“连到系统上的设备没有发挥作用”

SetDIBits函数

为了解决上述问题,需要使用 setDIBits 函数 设置像素,它的定义如下

int SetDIBits(
  [in] HDC              hdc,
  [in] HBITMAP          hbm,
  [in] UINT             start,
  [in] UINT             cLines,
  [in] const VOID       *lpBits,
  [in] const BITMAPINFO *lpbmi,
  [in] UINT             ColorUse
);

它的功能是使用指定 DIB 中找到的颜色数据,在兼容的位图设置像素,网上对于它的介绍很少,介绍内容又比较隐晦难懂,一些内容看不懂很正常。接下来我会通过我的理解来介绍它。

它的第一个参数是设备上下文的句柄,但在这个项目中它似乎并不重要,因为当我把它设为NULL时也能照常显示出窗口,第二个参数是一个位图句柄,只需要创建一个位图传进去就行了,第三个参数一般直接填0就行,第四个参数填要显示的窗口的高度,如果以左上角为原点,此值需要为负数,第五个参数是RGB数组的指针,控制每个像素的颜色与透明度,第六个参数是一个 BITMAPINFO 结构 里面还有一个 BITMAPINFOHEADER 结构 都比较简单,最后一个参数参数必须是以下值之一

含义
DIB_PAL_COLORS颜色表包含一个由 16 位索引组成的数组,该数组包含在 由 hdc参数标识的设备上下文的逻辑调色板中
DIB_RGB_COLORS提供颜色表并包含文本 RGB 值

接下来就可以直接上代码

...
//为0就行
POINT pSrc = { 0, 0 };

//创建一副与当前DC兼容的位图
HBITMAP hCustomBmp = NULL;
hCustomBmp = CreateCompatibleBitmap(hLayeredWindowDC, 100, 100);
//将hCustomBmp指定到hCompatibleDC中
SelectObject(hCompatibleDC, hCustomBmp);
//填充bmpInfo
BITMAPINFO bmpInfo = { 0 };
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = 100;
bmpInfo.bmiHeader.biHeight = -(int)100;
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biCompression = BI_RGB;
bmpInfo.bmiHeader.biBitCount = 32;
//初始化位图
char* datas = new char[100 * 100 * 4];
ZeroMemory(datas, 100 * 100 * 4);
//填充像素
char* pdata = datas;
for (int i = 0; i< 100; i++)
{
    for (int j = 0; j< 100; j++)
    {
        //顺序并不是RGBA,而是BGRA
        *pdata = 233;   //Blue
        *(pdata + 1) = 222; //Ggeen
        *(pdata + 2) = 233; //Red
        *(pdata + 3) = 233; //Alpha
        pdata += 4;
    }
}
//使用指定的DIB颜色数据来设置位图中的像素
SetDIBits(NULL, hCustomBmp, 0, 100, datas, &bmpInfo, DIB_RGB_COLORS);

//更新分层窗口
...

虽然到现在你也可能不清楚SetDIBits函数到底是干什么的,但只需要知道它能让分层窗口显示出来就行了

现在运行程序就可以看到一个类似粉色的正方形在主窗口上了

这样就实现了一个分层窗口的创建与显示。

接下来我们就要开始封装这个分层窗口的创建与显示功能,方便创建多个分层窗口。

HWND CreateLayeredWindow(HINSTANCE hInstance, HWND hWnd, int iWidth, int iHeight, int iPosX, int iPosY, COLORREF* colBGRA)
{

    //注册分层窗口
    RegWindow(hInstance, L"LayeredWindow", WindowProc, BLACK_BRUSH);
    //创建分层窗口
    HWND hLayeredWindow = CreateWindowEx(WS_EX_LAYERED, L"LayeredWindow", 0,
        WS_POPUP | WS_BORDER, 0, 0, iWidth, iHeight, NULL, NULL, hInstance, NULL);

    //将分层窗口设为本窗口的子窗口
    SetParent(hLayeredWindow, hWnd);

    //更新分层窗口//

    //创建分层窗口的DC
    HDC hLayeredWindowDC = GetDC(hLayeredWindow);
    HDC hCompatibleDC = CreateCompatibleDC(hLayeredWindowDC);
    //填充BLENDFUNCTION结构
    BLENDFUNCTION blend = { 0 };
    blend.BlendOp = AC_SRC_OVER;
    blend.SourceConstantAlpha = 255;
    blend.AlphaFormat = AC_SRC_ALPHA;
    //控制显示位置
    POINT ptDst = { iPosX, iPosY };
    //控制窗口大小
    SIZE sizeWnd = { iWidth, iHeight };
    //为0就行
    POINT pSrc = { 0, 0 };

    //创建一副与当前DC兼容的位图
    HBITMAP hCustomBmp = NULL;
    hCustomBmp = CreateCompatibleBitmap(hLayeredWindowDC, iWidth, iHeight);
    //将hCustomBmp指定到hCompatibleDC中
    SelectObject(hCompatibleDC, hCustomBmp);
    //填充bmpInfo
    BITMAPINFO bmpInfo = { 0 };
    bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmpInfo.bmiHeader.biWidth = iWidth;
    bmpInfo.bmiHeader.biHeight = -iHeight;
    bmpInfo.bmiHeader.biPlanes = 1;
    bmpInfo.bmiHeader.biCompression = BI_RGB;
    bmpInfo.bmiHeader.biBitCount = 32;
    //使用指定的DIB颜色数据来设置位图中的像素
    SetDIBits(NULL, hCustomBmp, 0, iHeight, colBGRA, &bmpInfo, DIB_RGB_COLORS);
    //更新分层窗口
    UpdateLayeredWindow(hLayeredWindow, hLayeredWindowDC, &ptDst, &sizeWnd, hCompatibleDC, &pSrc, NULL, &blend, ULW_ALPHA);
    //释放DC
    DeleteDC(hLayeredWindowDC);
    DeleteDC(hCompatibleDC);    

    return hLayeredWindow;
}

就算看不懂也没关系,只要会用就行了

hInstance:实例句柄

hWnd:主窗口句柄

iWidth/iHeight:窗口宽高

iPosX/iPosY:窗口位置

colBGRA:窗口每个像素点的信息

RGBA与BGRA

因为一些特性,SetDIBits函数中RGB数组的指针指向的内容顺序为BGRA而不是RGBA

所以colBGRA内容顺序必须是BGRA。

如果你到现在都对RGBA和BGRA感觉陌生,那么接下来的例子也许会让你恍然大悟

//非win32窗口程序
#includeint main()
{
    //COLORREF就是存储RGBA的数据类型
    //Alpha默认为0
    //一个rgb
    COLORREF rgb = RGB(1,2,3);
    //bgr就是把rgb反过来
    COLORREF bgr = RGB(3,2,1);
    //colBGRA就是一个COLORREF数组,存储多个像素点的BGRA信息
    COLORREF bgr[9];
    
    return 0;
}

如果你到现在都没看懂,那我建议先去熟悉一下win32 GDI。

RGB宏明显不能满足我们,因为RGB宏它无法改变透明度,所以我们就需要一个RGBA宏,它的定义如下

#define RGBA(r,g,b,a)          (COLORREF)(((BYTE)(r) |((WORD)((BYTE)(g))<< 8)) |(((DWORD)((BYTE)(b))<< 16)) |(((DWORD)((BYTE)(a))<< 24)))

不用去看它的原理,会用就行。

现在我们就可以试着去使用我们刚才封装的创建分层窗口函数,也算是展示一遍完整代码

#include#define RGBA(r,g,b,a)          (COLORREF)(((BYTE)(r) |((WORD)((BYTE)(g))<< 8)) |(((DWORD)((BYTE)(b))<< 16)) |(((DWORD)((BYTE)(a))<< 24)))

//窗口过程函数
LRESULT CALLBACK WindowProc(HWND hWnd, UINT Msg,
    WPARAM wParam, LPARAM lParam)
{
    switch (Msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hWnd, Msg, wParam, lParam);
    }
    return 0;
}
//注册窗口函数
void RegWindow(HINSTANCE hInstance, LPCWSTR lpClassName, WNDPROC wndProc, DWORD dwColor)
{
    WNDCLASS wnd;
    wnd.cbClsExtra = 0;
    wnd.cbWndExtra = 0;
    wnd.hbrBackground = (HBRUSH)(GetStockObject(dwColor));
    wnd.hCursor = LoadCursor(NULL, IDC_ARROW);
    wnd.hIcon = LoadCursor(NULL, IDI_APPLICATION);
    wnd.lpfnWndProc = wndProc;
    wnd.lpszClassName = lpClassName;
    wnd.lpszMenuName = NULL;
    wnd.style = CS_HREDRAW;
    wnd.hInstance = hInstance;
    RegisterClass(&wnd);
}
//创建分层窗口
HWND CreateLayeredWindow(HINSTANCE hInstance, HWND hWnd, int iWidth, int iHeight, int iPosX, int iPosY, COLORREF* colRGBA)
{
    //创建分层窗口//

    //注册分层窗口
    RegWindow(hInstance, L"LayeredWindow", WindowProc, BLACK_BRUSH);
    //创建分层窗口
    HWND hLayeredWindow = CreateWindowEx(WS_EX_LAYERED, L"LayeredWindow", 0,
        WS_POPUP | WS_BORDER, 0, 0, iWidth, iHeight, NULL, NULL, hInstance, NULL);

    //将分层窗口设为本窗口的子窗口
    SetParent(hLayeredWindow, hWnd);

    //更新分层窗口//

    //创建分层窗口的DC
    HDC hLayeredWindowDC = GetDC(hLayeredWindow);
    HDC hCompatibleDC = CreateCompatibleDC(hLayeredWindowDC);
    //填充BLENDFUNCTION结构
    BLENDFUNCTION blend = { 0 };
    blend.BlendOp = AC_SRC_OVER;
    blend.SourceConstantAlpha = 255;
    blend.AlphaFormat = AC_SRC_ALPHA;
    //控制显示位置
    POINT ptDst = { iPosX, iPosY };
    //控制窗口大小
    SIZE sizeWnd = { iWidth, iHeight };
    //为0就行
    POINT pSrc = { 0, 0 };

    //创建一副与当前DC兼容的位图
    HBITMAP hCustomBmp = NULL;
    hCustomBmp = CreateCompatibleBitmap(hLayeredWindowDC, iWidth, iHeight);
    //将hCustomBmp指定到hCompatibleDC中
    SelectObject(hCompatibleDC, hCustomBmp);
    //填充bmpInfo
    BITMAPINFO bmpInfo = { 0 };
    bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmpInfo.bmiHeader.biWidth = iWidth;
    bmpInfo.bmiHeader.biHeight = -iHeight;
    bmpInfo.bmiHeader.biPlanes = 1;
    bmpInfo.bmiHeader.biCompression = BI_RGB;
    bmpInfo.bmiHeader.biBitCount = 32;
    //使用指定的DIB颜色数据来设置位图中的像素
    SetDIBits(NULL, hCustomBmp, 0, iHeight, colRGBA, &bmpInfo, DIB_RGB_COLORS);
    //更新分层窗口
    UpdateLayeredWindow(hLayeredWindow, hLayeredWindowDC, &ptDst, &sizeWnd, hCompatibleDC, &pSrc, NULL, &blend, ULW_ALPHA);
    //释放DC
    DeleteDC(hLayeredWindowDC);
    DeleteDC(hCompatibleDC);

    return hLayeredWindow;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE PrevhInstance, LPSTR lpCmdLine, int nCmdShow)
{
    //注册窗口
    RegWindow(hInstance, L"WINDOW", WindowProc, WHITE_BRUSH);
    //创建窗口
    HWND hWnd = CreateWindow(L"WINDOW", 0,
        WS_OVERLAPPEDWINDOW, 100, 100, 300, 300, NULL, NULL, hInstance, NULL);
    //显示窗口
    ShowWindow(hWnd, SW_SHOW);
    //更新窗口
    UpdateWindow(hWnd);

    //创建BGRA数据
    COLORREF* colBGRA = new COLORREF[100*100];
    //初始化
    ZeroMemory(colBGRA, 100 * 100 * sizeof(COLORREF));
    //填充半透明蓝色
    for (int i = 0; i< 100*100; i++)
    {
        //虽然用的是RGBA,但实际上是BGRA
        colBGRA[i] = RGBA(233, 0, 0, 128);
        //                 B    G    R   A
    }
    //创建分层窗口
    HWND hLW = CreateLayeredWindow(hInstance, hWnd, 100, 100, 0, 0, colBGRA);

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

不出意外的话效果是这样

现在已经把最最基础的分层窗口的创建给讲完了,接下来所有操作都是基于本篇完成的。一些比较漂亮的UI实现就放到下篇文章讲吧。

项目连接 github - LayeredWindowDemo

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


本文题目:分层窗口的说明与使用(1)-创新互联
当前路径:http://myzitong.com/article/dsdijd.html