本文主要分析Visual Studio Samples\1033\C++\MFC\Visual C++ 2008 Feature Pack\MSMoneyDemo这个Sample
一般窗口的标题栏上面都是只有固定的最小化,恢复,最大化按钮,这些按钮的大小,图标都是系统自定义的,
本文分析VS2008 sp1 的事例代码实现自己的标题栏。
CMSMCaptionBar实现了,自定义的标题栏窗口,类定义如下。
class CMSMCaptionBar : public CPane
{
DECLARE_DYNCREATE(CMSMCaptionBar)

// Construction
public:
CMSMCaptionBar ();

virtual ~CMSMCaptionBar ();

virtual void SetIcon (HICON hIcon);

void SetCaptionHeight (int nHeight);

int GetCaptionHeight () const;

void SetCaptionFont (const LOGFONT& lf);

HFONT GetCaptionFont () const;

virtual COLORREF GetCaptionTextColor () const;

void SetParentActive (BOOL bParentActive = true);

BOOL IsParentActive () const;

void SetParentMaximize (BOOL bParentMaximize = true);

BOOL IsParentMaximize () const;

// Attributes
public:

// Operations
public:

// Overrides
public:
virtual BOOL Create(CWnd* pParentWnd, UINT nID = uiCaprionBarID);
virtual BOOL CreateEx(CWnd* pParentWnd, UINT nID = uiCaprionBarID);
virtual CSize CalcFixedLayout(BOOL, BOOL);
protected:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
virtual void DoPaint(CDC* pDCPaint);
virtual BOOL PreTranslateMessage(MSG* pMsg);
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);

protected:
afx_msg LRESULT OnSetText(WPARAM, LPARAM lParam);
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnContextMenu(CWnd* pWnd, CPoint point);
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);

DECLARE_MESSAGE_MAP()

virtual UINT HitTest (const CPoint& pt) const;
virtual void ShowSysMenu (const CPoint& point);

public:
CString      m_strCaption;

HICON      m_hIcon;
CSize      m_szIcon;

BOOL      m_bParentActive;
BOOL      m_bParentMaximize;

int       m_SystemHeight;
int       m_CaptionHeight;
CFont      m_CaptionFont;

CMSMCaptionBarButton m_BtnMinimize;
CMSMCaptionBarButton m_BtnMaximize;
CMSMCaptionBarButton m_BtnClose;
};

下面是类的派生关系图:
CObject
   CCmdTarget
      CWnd
         CBasePane
            CPane
       CMSMCaptionBar
从这个图上可以看出:CMSMCaptionBar也是一个窗口,(从CWnd类派生的都是一个窗口WIndow)。

2 如何设计设计一个Caption Window
一个窗口主要由UI部分以及消息响应部分组成。
UI部分通俗的说就是窗口的外观描述,通过外观描述可以绘制出窗口。
消息响应部分就是该窗口会处理哪些消息。比如双击时最大化还是恢复,点击上面的关闭按钮,程序关闭等等,下面会详细描述。
2.1 Caption Window的组成
1)元素组成:标题图标,窗口标题,最小化按钮,恢复按钮,最大化按钮。
描述这些元素需要相应的成员变量,也就是CMSMCaptionBar的public成员:
CString      m_strCaption;//窗口标题
CFont      m_CaptionFont;//标题字体

HICON      m_hIcon;//标题图标
CSize      m_szIcon;//图标大小

//最小化按钮,恢复按钮,最大化按钮。
CMSMCaptionBarButton m_BtnMinimize;
CMSMCaptionBarButton m_BtnMaximize;
CMSMCaptionBarButton m_BtnClose;

对于Caption Window,还有自己的一些属性,比如窗口高度。

int       m_SystemHeight;
int       m_CaptionHeight;//标题栏高度

2)如何绘制元素
为了保证窗口接收双击事件,需要组成S_DBLCLKS风格的窗口。
LPCTSTR lpszClass = AfxRegisterWndClass(CS_DBLCLKS, ::LoadCursor(NULL, IDC_ARROW),
  (HBRUSH)(COLOR_BTNFACE+1), NULL);

Create->CreateEx
创建窗口
CWnd::Create(lpszClass, NULL, dwStyle | WS_CLIPSIBLINGS, rect, pParentWnd, nID)
创建好窗口之后还必须加入到窗口的一个Pane的列表中去
if (pParentWnd->IsKindOf (RUNTIME_CLASS (CFrameWndEx)))
{
  ((CFrameWndEx*) pParentWnd)->AddPane (this);
}


