Click here to Skip to main content
Click here to Skip to main content

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

By , 9 Aug 2010
 

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Mukit, Ataul
Chief Technology Officer Rational Technologies
Bangladesh Bangladesh
Member
You don't learn patterns, you just code it.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberlin 117n18 Oct '12 - 5:27 
GeneralMy vote of 4memberChristian Amado21 Aug '12 - 5:01 
QuestionMy vote of 3 - reasonmemberimagiro13 Jan '12 - 5:54 
AnswerRe: My vote of 3 - reasonmemberMukit, Ataul13 Jan '12 - 17:53 
GeneralMy vote of 3memberimagiro13 Jan '12 - 5:44 
QuestionMy vote of 5memberMember 78000405 Dec '11 - 19:46 
very nice, excellent man!!! d^^b let keep it up Thumbs Up | :thumbsup:
AnswerRe: My vote of 5memberMukit, Ataul6 Dec '11 - 4:58 
GeneralMy vote of 5memberMember 78000405 Dec '11 - 19:45 
GeneralNot work in VS2010 and also i want it in c# or vb.net.memberfixfify10 Feb '11 - 4:09 
GeneralRe: Not work in VS2010 and also i want it in c# or vb.net.memberMukit, Ataul8 Apr '11 - 21:14 
GeneralMy vote of 5memberMember 191619813 Oct '10 - 18:28 
GeneralRe: My vote of 5memberMukit, Ataul13 Oct '10 - 18:56 
GeneralMy vote of 5mvpMd. Marufuzzaman15 Sep '10 - 20:55 
GeneralRe: My vote of 5memberMukit, Ataul15 Sep '10 - 22:49 
GeneralRe: My vote of 5mvpMd. Marufuzzaman16 Sep '10 - 4:54 
GeneralMy vote of 5memberBigdeak13 Sep '10 - 23:04 
GeneralRe: My vote of 5memberMukit, Ataul14 Sep '10 - 0:45 
GeneralDoes not compile with VS2010 [modified]membermerano17 Aug '10 - 3:29 
GeneralRe: Does not compile with VS2010memberMukit, Ataul17 Aug '10 - 6:03 
GeneralRe: Does not compile with VS2010 [modified]memberMember 78000405 Dec '11 - 19:56 
GeneralMy vote of 5membersnowywhite13 Aug '10 - 8:49 
GeneralRe: My vote of 5memberMukit, Ataul25 Dec '10 - 18:22 
GeneralMy vote of 5membertareqsiraj10 Aug '10 - 19:41 
GeneralRe: My vote of 5memberMukit, Ataul11 Aug '10 - 3:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 9 Aug 2010
Article Copyright 2010 by Mukit, Ataul
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid