几天前在 CSDN 上有人问及一个问题:如何使得一个半透明窗口上,控件所占据的位置是不透明的?
开始有人提到使用 SetLayeredWindowAttributes() API,但考虑到该 API 会将整个窗口设置为一致的透明度,显然不可能满足问题中的要求。所以老汉认为应该使用 UpdateLayeredWindow() API 来完成这一工作,为了不重复输入,把我当时的回复抄录如下:
真正可以使用的 API 是 UpdateLayeredWindow()。此函数可以根据一幅选入到 DC 中去的 32 位位图的每个像素的 Alpha 通道值设置窗口上对应像素的半透明度。其实现在楼主要做得就是,总是生成一幅和窗口大小一样的 32 位位图,把控件占据的区域的位图像素的 Alpha 通道值全部设置为 255,即不透明,而其余地方的像素则可以根据需要设置为适当的半透明,然后再调用本函数即可。需要注意的是,如果窗口的大小可以改变的话,显然每次都需 要动态生成此位图,并调用本函数对窗口进行更新。
为了验证这一想法,我特意完成了一个类 CWindowUpdater,代码附后。使用者仅需在顶级窗口的 WM_SIZE 消息的响应中调用 CWindowUpdater::Update(hwnd) 即可。
需要说明的是,这种方法虽然在视觉上解决了上述问题,但是带来了另外一个问题。经过 UpdateLayeredWindow() 作用的窗口,其内容会因此而变成一幅静态图像,所以后果会成为,虽然控件是不透明的,但是用户与控件交互(例如鼠标点击,键盘输入等)时原本应该有的视觉 反馈(例如按钮成为下压状态)却全部不可见了,这使得这种方法基本上是鸡肋,并没有真正意义上的实用价值。如果有哪位实现了更完美的解决方案,望不吝赐 教。不过,它至少在技术上演示了如何设置窗口上每个像素的透明度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
class CWindowUpdater { typedef BOOL (WINAPI *fnUpdateLayeredWindow)(HWND hWnd, HDC hdcDst, LPPOINT pptDst, LPSIZE psize, HDC hdcSrc, LPPOINT pptSrc, COLORREF crKey, PBLENDFUNCTION pblend, DWORD dwFlags); static int BytesPerLine(int iWidth, int iBitsPerPixel) { return ((iWidth * iBitsPerPixel + 31) & (~31)) >> 3; } public: static BOOL Update(HWND hwnd) { static fnUpdateLayeredWindow _fnUpdateLayeredWindow = (fnUpdateLayeredWindow)-1; if(_fnUpdateLayeredWindow == (fnUpdateLayeredWindow)-1) { (FARPROC&)_fnUpdateLayeredWindow = GetProcAddress(GetModuleHandle(_T("USER32.DLL")), "UpdateLayeredWindow"); } if(!_fnUpdateLayeredWindow) return FALSE; RECT rc; if(!GetWindowRect(hwnd, &rc)) return FALSE; SIZE size = { rc.right - rc.left, rc.bottom - rc.top }; HDC hdcMem = CreateCompatibleDC(NULL); if(!hdcMem) return FALSE; // 1. create a 32-bit memory bitmap according to the window's size BITMAPINFOHEADER bmih = { 0 }; // Populate BITMAPINFO header bmih.biSize = sizeof(BITMAPINFOHEADER); bmih.biWidth = size.cx; bmih.biHeight = size.cy; bmih.biPlanes = 1; bmih.biBitCount = 32; bmih.biCompression = BI_RGB; bmih.biClrUsed = 0; bmih.biSizeImage = BytesPerLine(size.cx, 32) * size.cy; PVOID pvBits = NULL; HBITMAP hbmpMem = CreateDIBSection(NULL, (PBITMAPINFO)&bmih, DIB_RGB_COLORS, &pvBits, NULL, 0); if(hbmpMem) { HGDIOBJ hbmpOld = SelectObject(hdcMem, hbmpMem); // 2. get window's content SendMessage(hwnd, WM_PRINT, (WPARAM)hdcMem, (LPARAM)PRF_NONCLIENT | PRF_CLIENT | PRF_CHILDREN | PRF_CHECKVISIBLE); // 3. get rid of the areas occupied by children // SetWindowTransparency(hwnd, hdcMem, hbmpMem, 128, FALSE); SetWindowTransparency(hwnd, pvBits, size.cx, size.cy, 128, FALSE); // 4. pre-multiply rgb channels with alpha channel // PreMultiplyRGBChannels(hdcMem, hbmpMem); PreMultiplyRGBChannels(pvBits, size.cx, size.cy); // 5. ensure the WS_EX_LAYERED extended style is there SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | 0x00080000); // WS_EX_LAYERED // 6. update the window POINT ptSrc = { 0, 0 }; // start point of the copy from memory DC to screen DC BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; _fnUpdateLayeredWindow(hwnd, NULL, (LPPOINT)&rc, &size, hdcMem, &ptSrc, 0, &bf, 0x00000002); // ULW_ALPHA // clean up SelectObject(hdcMem, hbmpOld); DeleteObject(hbmpMem); } DeleteDC(hdcMem); return TRUE; } static BOOL PreMultiplyRGBChannels(HDC hdc, HBITMAP hbmp) { if(!hdc || !hbmp) return FALSE; BITMAPINFO bmi = { 0 }; bmi.bmiHeader.biSize = sizeof(bmi); if(!GetDIBits(hdc, hbmp, 0, 1, 0, &bmi, DIB_RGB_COLORS) || bmi.bmiHeader.biBitCount != 32) return FALSE; PBYTE pBuff = new BYTE[bmi.bmiHeader.biSizeImage + 0x20]; if(!pBuff) return FALSE; BOOL bRet = FALSE; bmi.bmiHeader.biSize = sizeof(bmi); bmi.bmiHeader.biCompression = BI_RGB; if(GetDIBits(hdc, hbmp, 0, bmi.bmiHeader.biHeight, (PVOID)pBuff, &bmi, DIB_RGB_COLORS)) { PreMultiplyRGBChannels(pBuff, bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight); bRet = SetDIBits(hdc, hbmp, 0, bmi.bmiHeader.biHeight, (PVOID)pBuff, &bmi, DIB_RGB_COLORS); } delete[] pBuff; return bRet; } static BOOL PreMultiplyRGBChannels(PVOID pvBits, int cx, int cy) { if(!pvBits || cx <= 0 || cy <= 0) return FALSE; // pre-multiply rgb channels with alpha channel for(int y=0; y<cy; y++) { PBYTE pPixel = ((PBYTE)pvBits) + cx * 4 * y; for(int x=0; x<cx; x++) { pPixel[0] = pPixel[0] * pPixel[3] / 255; pPixel[1] = pPixel[1] * pPixel[3] / 255; pPixel[2] = pPixel[2] * pPixel[3] / 255; pPixel += 4; } } return TRUE; } static BOOL SetWindowTransparency(HWND hwnd, HDC hdc, HBITMAP hbmp, int iAlpha, BOOL bExcludeChildren) { if(!hwnd || !IsWindow(hwnd) || !hdc || !hbmp || iAlpha < 0 || iAlpha > 255) return FALSE; BITMAPINFO bmi = { 0 }; bmi.bmiHeader.biSize = sizeof(bmi); if(!GetDIBits(hdc, hbmp, 0, 1, 0, &bmi, DIB_RGB_COLORS) || bmi.bmiHeader.biBitCount != 32) return FALSE; PBYTE pBuff = new BYTE[bmi.bmiHeader.biSizeImage + 0x20]; if(!pBuff) return FALSE; BOOL bRet = FALSE; bmi.bmiHeader.biSize = sizeof(bmi); bmi.bmiHeader.biCompression = BI_RGB; if(GetDIBits(hdc, hbmp, 0, bmi.bmiHeader.biHeight, (PVOID)pBuff, &bmi, DIB_RGB_COLORS)) { SetWindowTransparency(hwnd, pBuff, bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight, iAlpha, bExcludeChildren); bRet = SetDIBits(hdc, hbmp, 0, bmi.bmiHeader.biHeight, (PVOID)pBuff, &bmi, DIB_RGB_COLORS); } delete[] pBuff; return bRet; } static BOOL SetWindowTransparency(HWND hwnd, PVOID pvBits, int cx, int cy, int iAlpha, BOOL bExcludeChildren) { if(!hwnd || !IsWindow(hwnd) || !pvBits || cx <= 0 || cy <= 0 || iAlpha < 0 || iAlpha > 255) return FALSE; RECT rcWindow; GetWindowRect(hwnd, &rcWindow); MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&rcWindow, 2); // 1. get all children HRGN hrgnChildren = CreateRectRgn(0, 0, 0, 0); RECT rc; HWND hwndChild = GetWindow(hwnd, GW_CHILD); while(hwndChild) { GetWindowRect(hwndChild, &rc); // in screen coord' MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&rc, 2); // in client coord' OffsetRect(&rc, -rcWindow.left, -rcWindow.top); // in window coord' HRGN hrgnChild = CreateRectRgnIndirect(&rc); CombineRgn(hrgnChildren, hrgnChildren, hrgnChild, RGN_OR); DeleteObject(hrgnChild); hwndChild = GetWindow(hwndChild, GW_HWNDNEXT); } for(int y=0; y<cy; y++) { PBYTE pPixel = ((PBYTE)pvBits) + cx * 4 * y; for(int x=0; x<cx; x++) { if(PtInRegion(hrgnChildren, x, cy - y)) pPixel[3] = 255; else pPixel[3] = iAlpha; pPixel += 4; } } DeleteObject(hrgnChildren); return TRUE; } }; |