BCG 是個很不錯的介面庫,MFC 傳統介面的不二選擇。他的絕大部分控制項都相當不錯,不過在一些細節地方,似乎 XtremeToolkit 還略勝一籌,比如顏色選擇按鈕、目錄選擇按鈕...
他的顏色按鈕,微軟吸收以後命名為 CMFCColorButton,保持以前下拉式清單的風格,個人不太喜歡,還是覺得 XtremeToolkit 的做的類似於按鈕的做的更合我意一點。但是 XtremeToolkit 是要掏錢的,既然俺有 CMFCColorButton,那就來擴充他吧,讓他來滿足我們的要求。
先看看:
這是正常時候的效果:
當點擊時
上面左邊就是標準的 CMFCColorButton,右邊是俺自己擴充的新類。
說說實現的方法,跟各位探討一下:
1、預設情況下,CMFCColorButton 重載了基類 CMFCButton 的 OnDraw 和 OnDrawBorder:
virtual void OnDraw(CDC* pDC, const CRect& rect, UINT uiState);</p><p>virtual void OnDrawBorder(CDC* pDC, CRect& rectClient, UINT uiState);<br />
他的這個彈出的顏色面板是 CMFCColorPopupMenu,每當觸發的時候動態建立。
主要的繪製代碼都是在 OnDraw 中實現的,OnDrawBorder 只是負責繪製焦點區,就是獲得焦點時候的那個虛線框。
關鍵的東西就是繪製那個箭頭和豎線條:
CRect rectArrow = rect;<br /> rectArrow.left = rectColor.right + nImageHorzMargin / 2;</p><p> //------------<br /> // Draw Arrow:<br /> //------------<br /> CMenuImages::Draw(pDC, m_bLargeArrow ? CMenuImages::IdArrowDownLarge : CMenuImages::IdArrowDown, rectArrow, (uiState & ODS_DISABLED) ? CMenuImages::ImageGray : CMenuImages::ImageBlack);</p><p> //----------------<br /> // Draw separator:<br /> //----------------<br /> CRect rectSeparator = rectArrow;<br /> rectSeparator.right = rectSeparator.left + 2;<br /> rectSeparator.DeflateRect(0, 2);</p><p> if (!m_bWinXPTheme || m_bDontUseWinXPTheme)<br /> {<br /> rectSeparator.left += m_sizePushOffset.cx;<br /> rectSeparator.top += m_sizePushOffset.cy;<br /> }</p><p> pDC->Draw3dRect(rectSeparator, afxGlobalData.clrBtnDkShadow, afxGlobalData.clrBtnHilite);</p><p>
2、這樣改了之後,沒有點擊的請情況下看起來就好了,但是,等等,當點擊按鈕的時候,並沒有出現預設按鈕按下下壓的那種效果,而 CButton 和 CMFCButton 都是有這種效果的,於是響應 OnLButtonDown 在調用基類 CMFCColorButton 響應之前先調用 CMFCButton 的點擊,這樣也就有了按鈕下壓的效果了。
3、下壓效果是有了,可是問題來了,當點擊按鈕的時候,那個顏色面板也順利的彈出來了,這時候按鈕一直保持按下狀態,當我們選擇在其他其他點擊滑鼠,不選擇任何顏色,僅僅關閉顏色面板,這個按下的狀態並沒有恢複成正常的彈起狀態。怎麼辦?前面提到這個 CMFCColorPopupMenu 是動態建立的,關閉的時候銷毀這個對象的指標。在彈出的時候,會調用 OnShowColorPopup 這個虛函數,,可惜的是他關閉的時候並不向我們的父視窗,也就是這裡的基類 CMFCColorButton 傳遞任何訊息,於是我們要做的,就是重載他
virtual void OnShowColorPopup();
重載了做什麼呢?hook 他!截獲他的 WM_CLOSE 訊息,向我們派生出來的按鈕發送一條 WM_LBUTTONUP 的訊息,就這樣,按鈕也彈起來了,問題解決了。
4、最後一個問題,用按鈕點擊也沒問題,按下、恢複的狀態也都恢複得挺好,可是快速鍵呢?基類 CMFCColorButton 只支援空格鍵和向下的方向鍵來開啟顏色選擇面板,我們可以增加一個向上的方向鍵來關閉顏色面板,如果此時面板有顯示的話。這裡的代碼倒是很簡單:
void CXColorButton::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)<br />{<br /> switch (nChar)<br /> {<br /> case VK_SPACE:<br /> case VK_DOWN:<br /> PostMessage(WM_LBUTTONDOWN);<br /> return;<br /> case VK_UP:<br /> if (m_pPopup != NULL && m_pPopup->GetSafeHwnd() != NULL)<br /> {<br /> m_pPopup->SendMessage(WM_CLOSE);<br /> m_pPopup = NULL;<br /> }<br /> return;<br /> default:<br /> break;<br /> }</p><p> CMFCButton::OnKeyDown(nChar, nRepCnt, nFlags);<br />}<br />
這裡的 m_pPopup 就是那個顏色選擇面板 CMFCColorPopupMenu 的指標。
至此,所有問題都已經解決,效果幾乎可以說是完美
後記:提到 hook 訊息,不得不感謝 PJ Naughter,他寫的 CHookWnd 讓 programer 從繁瑣的 GetWindowLong 和 SetWindowLong 中解放了出來,只需要子類化 HookWnd 即可。當然,這個東西也不是說拿來就很好套用的,我自己把它擴充了一下,核心思想是通過一個 CObject 的指標來訪問不同的類。如果聲明為友元類,比如我們這個例子,把 CXXXHooker 聲明為我們子類化的 CXColorButton 的 friend class,那就更是得心應手隨心所欲了,嘿嘿