Figure 2   CCaptionPainter

PaintCap.h

////////////////////////////////////////////////////////////////
// 1997 Microsoft Systems Journal. 
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
#ifndef _PAINTCAP_H
#define _PAINTCAP_H

#include "Subclass.h"

//////////////////
// This struct is passed to client as LPARAM of callback message
//
struct PAINTCAP {
   CDC*    m_pDC;            // DC to draw in
   CSize    m_szCaption;     // size of caption rectangle
   BOOL    m_bActive;        // activated state
};

//////////////////
// Generic caption painter. Handles WM_NCPAINT, WM_NCACTIVATE, etc.
// to let you draw custom captions. 
//
class CCaptionPainter : public CSubclassWnd {
protected:
   CFrameWnd*&    m_pFrame;          // same as CSubclassWnd::m_pWndHooked
   CBitmap        m_bmCaption[2];    // bitmaps for active/inactive captions 
   CSize          m_szCaption;       // size of caption rectangle
   BOOL           m_bActive;         // active/inactive state
   UINT           m_nPaintMsg;       // callback message to paint caption
   DECLARE_DYNAMIC(CCaptionPainter);

   // Helper
   void PaintCaption(const CRect& rc);

   // These are similar to, but NOT the same as the equivalent CWnd fns. Don't
   // override unless you're a guru, and even THEN I wouldn't recommend it.
   virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp);    
   virtual void OnNcPaint(HRGN hUpdateRgn);
   virtual BOOL OnNcActivate(BOOL bActive);
   virtual void OnSetText(LPCTSTR lpText);

public:
   CCaptionPainter();
   virtual ~CCaptionPainter();

   BOOL Install(CFrameWnd* pFrameWnd, UINT nPaintMsg=0);
   int  DrawIcon(const PAINTCAP& pc);
   int  DrawButtons(const PAINTCAP& pc);
   void DrawNormalCaption(const PAINTCAP& pc);
   void Invalidate() { m_szCaption=CSize(0,0); }
};

void WINAPI SetInternalWindowText(HWND hwnd, LPCTSTR lpText);

#endif

PaintCap.cpp

////////////////////////////////////////////////////////////////
// 1997 Microsoft Systems Journal. 
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
#include "StdAfx.h"
#include "PaintCap.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

////////////////
// Class used to get the caption rectangle of a window in window coords.
// This is the area of the title bar inside the window frame, including
// the icon and min/max/close buttons.
// 
class CCaptionRect : public CRect {
public:
   CCaptionRect(const CFrameWnd* pFrame);
};

IMPLEMENT_DYNAMIC(CCaptionPainter, CSubclassWnd);

CCaptionPainter::CCaptionPainter() : m_pFrame((CFrameWnd*&)m_pWndHooked)
{
   Invalidate();
}

CCaptionPainter::~CCaptionPainter()
{
}

//////////////////
// Install caption handler. nPaintMsg is message I will send to frame
// when its caption needs painting.
//
BOOL CCaptionPainter::Install(CFrameWnd* pFrameWnd, UINT nPaintMsg)
{
   ASSERT_KINDOF(CFrameWnd, pFrameWnd);
   m_nPaintMsg = nPaintMsg;
   return HookWindow(pFrameWnd);
}

//////////////////
// Message handler handles caption-related messages
//
LRESULT CCaptionPainter::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
   switch (msg) {
   case WM_NCPAINT:
       OnNcPaint((HRGN)wp);
       return 0;
   case WM_NCACTIVATE:
       return OnNcActivate(wp);
   case WM_SETTEXT:
       OnSetText((LPCTSTR)lp);
       return 0;
   case WM_SYSCOLORCHANGE:
   case WM_SETTINGCHANGE:
       Invalidate();
       m_pFrame->SendMessage(m_nPaintMsg, 1, 0L);
       return 0;
   }
   // I don't handle it: pass along
   return CSubclassWnd::WindowProc(msg, wp, lp);
}

