我用过这个类,尽管对它并不是很清楚。在分割窗口或者TAB窗口的pane部分,添加这个类的对象,会使得窗口出现一个标题栏。标题栏可以是横向的,也可以是纵向的,上面还可以有一个关闭按钮。当然这个pane容器窗口并不是必须的。好了,至少,我们大概知道了这个类能够做什么了。
分析源代码就我而言,是我一个主要的学习方法。不知道大家是否也是如此,知道别人怎么使用一个函数,怎么达成一个目标。尤其出自那些大师们之手,你基本可以断定哪种做法至少不会是一种糟糕的方法,如果不是最佳的话。
我先粗粗浏览了一遍这个类,我知道可以学习几个地方。
1. 如果建立一个通用窗口类,如何使用CCustomDraw 模板类。
2. 如何在窗口类里面动态添加工具条以及如何自绘按钮,处理工具条上的按钮消息。
3. 如果维护一个临时的客户窗口。
windows消息包含窗口消息、命令消息和控件通知消息,控件通知消息,是指这样一种消息,一个窗口内的子控件发生了一些事情,需要通知父窗口。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框,以及Windows公共控件如树状视图、列表视图等。例如,单击或双击一个控件、在控件中选择部分文本、操作控件的滚动条都会产生通知消息。 她类似于命令消息,当用户与控件窗口交互时,那么控件通知消息就会从控件窗口发送到它的主窗口。但是这种消息的存在并不是为了处理用户命令,而是为了让主窗口能够改变控件,例如加载、显示数据。
控件通知消息种类:
第一控件通知格式
第一控件通知格式只是窗口消息的子集。它的特征格式如下:WM_XXXX。它主要来自下面的3种消息类型:
(1)表示一个控件窗口要么已经被创建或销毁,要么已经被鼠标单击的消息:WM_PARENTNOTIFY;
(2)发送到父窗口,用来绘制自身窗口的消息,例如: WM_CTLCOLOR、WM_DRAWITEM、WM_MEASUREITEM、WM_DELETEITEM、WM_CHARTOITEM、WM_VKTOITEM、WM_COMMAND和WM_COMPAREITEM
(3)有滚动调控件发送,通知父窗口滚动窗口的消息:WM_VSCROLL和WM_HSCROLL
第二控件通知类与命令消息共享,它的特征格式如下:WM_COMMAND。
在WM_COMMAND中,lParam用来区分是命令消息还是控件通知消息:如果lParam为NULL,则这是个命令消息,否则lParam里面放的必然就是控件的句柄,是一个控件通知消息。对于wParam则是低位放的是控件ID,高位放的是相应的消息事件。
第三控件通知格式
这个才是本文要涉及的内容,同时他也是最为灵活的一种格式。它的特征格式如下:WM_NOTIFY。
在WM_NOTIFY中,lParam中放的是一个称为NMHDR结构的指针。
有了前面这些铺垫,我们来看看 CPaneContainerImpl 类的定义:
template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
class ATL_NO_VTABLE CPaneContainerImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >, public CCustomDraw< T >
{
public:
DECLARE_WND_CLASS_EX(NULL, 0, -1)
.......
这个类的基类就是一个普通窗口,第二个基类则是 CCustomDraw。我们前面见识过 COwnerDraw,弄这两者功能接近,弄清楚两者的差别也是我们关心的。
template <class T>
class CCustomDraw
{
public:
// Message map and handlers
BEGIN_MSG_MAP(CCustomDraw< T >)
NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)
ALT_MSG_MAP(1)
REFLECTED_NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)
END_MSG_MAP()
// message handler
LRESULT OnCustomDraw(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
{
T* pT = static_cast<T*>(this);
pT->SetMsgHandled(TRUE);
LPNMCUSTOMDRAW lpNMCustomDraw = (LPNMCUSTOMDRAW)pnmh;
DWORD dwRet = 0;
switch(lpNMCustomDraw->dwDrawStage)
{
case CDDS_PREPAINT:
dwRet = pT->OnPrePaint(idCtrl, lpNMCustomDraw);
break;
case CDDS_POSTPAINT:
dwRet = pT->OnPostPaint(idCtrl, lpNMCustomDraw);
break;
。。。。。。。
default:
pT->SetMsgHandled(FALSE);
break;
}
bHandled = pT->IsMsgHandled();
return dwRet;
}
// Overrideables
DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/)
{
return CDRF_DODEFAULT;
}
上面是 CCustomDraw 这个类的主要代码, 根据前面的经验,我想,或者会使用,类似
CHAIN_MSG_MAP_ALT( COwnerDraw<CPicLooker>,1 )
这样的代码,但是,我在消息映射代码段没有看到这样的代码,看来看去,显然它是通过那个通知消息触发了自绘能力。我想上面的方式应该也是可以实现的,有兴趣的朋友可以试一下。
// Message map and handlers
BEGIN_MSG_MAP(CPaneContainerImpl)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
MESSAGE_HANDLER(WM_GETFONT, OnGetFont)
MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
#ifndef _WIN32_WCE
MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
#endif // !_WIN32_WCE
MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
MESSAGE_HANDLER(WM_COMMAND, OnCommand)
FORWARD_NOTIFICATIONS()
END_MSG_MAP()
LRESULT OnNotify(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
if(m_tb.m_hWnd == NULL)
{
bHandled = FALSE;
return 1;
}
T* pT = static_cast<T*>(this);
pT;
LPNMHDR lpnmh = (LPNMHDR)lParam;
LRESULT lRet = 0;
// pass toolbar custom draw notifications to the base class
if(lpnmh->code == NM_CUSTOMDRAW && lpnmh->hwndFrom == m_tb.m_hWnd)
lRet = CCustomDraw< T >::OnCustomDraw(0, lpnmh, bHandled);
#ifndef _WIN32_WCE
// tooltip notifications come with the tooltip window handle and button ID,
// pass them to the parent if we don't handle them
else if(lpnmh->code == TTN_GETDISPINFO && lpnmh->idFrom == pT->m_nCloseBtnID)
bHandled = pT->GetToolTipText(lpnmh);
#endif // !_WIN32_WCE
// only let notifications not from the toolbar go to the parent
else if(lpnmh->hwndFrom != m_tb.m_hWnd && lpnmh->idFrom != pT->m_nCloseBtnID)
bHandled = FALSE;
return lRet;
}
这段代码值得仔细看, 自绘的目标并不是针对 CPaneContainerImpl 的,而是针对他的控件-工具条的。如果没有关闭按钮,就是没有工具条的话,这个函数直接返回。如果是工具条的 NM_CUSTOMDRAW 通知,直接调用
lRet = CCustomDraw< T >::OnCustomDraw(0, lpnmh, bHandled);
如果是对于关闭按钮的 TTN_GETDISPINFO 通知,就将提示文本返回给它。最后一个if语句的意思是, 如果同时不是工具条,同时这个通知与关闭按钮无关的话,就不处理这个通知。也就是说,这个通知会继续向上返回,交由上层窗口处理。
CPaneContainerImpl 窗口自身的绘制是由OnPaint完成的。CCustomDraw 并不是针对它的,而是针对窗口内部的控件,这大概就是与 COwnerDraw的不同之处。
至于工具条上的按钮,我们知道,按下工具条上的按钮会触发 COMMAND消息。这个消息会发给上层窗口,那么CPaneContainerImpl就是工具条的父窗口,MESSAGE_HANDLER(WM_COMMAND, OnCommand) 就是处理关闭按钮事件的。
LRESULT OnCommand(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
// if command comes from the close button, substitute HWND of the pane container instead
if(m_tb.m_hWnd != NULL && (HWND)lParam == m_tb.m_hWnd)
return ::SendMessage(GetParent(), WM_COMMAND, wParam, (LPARAM)m_hWnd);
bHandled = FALSE;
return 1;
}
这里对WM_COMMAND消息只是简单发送给父窗口。毕竟我们控件里面不可能写事件处理代码,我们都不知道用户设计的这个事件用来做什么呢。
至于动态建立的工具条,我并没有找到并关于自绘的风格。我查找了微软关于自绘的文档,大致明白了它的机制。也明白了为什么不直接使用Button,而使用Toolbar的原因。原来Button并不支持自绘。下面是哪个页面的连接。
http://msdn.microsoft.com/en-us/library/windows/desktop/ff919569(v=vs.85).aspx#CustomDraw_Prepaint
至此前面说提出的任务,三件我们完成了两件。最后一件很简单,技术上没有什么难度。自己看看代码吧。
HWND SetClient(HWND hWndClient)
{
HWND hWndOldClient = m_wndClient;
m_wndClient = hWndClient;
if(m_hWnd != NULL)
{
T* pT = static_cast<T*>(this);
pT->UpdateLayout();
}
return hWndOldClient;
}
另外需要说明:本文部分内容是从网络拷贝过来的。由于写文章的时候关闭了哪个网页,找不到了。没有办法提供作者的名字,这里想原作者表示歉意和感谢。