源代碼分析之如何實現自定義的標題欄

源代碼分析之如何實現自定義的標題欄

本文主要分析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));

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章