Introduction
As we are moving forward to the next biggest thing after Windows XP, which is Windows 7, we are bounded by some constraints put forward by the Redmondians (a witty term introduced by Paul DiLascia to address the Microsoft guys); and one of the constraints is, the inability to paint something in the caption bar of a top level window in Windows 7/Vista with the Aero theme or Glass theme activated (I like the term Glass better). However, after observing the fancy drawing in the caption area and MS-Office or MS-Paint applications, some of us are inclined to, or sometimes forced to (because of client request) mimic the same.
Luckily, Microsoft has opened their gates to us through the following link: http://msdn.microsoft.com/en-us/library/bb688195(VS.85).aspx, to achieve some portion of this magical task (magical task = doing our own custom painting in the caption area).
However, that link or some other links I have gone through doesn't show how to paint a bitmap in the caption. It seemed easy at first, but just performing a bitblt to the device context after loading a bitmap the usual way was not doing the job for me. Hence, after a little bit of R&D, I was able to achieve my target of showing a bitmap with transparent background in the caption area.
I do know, there must be a better way to achieve what I did here, but my limitation in knowledge related to bitmap formats and DIBs only allowed me to do it in an amateurish way. I hope some body will come up with a better article regarding this, but for now, I have to be satisfied with what I have posted here so far :(.
The Basic Idea
The main concepts of how to achieve the custom caption painting is discussed in the link I pasted above.
Now, to add a bitmap to the caption, you have to first load a 32 bit bitmap. I found the code to load a 32 bit bitmap given a filename, somewhere in the internet, but I heartily apologize to to the original writer of the code, I just can't remember from where I got this beautiful piece of code.
But, just loading a 32 bit bitmap and bitblting to the DC while painting the caption is not enough. You have to make sure your bitmap's alpha channel is set properly; otherwise, nothing will be drawn in the caption. Also, playing with this alpha channel value can give you nice transparency effects for the bitmap to be shown in the caption. This was the toughest part for me to figure out.
Setting the background to transparent was simple enough after figuring out how it all worked. Whatever pixel being currently processed while loading the bitmap matches the RGB value selected as the (background) transparent color, is set to 0x00000000; meaning that pixel is fully transparent.
Here is the function that does the whole thing for me (i.e., load the bitmap with proper alpha channel values to BitBlt to the device context):
HBITMAP LoadDIBSectionFromFile( LPCTSTR lpszFileName, LPVOID *ppvBits,
HANDLE hSection, DWORD dwOffset, BOOL bMaskColor,
COLORREF clrTrans, DWORD dwAlphaFactor)
{
LPVOID lpBits;
CSimpleFile file;
if( !file.Open( lpszFileName, CSimpleFile::modeRead) )
return NULL;
BITMAPFILEHEADER bmfHeader;
long nFileLen;
nFileLen = file.GetLength();
if (file.Read((LPSTR)&bmfHeader, sizeof(bmfHeader)) != sizeof(bmfHeader))
return NULL;
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;
file.Read( pbmInfo, sizeof(BITMAPINFO) + sizeof(RGBQUAD)*256 );
BITMAPINFO &bmInfo = *pbmInfo ;
HBITMAP hBmp = CreateDIBSection( NULL, pbmInfo,
DIB_RGB_COLORS, &lpBits,
hSection, dwOffset );
LPBYTE lpDIBBits; 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);
if( ppvBits )
*ppvBits = lpBits;
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
{
*lpdwPixel &= 0x00FFFFFF;
*lpdwPixel |= dwAlphaFactor;
}
lpdwPixel++;
}
::GlobalFree(pbmInfo);
return hBmp;
}
You can notice there is a CSimpleFile
class used, and that is to read the bitmap from the file.
The class looks like this:
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;
};
There is nothing much to explain about this class, except it encapsulates simple file reading, and this too I found with the code related to loading the 32 bit bitmap, and again, I deeply apologize for not remembering the source from where I found it.
Using the Code
Here is an example of using the function to load a 32 bit bitmap and apply transparency to it:
DWORD dwTransparencyFactor = 0xFF000000;
HBITMAP hBmpRes = LoadDIBSectionFromFile(_T("Bitmap32.bmp"),
0, 0, 0, TRUE, RGB(255, 255, 255), dwTransparencyFactor);
BitBlt(hdcPaint, 0, 0, cx, cy, hdcRes, 0, 0, SRCCOPY);
Make sure you release the returned bitmap handle from this method through the DeleteObject(..)
Win32 API; otherwise, there will be a resource leak.
Points of Interest
You have to keep in mind that the bitmap you are using has to be a 32 bit bitmap; otherwise, it won't be drawn properly in the caption. An interesting thing about the source code uploaded is, I actually didn't have the Windows 7/Windows Vista SDK installed. Therefore, I created two extra headers, dwmapi_proxy.h and UxThemeEx.h, which contains the equivalent functions for the Windows 7 APIs, like DWMIsCompositionEnabled
, DwmDefWindowProc
, and DwmExtendFrameIntoClientArea
of dwmapi.dll, as well as DrawThemedTextEx
of UxTheme.dll. These functions just load the dwmapi and UxTheme DLLs and call the required functions using GetProcAddress
and function pointers.
Acknowledgements
Acknowledgements (+ apologies) goes to the mystery developer (at least to me, he's a mystery) who wrote the major portion of the LoadDIBSectionFromFile
which I modified a little to achieve my target.
Also, a lot of thanks goes to Mr. Tareq Ahmed Siraj for pointing me to the right direction when I was somewhat at a loss about how to paint the window caption in the Aero theme.
History
- Article uploaded: 9 August, 2010