分层窗口的说明与使用(2)


在上一篇文章 分层窗口的说明与使用(1) 中我们已经初步掌握了分层窗口的创建,本文将会基于上文中的知识试着制作出好看的窗口特效

我会尽量将代码放在一个文件里防止有些人看的稀里糊涂

本文篇幅较长,希望能够耐心看完

本文适合有一定win32基础的人

小小的改动

我们先拿出上篇文章的代码并找到CreateLayeredWindow函数

//创建分层窗口
HWND CreateLayeredWindow(HINSTANCE hInstance, HWND hWnd, int iWidth, int iHeight, int iPosX, int iPosY, COLORREF* colBGRA)
{

    //注册分层窗口
    RegWindow(hInstance, L"LayeredWindow", DefWindowProc, BLACK_BRUSH);
    //创建分层窗口
    HWND hLayeredWindow = CreateWindowEx(WS_EX_LAYERED, L"LayeredWindow", 0,
        WS_POPUP | WS_BORDER, 0, 0, iWidth, iHeight, hWnd, 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;
    //设置位图中的像素
    SetDIBits(NULL, hCustomBmp, 0, iHeight, colBGRA, &bmpInfo, DIB_RGB_COLORS);
    //更新分层窗口
    UpdateLayeredWindow(hLayeredWindow, hLayeredWindowDC, &ptDst, &sizeWnd, hCompatibleDC, &pSrc, NULL, &blend, ULW_ALPHA);
    //释放DC
    DeleteDC(hLayeredWindowDC);
    return hLayeredWindow;
}

为了方便,我们需要将colBGRA中的像素点数据从BGRA转为RGBA,若不知道RGBA和BGRA请看上一篇文章

因此我们需要一个翻转RGB值的函数,代码如下(我想我的注释应该够详细了,不需要多说什么)

void TurnRGB(COLORREF* srcCols, COLORREF* tagCols, DWORD dwSize, DWORD dwWidth)
{
    if (!srcCols || !tagCols || dwSize <= dwWidth || dwSize % dwWidth != 0)
        /*
        若srcCols或tagCols为NULL,意为没有源数据能翻转或没有存储翻转后数据的存放处
        dwSize <= dwWidth 意味着数据的宽度大于数据总大小,怎么可能0w0
        dwSize % dwWidth != 0 意味着像素点数据若以dwWidth为一行的个数,则无法拼成一个矩形,无法形成一个完整的窗口
        */
        return;
    DWORD dwHeight = dwSize / dwWidth;
    //计算高度
    for (DWORD index = 0; index < dwSize; index++)
    //遍历每个像素点并执行翻转操作
    {
        //col用于存储当前像素点的数据
        COLORREF col = srcCols[index];
        //翻转RGB值
        tagCols[index] = RGBA((BYTE)(col >> 16), (BYTE)(col >> 8), (BYTE)col, (BYTE)(col >> 24));
    }
}

srcCols:像素点数据

tagCols:接收像素点翻转后的数据的变量

dwSize:像素大小(总数量)

dwWidth:像素点数据宽度(就是分层窗口的宽)

将上面的函数放在CreateLayeredWindow之前便于调用,然后就可以对CreateLayeredWindow进行小小的改动

//创建分层窗口
HWND CreateLayeredWindow(HINSTANCE hInstance, HWND hWnd, int iWidth, int iHeight, int iPosX, int iPosY, COLORREF* colRGBA)
//将变量名colBGRA改为colRGBA
{
    DWORD dwColSize = iWidth * iHeight;
    COLORREF* colBGRA = new COLORREF[dwColSize];
    TurnRGB(colRGBA, colBGRA, dwColSize, iWidth);
    //注册分层窗口
    RegWindow(hInstance, L"LayeredWindow", DefWindowProc, BLACK_BRUSH);
    //创建分层窗口
    HWND hLayeredWindow = CreateWindowEx(WS_EX_LAYERED, L"LayeredWindow", 0,
        WS_POPUP | WS_BORDER, 0, 0, iWidth, iHeight, hWnd, 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;

    //设置位图中的像素
    SetDIBits(NULL, hCustomBmp, 0, iHeight, colBGRA, &bmpInfo, DIB_RGB_COLORS);

    //更新分层窗口
    UpdateLayeredWindow(hLayeredWindow, hLayeredWindowDC, &ptDst, &sizeWnd, hCompatibleDC, &pSrc, NULL, &blend, ULW_ALPHA);

    //释放DC
    DeleteDC(hLayeredWindowDC);
    return hLayeredWindow;
}

这样就可以直接传入RGBA值而不必先翻转RGBA值为BGRA值再传入了。

然后我们在找到CreateLayeredWindow函数里的这一行代码

//注册分层窗口
RegWindow(hInstance, L"LayeredWindow", WindowProc, BLACK_BRUSH);

将其中的"WindowProc"改为"DefWindowProc"

//注册分层窗口
RegWindow(hInstance, L"LayeredWindow", DefWindowProc, BLACK_BRUSH);

分层窗口并不需要自己的窗口过程函数,用微软提供的就行。

然后找到这一行

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

将倒数第四个参数改为hWnd

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

这样调用 GetParent函数 时就不会出错返回NULL了

接着找到WinMain函数

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, 800, 500, 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
        //                   R   G  B   A
        colBGRA[i] = RGBA(233, 0, 0, 128);
    }

    //创建分层窗口
    HWND hLW = CreateLayeredWindow(hInstance, hWnd, 100, 100, 0, 0, colBGRA);

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

将14~28行的代码删除或注释掉

...
    //更新窗口
    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
        //                   R   G  B   A
        //colBGRA[i] = RGBA(233, 0, 0, 128);
    //}

    //创建分层窗口
    //HWND hLW = CreateLayeredWindow(hInstance, hWnd, 100, 100, 0, 0, colBGRA);

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

现在的完整代码应该是这样的

#include <windows.h>

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

void TurnRGB(COLORREF* srcCols, COLORREF* tagCols, DWORD dwSize, DWORD dwWidth)
{
    if (!srcCols || !tagCols || dwSize <= dwWidth || dwSize % dwWidth != 0)
        /*
        若srcCols或tagCols为NULL,意为没有源数据能翻转或没有存储翻转后数据的存放处
        dwSize <= dwWidth 意味着数据的宽度大于数据总大小,怎么可能0w0
        dwSize % dwWidth != 0 意味着像素点数据若以dwWidth为一行的个数,则无法拼成一个矩形,无法形成一个完整的窗口
        */
        return;
    DWORD dwHeight = dwSize / dwWidth;
    //计算高度
    for (DWORD index = 0; index < dwSize; index++)
        //遍历每个像素点并执行翻转操作
    {
        //col用于存储当前像素点的数据
        COLORREF col = srcCols[index];
        //翻转RGB值
        tagCols[index] = RGBA((BYTE)(col >> 16), (BYTE)(col >> 8), (BYTE)col, (BYTE)(col >> 24));
    }
}

//创建分层窗口
HWND CreateLayeredWindow(HINSTANCE hInstance, HWND hWnd, int iWidth, int iHeight, int iPosX, int iPosY, COLORREF* colRGBA)
//将变量名colBGRA改为colRGBA
{
    DWORD dwColSize = iWidth * iHeight;
    COLORREF* colBGRA = new COLORREF[dwColSize];
    TurnRGB(colRGBA, colBGRA, dwColSize, iWidth);
    //注册分层窗口
    RegWindow(hInstance, L"LayeredWindow", DefWindowProc, BLACK_BRUSH);
    //创建分层窗口
    HWND hLayeredWindow = CreateWindowEx(WS_EX_LAYERED, L"LayeredWindow", 0,
        WS_POPUP | WS_BORDER, 0, 0, iWidth, iHeight, hWnd, 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;

    //设置位图中的像素
    SetDIBits(NULL, hCustomBmp, 0, iHeight, colBGRA, &bmpInfo, DIB_RGB_COLORS);

    //更新分层窗口
    UpdateLayeredWindow(hLayeredWindow, hLayeredWindowDC, &ptDst, &sizeWnd, hCompatibleDC, &pSrc, NULL, &blend, ULW_ALPHA);

    //释放DC
    DeleteDC(hLayeredWindowDC);
    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, 800, 500, 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
        //                   R   G  B   A
        //colBGRA[i] = RGBA(233, 0, 0, 128);
    //}

    //创建分层窗口
    //HWND hLW = CreateLayeredWindow(hInstance, hWnd, 100, 100, 0, 0, colBGRA);

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

现在我们就可以正式进入本文主题了。

悬停效果

拿360做例子

6d925e80fb4c44878eeb185a0ccfa1d0.gif

其实就是点击一个按钮后有一个框框住了按钮的效果,这种效果用于实现三态按钮。如果用纯win32按照正常的思路设计会比较麻烦,但用分层窗口就会比较容易

因为win32的按钮要检测鼠标悬停,无论是原版按钮还是自绘按钮,都比较麻烦,讲起来可能比较乱,所以本文不加以论述,也就是做不出如下效果

ba3d409c646b4fc6a49d8ffed73bdfc7.gif

 思路

我们要先创建几个按钮,然后检测到按钮按下后就将分层窗口移动到该按钮上,而动窗口需要MoveWindow函数

BOOL MoveWindow(
  [in] HWND hWnd,
  [in] int  X,
  [in] int  Y,
  [in] int  nWidth,
  [in] int  nHeight,
  [in] BOOL bRepaint
);

hWnd:要移动的窗口的hWnd

x,y:要移动到哪里(新位置的坐标)

nWidth,nHeight:窗口移动后的大小

bRepaint:是否重绘

实践

这里我们先创建4个按钮,而创建按钮需要在WM_CREATE消息里创建

而处理按钮事件需要接收WM_COMMAND消息

我们找到主窗口的窗口过程函数WindowProc,添加WM_CREATE与WM_COMMAND消息并处理

(若不知道为什么要处理WM_CREATE与WM_COMMAND消息,请先去学习win32编程基础)

LRESULT CALLBACK WindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    switch (Msg)
    {
    case WM_CREATE:
    {
        LPCREATESTRUCT pc = (LPCREATESTRUCT)lParam;
        break;
    }
    case WM_COMMAND:
    {
        WORD id = LOWORD(wParam);
        WORD code = HIWORD(wParam);
        HWND hwnd = (HWND)lParam;

        break;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hWnd, Msg, wParam, lParam);
    }

    return 0;
}

接着我们创建4个宽为120,高为150的按钮,分别命名为choice1、choice2、choice3、choice4,对应的id分别为1,、2、3、4

...
case WM_CREATE:
{
    LPCREATESTRUCT pc = (LPCREATESTRUCT)lParam;
    HWND hButton1 = CreateWindow(L"BUTTON", L"choice1", WS_CHILD | WS_VISIBLE, 10, 10, 120, 150, hWnd, (HMENU)1, pc->hInstance, NULL);
    HWND hButton2 = CreateWindow(L"BUTTON", L"choice2", WS_CHILD | WS_VISIBLE, 140, 10, 120, 150, hWnd, (HMENU)2, pc->hInstance, NULL);
    HWND hButton3 = CreateWindow(L"BUTTON", L"choice3", WS_CHILD | WS_VISIBLE, 270, 10, 120, 150, hWnd, (HMENU)3, pc->hInstance, NULL);
    HWND hButton4 = CreateWindow(L"BUTTON", L"choice4", WS_CHILD | WS_VISIBLE, 400, 10, 120, 150, hWnd, (HMENU)4, pc->hInstance, NULL);
    break;
}
...

接着我们在WM_COMMAND消息里处理按钮按下的消息

...
HWND hwnd = (HWND)lParam;
if (id == 1 && code == BN_CLICKED)
{
    MessageBox(hWnd, L"choice1", L"CLICK", 0);
}
else if (id == 2 && code == BN_CLICKED)
{
    MessageBox(hWnd, L"choice2", L"CLICK", 0);
}
else if (id == 3 && code == BN_CLICKED)
{
    MessageBox(hWnd, L"choice3", L"CLICK", 0);
}
else if (id == 4 && code == BN_CLICKED)
{
    MessageBox(hWnd, L"choice4", L"CLICK", 0);
}
break;
...

现在点击任意一个按钮就会弹出一个对话框,总体效果如下

3e3a44817fc74e14907e12430dd9f6fd.gif

 (颜色有点奇怪是因为gif本身的问题,虽然有按钮点丑...以后如果有时间我会专门写几篇关于按钮美化的文章)

接着我们要创建一个分层窗口用于实现悬停的效果

先声明全局变量并提前声明CreateLayeredWindow函数,还要定义一些关于分层窗口的宏便于使用

...
#define         RGBA(r,g,b,a)               (COLORREF)(((BYTE)(r) |((WORD)((BYTE)(g)) << 8)) |(((DWORD)((BYTE)(b)) << 16)) |(((DWORD)((BYTE)(a)) << 24)))
#define         LAYERED_WINDOW_WIDTH        120        //分层窗口的宽
#define         LAYERED_WINDOW_HEIGHT       150        //分层窗口的高

//声明全局变量用于存放分层窗口的句柄
HWND hLayeredWindow = NULL;

//也要提前声明TurnRGB函数否则会出错
void TurnRGB(COLORREF* srcCols, COLORREF* tagCols, DWORD dwSize, DWORD dwWidth);
//声明CreateLayeredWindow函数便于在WindowProc函数里使用
HWND CreateLayeredWindow(HINSTANCE hInstance, HWND hWnd, int iWidth, int iHeight, int iPosX, int iPosY, COLORREF* colRGBA);

//窗口过程函数
LRESULT CALLBACK WindowProc(HWND hWnd, UINT Msg,    WPARAM wParam, LPARAM lParam)
...

再在WM_CREATE消息里创建分层窗口

...
HWND hButton4 = CreateWindow(L"BUTTON", L"choice4", WS_CHILD | WS_VISIBLE, 400, 10, 120, 150, hWnd, (HMENU)4, pc->hInstance, NULL);
//创建一个红色半透明的像素矩阵
COLORREF* col = new COLORREF[120 * 150];
for (size_t i = 0; i < 120 * 150; i++)
//遍历填充颜色
{
    col[i] = RGBA(255, 0, 0, 20);
}

//创建分层窗口
//在(10,10)位置创建能使分层窗口与按钮重合
hLayeredWindow = CreateLayeredWindow(pc->hInstance, hWnd, LAYERED_WINDOW_WIDTH, LAYERED_WINDOW_HEIGHT, 10, 10, col);
if (!hLayeredWindow)
    //创建失败就取消创建直接结束窗口
    SendMessage(hWnd, WM_DESTROY, 0, 0);
break;
...

效果如图

21fadbf9b58948108e631af2ec32b671.png

 现在我们只需要使用MoveWindow函数移动分层窗口就行了

回到WM_COMMAND消息里,将MessageBox函数删除或注释,并使用MoveWindow函数将分层窗口移动到当前按钮的位置,代码如下

...
HWND hwnd = (HWND)lParam;

if (id == 1 && code == BN_CLICKED)
{
    //MessageBox(hWnd, L"choice1", L"CLICK", 0);
    //移动分层窗口到当前按钮位置,保持宽高不变,重绘窗口,下同
    MoveWindow(hLayeredWindow, 10, 10, LAYERED_WINDOW_WIDTH, LAYERED_WINDOW_HEIGHT, TRUE);
}
else if (id == 2 && code == BN_CLICKED)
{
    //MessageBox(hWnd, L"choice2", L"CLICK", 0);
    MoveWindow(hLayeredWindow, 140, 10, LAYERED_WINDOW_WIDTH, LAYERED_WINDOW_HEIGHT, TRUE);
}
else if (id == 3 && code == BN_CLICKED)
{
    //MessageBox(hWnd, L"choice3", L"CLICK", 0);
    MoveWindow(hLayeredWindow, 270, 10, LAYERED_WINDOW_WIDTH, LAYERED_WINDOW_HEIGHT, TRUE);
}
else if (id == 4 && code == BN_CLICKED)
{
    //MessageBox(hWnd, L"choice4", L"CLICK", 0);
    MoveWindow(hLayeredWindow, 400, 10, LAYERED_WINDOW_WIDTH, LAYERED_WINDOW_HEIGHT, TRUE);
}

break;
...

至此,一般的悬停效果就做好了,效果如下

84d737b276ce4a06801341b8c21ac319.gif

 完整代码:

#include <windows.h>

#define         RGBA(r,g,b,a)               (COLORREF)(((BYTE)(r) |((WORD)((BYTE)(g)) << 8)) |(((DWORD)((BYTE)(b)) << 16)) |(((DWORD)((BYTE)(a)) << 24)))
#define         LAYERED_WINDOW_WIDTH        120        //分层窗口的宽
#define         LAYERED_WINDOW_HEIGHT       150        //分层窗口的高

//声明全局变量用于存放分层窗口的句柄
HWND hLayeredWindow = NULL;

//也要提前声明TurnRGB函数否则会出错
void TurnRGB(COLORREF* srcCols, COLORREF* tagCols, DWORD dwSize, DWORD dwWidth);
//声明CreateLayeredWindow函数便于在WindowProc函数里使用
HWND CreateLayeredWindow(HINSTANCE hInstance, HWND hWnd, int iWidth, int iHeight, int iPosX, int iPosY, COLORREF* colRGBA);

//窗口过程函数
LRESULT CALLBACK WindowProc(HWND hWnd, UINT Msg,    WPARAM wParam, LPARAM lParam)
{
    switch (Msg)
    {
    case WM_CREATE:
    {
        LPCREATESTRUCT pc = (LPCREATESTRUCT)lParam;
        HWND hButton1 = CreateWindow(L"BUTTON", L"choice1", WS_CHILD | WS_VISIBLE, 10, 10, 120, 150, hWnd, (HMENU)1, pc->hInstance, NULL);
        HWND hButton2 = CreateWindow(L"BUTTON", L"choice2", WS_CHILD | WS_VISIBLE, 140, 10, 120, 150, hWnd, (HMENU)2, pc->hInstance, NULL);
        HWND hButton3 = CreateWindow(L"BUTTON", L"choice3", WS_CHILD | WS_VISIBLE, 270, 10, 120, 150, hWnd, (HMENU)3, pc->hInstance, NULL);
        HWND hButton4 = CreateWindow(L"BUTTON", L"choice4", WS_CHILD | WS_VISIBLE, 400, 10, 120, 150, hWnd, (HMENU)4, pc->hInstance, NULL);
        //创建一个红色半透明的像素矩阵
        COLORREF* col = new COLORREF[120 * 150];
        for (size_t i = 0; i < 120 * 150; i++)
            //遍历填充颜色
        {
            col[i] = RGBA(255, 0, 0, 20);
        }

        //创建分层窗口
        //在(10,10)位置创建能使分层窗口与按钮重合
        hLayeredWindow = CreateLayeredWindow(pc->hInstance, hWnd, LAYERED_WINDOW_WIDTH, LAYERED_WINDOW_HEIGHT, 10, 10, col);
        if (!hLayeredWindow)
            //创建失败就取消创建直接结束窗口
            SendMessage(hWnd, WM_DESTROY, 0, 0);
        break;
    }
    case WM_COMMAND:
    {
        WORD id = LOWORD(wParam);
        WORD code = HIWORD(wParam);
        HWND hwnd = (HWND)lParam;

        if (id == 1 && code == BN_CLICKED)
        {
            //MessageBox(hWnd, L"choice1", L"CLICK", 0);
            //移动分层窗口到当前按钮位置,保持宽高不变,重绘窗口,下同
            MoveWindow(hLayeredWindow, 10, 10, LAYERED_WINDOW_WIDTH, LAYERED_WINDOW_HEIGHT, TRUE);
        }
        else if (id == 2 && code == BN_CLICKED)
        {
            //MessageBox(hWnd, L"choice2", L"CLICK", 0);
            MoveWindow(hLayeredWindow, 140, 10, LAYERED_WINDOW_WIDTH, LAYERED_WINDOW_HEIGHT, TRUE);
        }
        else if (id == 3 && code == BN_CLICKED)
        {
            //MessageBox(hWnd, L"choice3", L"CLICK", 0);
            MoveWindow(hLayeredWindow, 270, 10, LAYERED_WINDOW_WIDTH, LAYERED_WINDOW_HEIGHT, TRUE);
        }
        else if (id == 4 && code == BN_CLICKED)
        {
            //MessageBox(hWnd, L"choice4", L"CLICK", 0);
            MoveWindow(hLayeredWindow, 400, 10, LAYERED_WINDOW_WIDTH, LAYERED_WINDOW_HEIGHT, TRUE);
        }

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

void TurnRGB(COLORREF* srcCols, COLORREF* tagCols, DWORD dwSize, DWORD dwWidth)
{
    if (!srcCols || !tagCols || dwSize <= dwWidth || dwSize % dwWidth != 0)
        /*
        若srcCols或tagCols为NULL,意为没有源数据能翻转或没有存储翻转后数据的存放处
        dwSize <= dwWidth 意味着数据的宽度大于数据总大小,怎么可能0w0
        dwSize % dwWidth != 0 意味着像素点数据若以dwWidth为一行的个数,则无法拼成一个矩形,无法形成一个完整的窗口
        */
        return;
    DWORD dwHeight = dwSize / dwWidth;
    //计算高度
    for (DWORD index = 0; index < dwSize; index++)
        //遍历每个像素点并执行翻转操作
    {
        //col用于存储当前像素点的数据
        COLORREF col = srcCols[index];
        //翻转RGB值
        tagCols[index] = RGBA((BYTE)(col >> 16), (BYTE)(col >> 8), (BYTE)col, (BYTE)(col >> 24));
    }
}
//创建分层窗口
HWND CreateLayeredWindow(HINSTANCE hInstance, HWND hWnd, int iWidth, int iHeight, int iPosX, int iPosY, COLORREF* colRGBA)
//将变量名colBGRA改为colRGBA
{
    DWORD dwColSize = iWidth * iHeight;
    COLORREF* colBGRA = new COLORREF[dwColSize];
    TurnRGB(colRGBA, colBGRA, dwColSize, iWidth);
    //注册分层窗口
    RegWindow(hInstance, L"LayeredWindow", DefWindowProc, BLACK_BRUSH);
    //创建分层窗口
    HWND hLayeredWindow = CreateWindowEx(WS_EX_LAYERED, L"LayeredWindow", 0,
        WS_POPUP | WS_BORDER, 0, 0, iWidth, iHeight, hWnd, 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;

    //设置位图中的像素
    SetDIBits(NULL, hCustomBmp, 0, iHeight, colBGRA, &bmpInfo, DIB_RGB_COLORS);

    //更新分层窗口
    UpdateLayeredWindow(hLayeredWindow, hLayeredWindowDC, &ptDst, &sizeWnd, hCompatibleDC, &pSrc, NULL, &blend, ULW_ALPHA);

    //释放DC
    DeleteDC(hLayeredWindowDC);
    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, 800, 500, 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
        //                   R   G  B   A
        //colBGRA[i] = RGBA(233, 0, 0, 128);
    //}

    //创建分层窗口
    //HWND hLW = CreateLayeredWindow(hInstance, hWnd, 100, 100, 0, 0, colBGRA);

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

当然,我们还可以添加滑动效果让它变高级一点,如下

52491cf868af4e97a661615384c5f1cc.gif

 (有卡顿是因为gif本身原因)

碍于篇幅有限,要是我写太多肯定没人看,所以剩下的就留到下次再讲吧

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值