/////////////////
// Handle WM_NCPAINT for main window
//
void CCaptionPainter::OnNcPaint(HRGN hRgn)
{
   ASSERT_VALID(m_pFrame);
   CCaptionRect rcCap(m_pFrame);       // caption rectangle in window coords
   CRect rcWin;                        // window rect
   m_pFrame->GetWindowRect(&rcWin);    // .. get window rect
   CRect rcCapScr =                    // convert caption rectangle...
       rcCap + rcWin.TopLeft();        // ..to screen coords

   // Don't bother painting if the caption doesn't lie within the region.
   if ((WORD)hRgn > 1 && !::RectInRegion(hRgn, &rcCapScr)) {
       Default();                      // just do default thing
       return;                         // and quit
   }

   // Exclude caption from update region
   //
   HRGN hRgnCaption = ::CreateRectRgnIndirect(&rcCapScr);
   HRGN hRgnNew     = ::CreateRectRgnIndirect(&rcCapScr);
   if ((WORD)hRgn > 1) {
       // wParam is a valid region: subtract caption from it
       ::CombineRgn(hRgnNew, hRgn, hRgnCaption, RGN_DIFF);
   } else {
       // wParam is not a valid region: create one that's the whole
       // window minus the caption bar
       HRGN hRgnAll = ::CreateRectRgnIndirect(&rcWin);
       CombineRgn(hRgnNew, hRgnAll, hRgnCaption, RGN_DIFF);
       DeleteObject(hRgnAll);
   }

   // Call Windows to do WM_NCPAINT with altered update region
   //
   MSG& msg = AfxGetThreadState()->m_lastSentMsg;
   WPARAM savewp = msg.wParam;    // save original wParam
   msg.wParam = (WPARAM)hRgnNew;  // set new region for DefWindowProc
   Default();                     // do normal WM_NCPAINT
   DeleteObject(hRgnCaption);     // clean up
   DeleteObject(hRgnNew);         // ...
   msg.wParam = savewp;           // restore original wParam

   PaintCaption(rcCap);           // Now paint my special caption
}

//////////////////
// Handle WM_NCACTIVATE for main window
//
BOOL CCaptionPainter::OnNcActivate(BOOL bActive)
{
   ASSERT_VALID(m_pFrame);

   // Mimic MFC kludge to stay active if WF_STAYACTIVE bit is on. Yuk!
   if (m_pFrame->m_nFlags & WF_STAYACTIVE)
       bActive = TRUE;
   if (!m_pFrame->IsWindowEnabled())
       bActive = FALSE;
   if (bActive==m_bActive)
       return TRUE;

   // If this is a MDI app, manually activate/paint active MDI child
   // window, because Windows won't do it if parent frame is invisible.
   // Must do this BEFORE calling Default, or it will not work.
   //
   CFrameWnd* pActiveFrame = m_pFrame->GetActiveFrame();
   if (pActiveFrame!=m_pFrame) {
       pActiveFrame->SendMessage(WM_NCACTIVATE,bActive);
       pActiveFrame->SendMessage(WM_NCPAINT);
   }

   // Turn WS_VISIBLE off before calling DefWindowProc,
   // so DefWindowProc won't paint and thereby cause flicker.
   // 
   DWORD dwStyle = m_pFrame->GetStyle();
   if (dwStyle & WS_VISIBLE)
       ::SetWindowLong(*m_pFrame, GWL_STYLE, (dwStyle & ~ WS_VISIBLE));

   MSG& msg = AfxGetThreadState()->m_lastSentMsg;
   msg.wParam = bActive;
   Default();
   if (dwStyle & WS_VISIBLE)
       ::SetWindowLong(*m_pFrame, GWL_STYLE, dwStyle);

   // At this point, nothing has happened (since WS_VISIBLE was off).
   // Now it's time to paint.
   //
   m_bActive = bActive;                  // update state
   m_pFrame->SendMessage(WM_NCPAINT);    // paint non-client area (frame too)
   return TRUE;                          // done OK
}

//////////////////
// Handle WM_SETTEXT for main window
//
void CCaptionPainter::OnSetText(LPCTSTR lpText)
{
   ASSERT_VALID(m_pFrame);

   // Turn WS_VISIBLE style off before calling Windows to
   // set the text, then turn it back on again after.
   //
   SetInternalWindowText(*m_pFrame, lpText);

   Invalidate();                            // force new bitmap
   PaintCaption(CCaptionRect(m_pFrame));    // paint caption
}

//////////////////
// "API" function to set internal window text without drawing
//
void WINAPI SetInternalWindowText(HWND hwnd, LPCTSTR lpText)
{
   // Turn off WS_VISIBLE if necessary to avoid painting
   DWORD dwStyle = ::GetWindowLong(hwnd, GWL_STYLE);
   if (dwStyle & WS_VISIBLE)
       ::SetWindowLong(hwnd, GWL_STYLE, dwStyle & ~WS_VISIBLE);
   
   // Call DefWindowProc directly to set internal window text.
   DefWindowProc(hwnd, WM_SETTEXT, 0, (LPARAM)lpText);

   // Restore parent visible state
   if (dwStyle & WS_VISIBLE)
       ::SetWindowLong(hwnd, GWL_STYLE, dwStyle);
}

//////////////////
// Paint custom caption. Flag tells whether active or not. Just blast the
// bitmap to the title bar, but not if minimized (iconic).
//
void CCaptionPainter::PaintCaption(const CRect& rc)
{
   ASSERT_VALID(m_pFrame);

   if (m_pFrame->IsIconic())
       return;

   // Get caption DC and rectangle
   CWindowDC dcWin(m_pFrame);                // window DC
   CDC dc;                                   // memory DC
   dc.CreateCompatibleDC(&dcWin);            // ...create it
   if (rc.Size() != m_szCaption) {           // if size changed:
       m_bmCaption[0].DeleteObject();        // invalidate bitmaps
       m_bmCaption[1].DeleteObject();        // ...
       m_szCaption = rc.Size();              // update new size
   }

   // Get active/inactive bitmap & determine if needs to be regenerated
   CBitmap& bm = m_bmCaption[m_bActive!=0];    // get bitmap
   BOOL bPaintIt = FALSE;                      // paint anew?
   if (!(HBITMAP)bm) {                         // no bitmap:
       bm.CreateCompatibleBitmap(&dcWin, rc.Width(), rc.Height()); // create one
       bPaintIt = TRUE;                        // and paint it
   }
   CBitmap* pOldBitmap = dc.SelectObject(&bm);   // select bitmap into memory DC

   // If bitmap needs painting, call client to do it.
   if (bPaintIt) {
       PAINTCAP pc;
       pc.m_pDC = &dc;
       pc.m_szCaption = rc.Size();
       pc.m_bActive  = m_bActive;
       if (m_nPaintMsg)
           m_pFrame->SendMessage(m_nPaintMsg, 0, (LPARAM)&pc);
       else
           // Not handled: draw normal caption
           DrawNormalCaption(pc);
   }

   // blast bits to screen
   dcWin.BitBlt(rc.left, rc.top, rc.Width(), rc.Height(), &dc, 0, 0, SRCCOPY);
   dc.SelectObject(pOldBitmap); // restore DC
}

/////////////////
// Draw normal caption. If you fail to handle the callback message,
// CCaptionPainter will call this.
//
void CCaptionPainter::DrawNormalCaption(const PAINTCAP& pc)
{
   UINT uFlags = DC_TEXT|DC_ICON;
   if (pc.m_bActive)
       uFlags |= DC_ACTIVE;
   CRect rc(CPoint(0,0), pc.m_szCaption);
   ::DrawCaption(*m_pFrame, *pc.m_pDC, &rc, uFlags);
   DrawButtons(pc);
}

////////////////
// Draw caption icon transparently. Returns width of icon.
//
int CCaptionPainter::DrawIcon(const PAINTCAP& pc)
{
   ASSERT_VALID(m_pFrame);

   // Within the basic button rectangle, Windows 95 uses a 1 or 2 pixel border
   // Icon has 2 pixel border on left, 1 pixel on top/bottom, 0 right
   //
   int cxIcon = GetSystemMetrics(SM_CXSIZE);
   CRect rc(0, 0, cxIcon, GetSystemMetrics(SM_CYSIZE));
   rc.DeflateRect(0,1);
   rc.left += 2;
   DrawIconEx(pc.m_pDC->m_hDC, rc.left, rc.top,
              (HICON)GetClassLong(*m_pFrame, GCL_HICONSM),
              rc.Width(), rc.Height(), 0, NULL, DI_NORMAL);
   return cxIcon;
}

////////////////
// Draw min, max/restore, close buttons transparently. Returns total
// width of buttons drawn. This is really yukky.
//
int CCaptionPainter::DrawButtons(const PAINTCAP& pc)
{
   ASSERT_VALID(m_pFrame);
   DWORD dwStyle = m_pFrame->GetStyle();
   if (!(dwStyle & WS_CAPTION))
       return 0;

   ASSERT(pc.m_pDC);
   CDC& dc = *pc.m_pDC;

   int cxIcon = GetSystemMetrics(SM_CXSIZE);
   int cyIcon = GetSystemMetrics(SM_CYSIZE);

   // Draw caption buttons. These are all drawn inside a rectangle
   // of dimensions SM_CXSIZE by SM_CYSIZE
   CRect rc(0, 0, cxIcon, cyIcon);
   rc += CPoint(pc.m_szCaption.cx-cxIcon, 0);    // move right

   // Close box has a 2 pixel border on all sides but left, which is zero
   rc.DeflateRect(0,2);
   rc.right -= 2;
   dc.DrawFrameControl(&rc, DFC_CAPTION, DFCS_CAPTIONCLOSE);

   // Max/restore button is like close box; just shift rectangle left
   // Also does help button, if any.
   BOOL bMaxBox = dwStyle & WS_MAXIMIZEBOX;
   if (bMaxBox || (m_pFrame->GetExStyle() & WS_EX_CONTEXTHELP)) {
       rc -= CPoint(cxIcon, 0);
       dc.DrawFrameControl(&rc, DFC_CAPTION,
                           bMaxBox ? (m_pFrame->IsZoomed() ? 
                           DFCS_CAPTIONRESTORE : DFCS_CAPTIONMAX) : 
                           DFCS_CAPTIONHELP);
   }

   // Minimize button has 2 pixel border on all sides but right.
   if (dwStyle & WS_MINIMIZEBOX) {
       rc -= CPoint(cxIcon-2,0);
       dc.DrawFrameControl(&rc, DFC_CAPTION, DFCS_CAPTIONMIN);
   }
   return pc.m_szCaption.cx - rc.left - 2;
}

