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