在 Windows 7/Vista Aero 主题中绘制带位图的自定义标题栏






4.90/5 (42投票s)
在 Windows 7/Vista 的 Aero 主题中,显示位图并控制其在窗口标题栏中的透明度,以及自定义绘制标题栏。
引言
随着我们迈向 Windows XP 之后的下一个重要版本 Windows 7,我们受到雷德蒙德人(Paul DiLascia 创造的一个俏皮术语,用来指代微软的人)施加的一些限制;其中一个限制是,在 Windows 7/Vista 中,当 Aero 主题或玻璃主题激活时,无法在顶层窗口的标题栏中绘制任何内容(我更喜欢“玻璃”这个术语)。然而,在观察了 MS-Office 或 MS-Paint 应用程序中花哨的标题区域绘制后,我们中的一些人倾向于,或者有时被迫(由于客户要求)模仿同样的风格。
幸运的是,微软通过以下链接向我们敞开了大门:http://msdn.microsoft.com/en-us/library/bb688195(VS.85).aspx,以实现这个“神奇任务”的一部分(神奇任务 = 在标题区域进行自定义绘制)。
然而,那个链接或我浏览过的其他一些链接并没有展示如何在标题栏中绘制位图。起初看起来很容易,但仅仅通过常规方式加载位图然后将其 BitBlt 到设备上下文并不奏效。因此,在进行了一些研究和开发后,我成功实现了在标题区域显示带有透明背景的位图的目标。
我知道,一定有更好的方法来实现我在这里做的事情,但我对位图格式和 DIB 的知识有限,只能以一种业余的方式完成。我希望有人能就此提出更好的文章,但就目前而言,我必须满足于我在这里发布的内容 :(。
基本思路
如何实现自定义标题栏绘制的主要概念在上面我粘贴的链接中有讨论。
现在,要在标题栏中添加位图,您必须先加载一个 32 位位图。我在互联网上找到了一个根据文件名加载 32 位位图的代码,但我在此诚挚地向原始作者道歉,我实在记不清是从哪里获得这段精美的代码的。
但是,仅仅加载一个 32 位位图并在绘制标题栏时将其 BitBlt 到 DC 是不够的。您必须确保您的位图的 alpha 通道设置正确;否则,在标题栏中将不会绘制任何内容。此外,玩弄这个 alpha 通道值可以为您在标题栏中显示的位图带来很好的透明度效果。这是我最难弄清楚的部分。
将背景设置为透明相对简单,一旦弄清楚了它是如何工作的。在加载位图时,当前正在处理的任何像素如果与其被选为(背景)透明色的 RGB 值匹配,则被设置为 0x00000000;这意味着该像素是完全透明的。
这是完成所有工作的函数(即,加载具有正确 alpha 通道值的位图以 BitBlt 到设备上下文)
// LoadDIBSectionFromFile - Creates a DIB section from BMP file
// lpszFileName - Name of the BMP file
// ppvBits - to receive address of bitmap bits
// hSection - optional handle to a file mapping object
// dwOffset - offset to the bitmap bit values within hSection
// bMaskColor - Specifies whether there is a mask color to be treated as transparent
// clrTrans - Specifies the color to be treated as transparent
// dwAlphaFactor - Specifies the alpha channel value along with the rest
// of the pixels to manipulate the transparency of the original image
// (for example, dwAlphaFactor = 0x7F000000 specifies a semi transparent image)
HBITMAP LoadDIBSectionFromFile( LPCTSTR lpszFileName, LPVOID *ppvBits,
HANDLE hSection, DWORD dwOffset, BOOL bMaskColor/* = FALSE*/,
COLORREF clrTrans/* = -1*/, DWORD dwAlphaFactor/* = 0xFF000000*/)
{
LPVOID lpBits;
CSimpleFile file;
if( !file.Open( lpszFileName, CSimpleFile::modeRead) )
return NULL;
BITMAPFILEHEADER bmfHeader;
long nFileLen;
nFileLen = file.GetLength();
// Read file header
if (file.Read((LPSTR)&bmfHeader, sizeof(bmfHeader)) != sizeof(bmfHeader))
return NULL;
// File type should be 'BM'
if (bmfHeader.bfType != ((WORD) ('M' << 8) | 'B'))
return NULL;
BITMAPINFO *pbmInfo;
pbmInfo = (BITMAPINFO *)::GlobalAlloc(GMEM_FIXED,
sizeof(BITMAPINFO) + sizeof(RGBQUAD)*256 );
if (pbmInfo == NULL)
return NULL;
// Read the BITMAPINFO
file.Read( pbmInfo, sizeof(BITMAPINFO) + sizeof(RGBQUAD)*256 );
BITMAPINFO &bmInfo = *pbmInfo ;
HBITMAP hBmp = CreateDIBSection( NULL, pbmInfo,
DIB_RGB_COLORS, &lpBits,
hSection, dwOffset );
LPBYTE lpDIBBits; // Pointer to DIB bits
int nColors = bmInfo.bmiHeader.biClrUsed ?
bmInfo.bmiHeader.biClrUsed : 1 << bmInfo.bmiHeader.biBitCount;
if( bmInfo.bmiHeader.biBitCount > 8 )
lpDIBBits = (LPBYTE)((LPDWORD)(bmInfo.bmiColors +
bmInfo.bmiHeader.biClrUsed) +
((bmInfo.bmiHeader.biCompression ==
BI_BITFIELDS) ? 3 : 0));
else
lpDIBBits = (LPBYTE)(bmInfo.bmiColors + nColors);
int nOffset = sizeof(BITMAPFILEHEADER) + (lpDIBBits - (LPBYTE)pbmInfo);
file.Seek( nOffset, CSimpleFile::begin);
file.Read((LPSTR)lpBits, nFileLen - nOffset); //bmInfo.biSizeImage );
if( ppvBits )
*ppvBits = lpBits;
// Now I can go through the image and set the alpha channel
DWORD* lpdwPixel = (DWORD *)lpBits;
for (long x=0;x<bmInfo.bmiHeader.biWidth;x++)
for (long y=0;y<bmInfo.bmiHeader.biHeight;y++)
{
if(bMaskColor && *lpdwPixel == clrTrans)
{
*lpdwPixel = 0x00000000;
}
else
{
// Clear the alpha bits
*lpdwPixel &= 0x00FFFFFF;
// Set the alpha bits
*lpdwPixel |= dwAlphaFactor;
}
lpdwPixel++;
}
::GlobalFree(pbmInfo);
return hBmp;
}
您会注意到这里使用了 `CSimpleFile` 类,这是为了从文件中读取位图。
该类看起来像这样
class CSimpleFile
{
public:
// Flag values
enum OpenFlags {
modeRead = (int) 0x00000,
modeWrite = (int) 0x00001,
modeReadWrite = (int) 0x00002,
shareCompat = (int) 0x00000,
shareExclusive = (int) 0x00010,
shareDenyWrite = (int) 0x00020,
shareDenyRead = (int) 0x00030,
shareDenyNone = (int) 0x00040,
modeNoInherit = (int) 0x00080,
modeCreate = (int) 0x01000,
modeNoTruncate = (int) 0x02000,
typeText = (int) 0x04000, // typeText and typeBinary are
typeBinary = (int) 0x08000, // used in derived classes only
osNoBuffer = (int) 0x10000,
osWriteThrough = (int) 0x20000,
osRandomAccess = (int) 0x40000,
osSequentialScan = (int) 0x80000,
};
enum Attribute {
normal = 0x00,
readOnly = 0x01,
hidden = 0x02,
system = 0x04,
volume = 0x08,
directory = 0x10,
archive = 0x20
};
enum SeekPosition { begin = 0x0, current = 0x1, end = 0x2 };
CSimpleFile()
{
m_hFile = NULL;
m_nBytesRead = 0;
}
BOOL Open(LPCTSTR lpszFileName, UINT mode)
{
if(mode == modeRead)
{
m_hFile = CreateFile(lpszFileName, // file to open
GENERIC_READ, // open for reading
FILE_SHARE_READ, // share for reading
NULL, // default security
OPEN_EXISTING, // existing file only
FILE_ATTRIBUTE_NORMAL, // normal file
NULL); // no attr. template
if (m_hFile == INVALID_HANDLE_VALUE)
{
return 0;
}
}
}
BOOL Close()
{
BOOL bError = FALSE;
if (m_hFile != INVALID_HANDLE_VALUE)
bError = !::CloseHandle(m_hFile);
return bError;
}
int Read(void* lpBuf, int nBytesToRead)
{
m_nBytesRead = 0;
// Attempt a synchronous read operation.
BOOL bResult = ReadFile(m_hFile, lpBuf,
nBytesToRead, &m_nBytesRead, NULL) ;
// Check for end of file.
if (bResult && (m_nBytesRead == 0) )
{
// you are at the end of the file.
}
return m_nBytesRead;
}
ULONGLONG Seek(LONGLONG lOff, UINT nFrom)
{
LARGE_INTEGER liOff;
liOff.QuadPart = lOff;
liOff.LowPart = ::SetFilePointer(m_hFile, liOff.LowPart, &liOff.HighPart,
(DWORD)nFrom);
if (liOff.LowPart == (DWORD)-1)
if (::GetLastError() != NO_ERROR)
{
// error
}
return liOff.QuadPart;
}
int GetLength()
{
ULARGE_INTEGER liSize;
liSize.LowPart = ::GetFileSize(m_hFile, &liSize.HighPart);
if (liSize.LowPart == INVALID_FILE_SIZE)
if (::GetLastError() != NO_ERROR)
{
// error
}
return liSize.QuadPart;
}
HANDLE m_hFile;
DWORD m_nBytesRead;
};
关于这个类没有什么太多需要解释的,除了它封装了简单的文件读取,而这个类也是我与加载 32 位位图相关的代码一起找到的,同样,我为不记得我从哪里找到它的来源而深表歉意。
Using the Code
这是使用该函数加载 32 位位图并应用透明度的一个示例
//DWORD dwTransparencyFactor = 0x7F000000; // Semi Transparent,
DWORD dwTransparencyFactor = 0xFF000000; // Fully Opaque
HBITMAP hBmpRes = LoadDIBSectionFromFile(_T("Bitmap32.bmp"),
0, 0, 0, TRUE, RGB(255, 255, 255), dwTransparencyFactor);
// Draw the bitmap
BitBlt(hdcPaint, 0, 0, cx, cy, hdcRes, 0, 0, SRCCOPY);
请确保通过 `DeleteObject(..)` Win32 API 释放此方法返回的位图句柄;否则,将存在资源泄漏。
关注点
您必须记住,您使用的位图必须是 32 位位图;否则,它将无法在标题栏中正确绘制。上传的源代码中一个有趣的事情是,我实际上没有安装 Windows 7/Windows Vista SDK。因此,我创建了两个额外的头文件,dwmapi_proxy.h 和 UxThemeEx.h,它们包含了 Windows 7 API 的等效函数,例如 dwmapi.dll 的 `DWMIsCompositionEnabled`、`DwmDefWindowProc` 和 `DwmExtendFrameIntoClientArea`,以及 UxTheme.dll 的 `DrawThemedTextEx`。这些函数只是加载 dwmapi 和 UxTheme DLL,并使用 `GetProcAddress` 和函数指针调用所需的函数。
致谢
感谢(以及道歉)献给神秘开发者(至少对我来说,他是个谜),他编写了 `LoadDIBSectionFromFile` 的大部分代码,我对其进行了少量修改以实现我的目标。
此外,非常感谢 Tareq Ahmed Siraj 先生,在我对如何在 Aero 主题中绘制窗口标题栏有些迷茫时,他指明了正确的方向。
历史
- 文章上传日期:2010 年 8 月 9 日