本文主要分析Visual Studio Samples\1033\C++\MFC\Visual C++ 2008 Feature Pack\MSMoneyDemo這個Sample
一般窗口的標(biāo)題欄上面都是只有固定的最小化,恢復(fù),最大化按鈕,這些按鈕的大小,圖標(biāo)都是系統(tǒng)自定義的,
本文分析VS2008 sp1 的事例代碼實現(xiàn)自己的標(biāo)題欄。
CMSMCaptionBar實現(xiàn)了,自定義的標(biāo)題欄窗口,類定義如下。
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;
};

下面是類的派生關(guān)系圖:
CObject
   CCmdTarget
      CWnd
         CBasePane
            CPane
       CMSMCaptionBar
從這個圖上可以看出:CMSMCaptionBar也是一個窗口,(從CWnd類派生的都是一個窗口WIndow)。

2 如何設(shè)計設(shè)計一個Caption Window
一個窗口主要由UI部分以及消息響應(yīng)部分組成。
UI部分通俗的說就是窗口的外觀描述,通過外觀描述可以繪制出窗口。
消息響應(yīng)部分就是該窗口會處理哪些消息。比如雙擊時最大化還是恢復(fù),點擊上面的關(guān)閉按鈕,程序關(guān)閉等等,下面會詳細描述。
2.1 Caption Window的組成
1)元素組成:標(biāo)題圖標(biāo),窗口標(biāo)題,最小化按鈕,恢復(fù)按鈕,最大化按鈕。
描述這些元素需要相應(yīng)的成員變量,也就是CMSMCaptionBar的public成員:
 CString      m_strCaption;//窗口標(biāo)題
 CFont      m_CaptionFont;//標(biāo)題字體
 
 HICON      m_hIcon;//標(biāo)題圖標(biāo)
 CSize      m_szIcon;//圖標(biāo)大小

 

 //最小化按鈕,恢復(fù)按鈕,最大化按鈕。
 CMSMCaptionBarButton m_BtnMinimize;
 CMSMCaptionBarButton m_BtnMaximize;
 CMSMCaptionBarButton m_BtnClose;

對于Caption Window,還有自己的一些屬性,比如窗口高度。

 int       m_SystemHeight;
 int       m_CaptionHeight;//標(biāo)題欄高度
 
2)如何繪制元素
為了保證窗口接收雙擊事件,需要組成S_DBLCLKS風(fēng)格的窗口。
LPCTSTR lpszClass = AfxRegisterWndClass(CS_DBLCLKS, ::LoadCursor(NULL, IDC_ARROW),
  (HBRUSH)(COLOR_BTNFACE+1), NULL);

Create->CreateEx
創(chuàng)建窗口
CWnd::Create(lpszClass, NULL, dwStyle | WS_CLIPSIBLINGS, rect, pParentWnd, nID)
創(chuàng)建好窗口之后還必須加入到窗口的一個Pane的列表中去
if (pParentWnd->IsKindOf (RUNTIME_CLASS (CFrameWndEx)))
 {
  ((CFrameWndEx*) pParentWnd)->AddPane (this);
 }
 
 
加載Capation Icon,設(shè)置Caption 標(biāo)題
SetIcon (hIcon);
SetWindowText (strCaption);
創(chuàng)建最小化按鈕,恢復(fù)按鈕,最大化按鈕
 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"));

對于按鈕上圖片的設(shè)置與加載是在CMSMVisualManager中設(shè)置的,這是在VS2008 Sp1中有的可以設(shè)置多種UI主題風(fēng)格,類似于換皮膚。
下面列出了與Caption Window相關(guān)的函數(shù)。
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;
}
通過這些函數(shù)可以設(shè)置Button的圖片以及Caption Windowd的背景。


通過上面就把窗口UI元素畫好了,下面介紹消息的處理

3)Caption Windows處理的消息又哪些?
 鼠標(biāo)左鍵單擊(需要判斷是最小化,最大化,恢復(fù),還是關(guān)閉);
 鼠標(biāo)右鍵單擊(彈出幫助菜單)
 鼠標(biāo)左鍵雙擊(最大化或者恢復(fù))
 WM_SIZE消息,當(dāng)窗口大小變化時,需要移動最小化,最大化,恢復(fù)按鈕的位置。
 
 也就是下面的一些消息。
 ON_MESSAGE(WM_SETTEXT, OnSetText)
 ON_WM_SIZE()
 ON_WM_CONTEXTMENU()
 ON_WM_SYSCOMMAND()
 
 
4)對于按鈕消息的處理WM_XXX應(yīng)當(dāng)轉(zhuǎn)化成WM_NCXXX,也就是說客戶端消息要轉(zhuǎn)化成非客戶端消息。因為對于單文檔程序來說,
標(biāo)題欄屬于非客戶區(qū)。

BOOL CMSMCaptionBar::PreTranslateMessage (MSG* pMsg)
在其中調(diào)用HitTest函數(shù)判斷鼠標(biāo)的位置,如果是在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不感興趣的消息會發(fā)送給父窗口處理。
 
5)WM_SIZE消息的處理

對WM_SIZE的消息處理就是移動最下化,最大化,以及恢復(fù)按鈕。


3)使用Caption Window
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
設(shè)置風(fēng)格,去掉WS_CAPTION | FWS_ADDTOTITLE,即創(chuàng)建不帶Caption的Mainframe,
因為Caption是我們自己要創(chuàng)建的,而不是自動創(chuàng)建

 ModifyStyle (WS_CAPTION | FWS_ADDTOTITLE, 0);
去掉窗口的邊框
 ModifyStyleEx (WS_EX_CLIENTEDGE, 0);
設(shè)置窗口風(fēng)格
 CMFCVisualManager::SetDefaultManager (RUNTIME_CLASS (CMSMVisualManager));