//////////////////
// CCaptionRect computes caption rectangle in window coords.
//
CCaptionRect::CCaptionRect(const CFrameWnd* pFrame)
{
   // Get size of frame around window
   DWORD dwStyle = pFrame->GetStyle();
   CSize szFrame = (dwStyle & WS_THICKFRAME) ?
       CSize(GetSystemMetrics(SM_CXSIZEFRAME),
             GetSystemMetrics(SM_CYSIZEFRAME)) :
       CSize(GetSystemMetrics(SM_CXFIXEDFRAME),
             GetSystemMetrics(SM_CYFIXEDFRAME));

   int cxIcon = GetSystemMetrics(SM_CXSIZE); // width of caption icon/button

   // Compute rectangle
   pFrame->GetWindowRect(this);                  // window rect in screen coords
   *this -= CPoint(left, top);                   // shift origin to (0,0)
   left  += szFrame.cx;                           // frame
   right -= szFrame.cx;                           // frame
   top   += szFrame.cy;                           // top = end of frame
   bottom = top + GetSystemMetrics(SM_CYCAPTION)  // height of caption
       - GetSystemMetrics(SM_CYBORDER);           // minus gray shadow border
}

Figure 3   ShadeCap Main Frame Window Class

MainFrm.h

////////////////////////////////////////////////////////////////
// 1997 Microsoft Systems Journal. 
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.

#include "PaintCap.h"

#ifdef _MDI
#define CBaseFrameWnd CMDIFrameWnd
#else
#define CBaseFrameWnd CFrameWnd
#endif

/////////////////
// Mainframe that draws shaded caption a la Excel 95
//
class CMainFrame : public CBaseFrameWnd {
•
•
•
    CCaptionPainter m_capp;            // caption painter
    afx_msg int         OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg LRESULT     OnPaintMyCaption(WPARAM wp, LPARAM lp);
};

#ifdef _MDI

//////////////////
// MDI child window class to install caption painter
//
class CMyChildWnd : public CMDIChildWnd {
•
•
•
    CCaptionPainter m_capp;            // caption painter
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
};

#endif
MainFrm.cpp
////////////////////////////////////////////////////////////////
// 1997 Microsoft Systems Journal. 
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// 
#include "StdAfx.h"
#include "ShadeCap.h"
#include "MainFrm.h"
#include "PaintCap.h"

// Could also use RegisterWindowMessage
const UINT WM_PAINTMYCAPTION = WM_USER;

IMPLEMENT_DYNCREATE(CMainFrame, CBaseFrameWnd)
BEGIN_MESSAGE_MAP  (CMainFrame, CBaseFrameWnd)
    ON_WM_CREATE()
    ON_MESSAGE(WM_PAINTMYCAPTION, OnPaintMyCaption)
•
•
•
END_MESSAGE_MAP()

•
•
•
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    .
    . // Standard create stuff
    .
    // Install caption painter
    m_capp.Install(this, WM_PAINTMYCAPTION);
    return 0;
}

•
•
•

//////////////////
// Paint custom caption.
// This is the function that actually does the shading. 
//
LRESULT CMainFrame::OnPaintMyCaption(WPARAM bSettingChange, LPARAM lParam)
{
    if (bSettingChange==1) {
        // wParam = 1 means system setting change: invalidate fonts.
        m_fontCaption.DeleteObject();
        m_fontAcme.DeleteObject();
        return 0;
    }
    const PAINTCAP& pc = *((PAINTCAP*)lParam);
    ASSERT(pc.m_pDC);
    CDC& dc = *pc.m_pDC;

.
. // Lots of code to do shaded caption
.

    return 0;
}

#ifdef _MDI

IMPLEMENT_DYNCREATE(CMyChildWnd, CMDIChildWnd)
BEGIN_MESSAGE_MAP  (CMyChildWnd, CMDIChildWnd)
    ON_WM_CREATE()
END_MESSAGE_MAP()

•
•
•

int CMyChildWnd::OnCreate(LPCREATESTRUCT lpcs)
{
    if (CMDIChildWnd::OnCreate(lpcs) == -1)
        return -1;
    m_capp.Install(this);
    return 0;
}

#endif