加载Capation Icon,设置Caption 标题
SetIcon (hIcon);
SetWindowText (strCaption);
创建最小化按钮,恢复按钮,最大化按钮
m_BtnClose.Create (_T(""), BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE,
                 rt, this, SC_CLOSE);
m_BtnClose.SetTooltip (_T("Close"));

m_BtnMaximize.Create (_T(""), BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE,
                 rt, this, SC_MAXIMIZE);
m_BtnMaximize.SetTooltip (_T("Maximize"));

m_BtnMinimize.Create (_T(""), BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE,
                 rt, this, SC_MINIMIZE);
m_BtnMinimize.SetTooltip (_T("Minimize"));

对于按钮上图片的设置与加载是在CMSMVisualManager中设置的,这是在VS2008 Sp1中有的可以设置多种UI主题风格,类似于换皮肤。
下面列出了与Caption Window相关的函数。
class CMSMVisualManager : public CMFCVisualManagerOffice2003
{
BOOL CMSMVisualManager::LoadMSMCaptionButtonsIcons (LPCTSTR lpszID);
virtual void MSMDrawCaptionButton (CDC* pDC, CRect rect, AFX_BUTTON_STATE state, UINT id);
BOOL LoadMSMCaptionButtonsIcons (LPCTSTR lpszID);

const CSize& GetMSMCaptionButtonsSize () const;

virtual void OnFillBarBackground (CDC* pDC, CBasePane* pBar,
CRect rectClient, CRect rectClip, BOOL bNCArea = FALSE);
     
CImageList m_CaptionButtonIconst;
CSize    m_CaptionButtonSize;
}
通过这些函数可以设置Button的图片以及Caption Windowd的背景。


通过上面就把窗口UI元素画好了,下面介绍消息的处理

3)Caption Windows处理的消息又哪些?
鼠标左键单击(需要判断是最小化,最大化,恢复,还是关闭);
鼠标右键单击(弹出帮助菜单)
鼠标左键双击(最大化或者恢复)
WM_SIZE消息,当窗口大小变化时,需要移动最小化,最大化,恢复按钮的位置。

也就是下面的一些消息。
ON_MESSAGE(WM_SETTEXT, OnSetText)
ON_WM_SIZE()
ON_WM_CONTEXTMENU()
ON_WM_SYSCOMMAND()


4)对于按钮消息的处理WM_XXX应当转化成WM_NCXXX,也就是说客户端消息要转化成非客户端消息。因为对于单文档程序来说,
标题栏属于非客户区。

BOOL CMSMCaptionBar::PreTranslateMessage (MSG* pMsg)
在其中调用HitTest函数判断鼠标的位置,如果是在Caption window中点击,则
判断uiHit = HTCAPTION;还是uiHit = HTSYSMENU;

   switch (pMsg->message)
   {
   case WM_LBUTTONDOWN:
    message = WM_NCLBUTTONDOWN;
    break;
   case WM_LBUTTONUP:
    message = WM_NCLBUTTONUP;
    break;
   case WM_LBUTTONDBLCLK:
    message = WM_NCLBUTTONDBLCLK;
    break;
   }

   if (message != 0)
   {
    if (message == WM_NCLBUTTONDOWN && uiHit == HTSYSMENU)
    {
     CRect rt;
     GetWindowRect (rt);

     ShowSysMenu (CPoint (rt.left, rt.bottom));
    }
    else
    {
     pParentWnd->SendMessage (message, wParam, lParam);
    }
   }

对于Caption Window不感兴趣的消息会发送给父窗口处理。

5)WM_SIZE消息的处理

对WM_SIZE的消息处理就是移动最下化,最大化,以及恢复按钮。


3)使用Caption Window
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
设置风格,去掉WS_CAPTION | FWS_ADDTOTITLE,即创建不带Caption的Mainframe,
因为Caption是我们自己要创建的,而不是自动创建

ModifyStyle (WS_CAPTION | FWS_ADDTOTITLE, 0);
去掉窗口的边框
ModifyStyleEx (WS_EX_CLIENTEDGE, 0);
设置窗口风格
CMFCVisualManager::SetDefaultManager (RUNTIME_CLASS (CMSMVisualManager));