以下内容全部为转载:
BCG 是个很不错的界面库,MFC 传统界面的不二选择。他的绝大部分控件都相当不错,不过在一些细节地方,似乎 XtremeToolkit 还略胜一筹,比如颜色选择按钮、目录选择按钮...
他的颜色按钮,微软吸收以后命名为 CMFCColorButton,保持以前下拉列表的风格,个人不太喜欢,还是觉得 XtremeToolkit 的做的类似于按钮的做的更合我意一点。但是 XtremeToolkit 是要掏钱的,既然俺有 CMFCColorButton,那就来扩展他吧,让他来满足我们的要求。
先看看效果图:
这是正常时候的效果:
当点击时
上面左边就是标准的 CMFCColorButton,右边是俺自己扩展的新类。
说说实现的方法,跟各位探讨一下:
1、默认情况下,CMFCColorButton 重载了基类 CMFCButton 的 OnDraw 和 OnDrawBorder:
- virtual void OnDraw(CDC* pDC, const CRect& rect, UINT uiState);
- virtual void OnDrawBorder(CDC* pDC, CRect& rectClient, UINT uiState);
他的这个弹出的颜色面板是 CMFCColorPopupMenu,每当触发的时候动态建立。
主要的绘制代码都是在 OnDraw 中实现的,OnDrawBorder 只是负责绘制焦点区,就是获得焦点时候的那个虚线框。
关键的东西就是绘制那个箭头和竖线条:
- CRect rectArrow = rect;
- rectArrow.left = rectColor.right + nImageHorzMargin / 2;
- //------------
- // Draw Arrow:
- //------------
- CMenuImages::Draw(pDC, m_bLargeArrow ? CMenuImages::IdArrowDownLarge : CMenuImages::IdArrowDown, rectArrow, (uiState & ODS_DISABLED) ? CMenuImages::ImageGray : CMenuImages::ImageBlack);
- //----------------
- // Draw separator:
- //----------------
- CRect rectSeparator = rectArrow;
- rectSeparator.right = rectSeparator.left + 2;
- rectSeparator.DeflateRect(0, 2);
- if (!m_bWinXPTheme || m_bDontUseWinXPTheme)
- {
- rectSeparator.left += m_sizePushOffset.cx;
- rectSeparator.top += m_sizePushOffset.cy;
- }
- pDC->Draw3dRect(rectSeparator, afxGlobalData.clrBtnDkShadow, afxGlobalData.clrBtnHilite);
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)
- {
- switch (nChar)
- {
- case VK_SPACE:
- case VK_DOWN:
- PostMessage(WM_LBUTTONDOWN);
- return;
- case VK_UP:
- if (m_pPopup != NULL && m_pPopup->GetSafeHwnd() != NULL)
- {
- m_pPopup->SendMessage(WM_CLOSE);
- m_pPopup = NULL;
- }
- return;
- default:
- break;
- }
- CMFCButton::OnKeyDown(nChar, nRepCnt, nFlags);
- }
这里的 m_pPopup 就是那个颜色选择面板 CMFCColorPopupMenu 的指针。
至此,所有问题都已经解决,效果几乎可以说是完美 :)
后记:提到 hook 消息,不得不感谢 PJ Naughter,他写的 CHookWnd 让 programer 从繁琐的 GetWindowLong 和 SetWindowLong 中解放了出来,只需要子类化 HookWnd 即可。当然,这个东西也不是说拿来就很好套用的,我自己把它扩展了一下,核心思想是通过一个 CObject 的指针来访问不同的类。如果声明为友元类,比如我们这个例子,把 CXXXHooker 声明为我们子类化的 CXColorButton 的 friend class,那就更是得心应手随心所欲了,嘿嘿