Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / Win32

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

Rate me:
Please Sign up or sign in to vote.
4.90/5 (43 votes)
9 Aug 2010CPOL4 min read 88.8K   2.1K   56   30
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):

C++
// 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:

C==
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:

C++
//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)


Written By
Technical Lead Kotha Technologies
Bangladesh Bangladesh
If you are not in - you are out !
- Chapter 1

Comments and Discussions

 
QuestionSystem Menu Pin
jung-kreidler7-Feb-19 23:35
jung-kreidler7-Feb-19 23:35 
QuestionMFC 6 Pin
Member 1091740618-Sep-14 4:38
Member 1091740618-Sep-14 4:38 
AnswerRe: MFC 6 Pin
Mukit, Ataul21-Sep-14 5:59
Mukit, Ataul21-Sep-14 5:59 
BugMultimonitor Bug Pin
Alex Culea2-Apr-14 11:15
Alex Culea2-Apr-14 11:15 
GeneralRe: Multimonitor Bug Pin
Member 860500024-May-16 2:21
Member 860500024-May-16 2:21 
GeneralRe: Multimonitor Bug Pin
Alex Culea28-Sep-17 3:16
Alex Culea28-Sep-17 3:16 
GeneralMy vote of 5 Pin
lin 117n18-Oct-12 5:27
lin 117n18-Oct-12 5:27 
GeneralMy vote of 4 Pin
Christian Amado21-Aug-12 5:01
professionalChristian Amado21-Aug-12 5:01 
QuestionMy vote of 3 - reason Pin
imagiro13-Jan-12 5:54
imagiro13-Jan-12 5:54 
AnswerRe: My vote of 3 - reason Pin
Mukit, Ataul13-Jan-12 17:53
Mukit, Ataul13-Jan-12 17:53 
GeneralMy vote of 3 Pin
imagiro13-Jan-12 5:44
imagiro13-Jan-12 5:44 
QuestionMy vote of 5 Pin
VinhTran_5-Dec-11 19:46
VinhTran_5-Dec-11 19:46 
AnswerRe: My vote of 5 Pin
Mukit, Ataul6-Dec-11 4:58
Mukit, Ataul6-Dec-11 4:58 
GeneralMy vote of 5 Pin
VinhTran_5-Dec-11 19:45
VinhTran_5-Dec-11 19:45 
GeneralNot work in VS2010 and also i want it in c# or vb.net. Pin
fixfify10-Feb-11 4:09
fixfify10-Feb-11 4:09 
GeneralRe: Not work in VS2010 and also i want it in c# or vb.net. Pin
Mukit, Ataul8-Apr-11 21:14
Mukit, Ataul8-Apr-11 21:14 
GeneralMy vote of 5 Pin
Member 191619813-Oct-10 18:28
Member 191619813-Oct-10 18:28 
GeneralRe: My vote of 5 Pin
Mukit, Ataul13-Oct-10 18:56
Mukit, Ataul13-Oct-10 18:56 
GeneralMy vote of 5 Pin
Md. Marufuzzaman15-Sep-10 20:55
professionalMd. Marufuzzaman15-Sep-10 20:55 
GeneralRe: My vote of 5 Pin
Mukit, Ataul15-Sep-10 22:49
Mukit, Ataul15-Sep-10 22:49 
GeneralRe: My vote of 5 Pin
Md. Marufuzzaman16-Sep-10 4:54
professionalMd. Marufuzzaman16-Sep-10 4:54 
GeneralMy vote of 5 Pin
Bigdeak13-Sep-10 23:04
Bigdeak13-Sep-10 23:04 
GeneralRe: My vote of 5 Pin
Mukit, Ataul14-Sep-10 0:45
Mukit, Ataul14-Sep-10 0:45 
GeneralDoes not compile with VS2010 [modified] Pin
merano17-Aug-10 3:29
merano17-Aug-10 3:29 
GeneralRe: Does not compile with VS2010 Pin
Mukit, Ataul17-Aug-10 6:03
Mukit, Ataul17-Aug-10 6:03 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.