Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Draw custom caption with bitmap in Windows 7/Vista Aero theme

0.00/5 (No votes)
9 Aug 2010 3  
Display a bitmap and control its transparency in the caption of a window, as well as custom draw the caption in the Aero theme of Windows 7/Vista.

screenshot.jpg

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):

// 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;
}

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 =  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);

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here