用MFC的都知道我们经常使用的对话框基本上都是继承于CDialogEx这个类的,看着MFC包装的如此神秘,今天是这把那些不认识的给他翻译出来然后看看这个类的真面目。
首先我先附上CDialogEx的源代码一份。
如下是CDialogEx.h的内容:
#include "afxcontrolbarutil.h"
#include "afxdialogimpl.h"
#ifdef _AFX_PACKING
#pragma pack(push, _AFX_PACKING)
#endif
#ifdef _AFX_MINREBUILD
#pragma component(minrebuild, off)
#endif
/*============================================================================*/
// CDialogEx dialog
class CDialogEx : public CDialog
{
friend class CMFCPopupMenu;
friend class CMFCDropDownListBox;
friend class CContextMenuManager;
DECLARE_DYNAMIC(CDialogEx)
// Construction
public:
CDialogEx();
CDialogEx(UINT nIDTemplate, CWnd *pParent = NULL);
CDialogEx(LPCTSTR lpszTemplateName, CWnd *pParentWnd = NULL);
protected:
void CommonConstruct();
// Attributes:
public:
enum BackgroundLocation
{
BACKGR_TILE,
BACKGR_TOPLEFT,
BACKGR_TOPRIGHT,
BACKGR_BOTTOMLEFT,
BACKGR_BOTTOMRIGHT,
};
protected:
HBITMAP m_hBkgrBitmap;
CSize m_sizeBkgrBitmap;
CBrush m_brBkgr;
BackgroundLocation m_BkgrLocation;
CDialogImpl m_Impl;
BOOL m_bAutoDestroyBmp;
// Operations:
public:
void SetBackgroundColor(COLORREF color, BOOL bRepaint = TRUE);
void SetBackgroundImage(HBITMAP hBitmap, BackgroundLocation location = BACKGR_TILE, BOOL bAutoDestroy = TRUE, BOOL bRepaint = TRUE);
BOOL SetBackgroundImage(UINT uiBmpResId, BackgroundLocation location = BACKGR_TILE, BOOL bRepaint = TRUE);
// Overrides
public:
virtual BOOL PreTranslateMessage(MSG* pMsg);
protected:
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
// Implementation
protected:
afx_msg void OnActivate(UINT nState, CWnd *pWndOther, BOOL bMinimized);
afx_msg BOOL OnNcActivate(BOOL bActive);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnDestroy();
afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
afx_msg void OnSysColorChange();
afx_msg void OnSettingChange(UINT uFlags, LPCTSTR lpszSection);
DECLARE_MESSAGE_MAP()
void SetActiveMenu(CMFCPopupMenu* pMenu);
};
#ifdef _AFX_MINREBUILD
#pragma component(minrebuild, on)
#endif
#ifdef _AFX_PACKING
#pragma pack(pop)
#endif
如下是CDialogEx.cpp的内容:
#include "stdafx.h"
#include "afxdialogex.h"
#include "afxpopupmenu.h"
#include "afxtoolbarmenubutton.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
IMPLEMENT_DYNAMIC(CDialogEx, CDialog)
/
// CDialogEx dialog
#pragma warning(disable : 4355)
CDialogEx::CDialogEx() : m_Impl(*this)
{
CommonConstruct();
}
CDialogEx::CDialogEx(UINT nIDTemplate, CWnd *pParent/*= NULL*/) : CDialog(nIDTemplate, pParent), m_Impl(*this)
{
CommonConstruct();
}
CDialogEx::CDialogEx(LPCTSTR lpszTemplateName, CWnd *pParentWnd/*= NULL*/) : CDialog(lpszTemplateName, pParentWnd), m_Impl(*this)
{
CommonConstruct();
}
#pragma warning(default : 4355)
void CDialogEx::CommonConstruct()
{
m_hBkgrBitmap = NULL;
m_sizeBkgrBitmap = CSize(0, 0);
m_BkgrLocation = (BackgroundLocation) -1;
m_bAutoDestroyBmp = FALSE;
}
void CDialogEx::SetBackgroundColor(COLORREF color, BOOL bRepaint)
{
if (m_brBkgr.GetSafeHandle() != NULL)
{
m_brBkgr.DeleteObject();
}
if (color != (COLORREF)-1)
{
m_brBkgr.CreateSolidBrush(color);
}
if (bRepaint && GetSafeHwnd() != NULL)
{
Invalidate();
UpdateWindow();
}
}
void CDialogEx::SetBackgroundImage(HBITMAP hBitmap, BackgroundLocation location, BOOL bAutoDestroy, BOOL bRepaint)
{
if (m_bAutoDestroyBmp && m_hBkgrBitmap != NULL)
{
::DeleteObject(m_hBkgrBitmap);
}
m_hBkgrBitmap = hBitmap;
m_BkgrLocation = location;
m_bAutoDestroyBmp = bAutoDestroy;
if (hBitmap != NULL)
{
BITMAP bmp;
::GetObject(hBitmap, sizeof(BITMAP), (LPVOID) &bmp);
m_sizeBkgrBitmap = CSize(bmp.bmWidth, bmp.bmHeight);
}
else
{
m_sizeBkgrBitmap = CSize(0, 0);
}
if (bRepaint && GetSafeHwnd() != NULL)
{
Invalidate();
UpdateWindow();
}
}
BOOL CDialogEx::SetBackgroundImage(UINT uiBmpResId, BackgroundLocation location, BOOL bRepaint)
{
HBITMAP hBitmap = NULL;
if (uiBmpResId != 0)
{
hBitmap = ::LoadBitmapW(AfxFindResourceHandle(MAKEINTRESOURCE(uiBmpResId), RT_BITMAP),
MAKEINTRESOURCEW(uiBmpResId));
if (hBitmap == NULL)
{
ASSERT(FALSE);
return FALSE;
}
}
SetBackgroundImage(hBitmap, location, TRUE /* Autodestroy */, bRepaint);
return TRUE;
}
BEGIN_MESSAGE_MAP(CDialogEx, CDialog)
ON_WM_ACTIVATE()
ON_WM_NCACTIVATE()
ON_WM_ERASEBKGND()
ON_WM_DESTROY()
ON_WM_CTLCOLOR()
ON_WM_SYSCOLORCHANGE()
ON_WM_SETTINGCHANGE()
END_MESSAGE_MAP()
/
// CDialogEx message handlers
void CDialogEx::OnActivate(UINT nState, CWnd *pWndOther, BOOL /*bMinimized*/)
{
Default();
m_Impl.OnActivate(nState, pWndOther);
}
BOOL CDialogEx::OnNcActivate(BOOL bActive)
{
m_Impl.OnNcActivate(bActive);
// Do not call the base class because it will call Default()
// and we may have changed bActive.
return(BOOL) DefWindowProc(WM_NCACTIVATE, bActive, 0L);
}
BOOL CDialogEx::OnEraseBkgnd(CDC* pDC)
{
if (m_brBkgr.GetSafeHandle() == NULL && m_hBkgrBitmap == NULL)
{
return CDialog::OnEraseBkgnd(pDC);
}
ASSERT_VALID(pDC);
CRect rectClient;
GetClientRect(rectClient);
if (m_BkgrLocation != BACKGR_TILE || m_hBkgrBitmap == NULL)
{
if (m_brBkgr.GetSafeHandle() != NULL)
{
pDC->FillRect(rectClient, &m_brBkgr);
}
else
{
CDialog::OnEraseBkgnd(pDC);
}
}
if (m_hBkgrBitmap == NULL)
{
return TRUE;
}
ASSERT(m_sizeBkgrBitmap != CSize(0, 0));
if (m_BkgrLocation != BACKGR_TILE)
{
CPoint ptImage = rectClient.TopLeft();
switch (m_BkgrLocation)
{
case BACKGR_TOPLEFT:
break;
case BACKGR_TOPRIGHT:
ptImage.x = rectClient.right - m_sizeBkgrBitmap.cx;
break;
case BACKGR_BOTTOMLEFT:
ptImage.y = rectClient.bottom - m_sizeBkgrBitmap.cy;
break;
case BACKGR_BOTTOMRIGHT:
ptImage.x = rectClient.right - m_sizeBkgrBitmap.cx;
ptImage.y = rectClient.bottom - m_sizeBkgrBitmap.cy;
break;
}
pDC->DrawState(ptImage, m_sizeBkgrBitmap, m_hBkgrBitmap, DSS_NORMAL);
}
else
{
// Tile background image:
for (int x = rectClient.left; x < rectClient.Width(); x += m_sizeBkgrBitmap.cx)
{
for (int y = rectClient.top; y < rectClient.Height(); y += m_sizeBkgrBitmap.cy)
{
pDC->DrawState(CPoint(x, y), m_sizeBkgrBitmap, m_hBkgrBitmap, DSS_NORMAL);
}
}
}
return TRUE;
}
void CDialogEx::OnDestroy()
{
if (m_bAutoDestroyBmp && m_hBkgrBitmap != NULL)
{
::DeleteObject(m_hBkgrBitmap);
m_hBkgrBitmap = NULL;
}
m_Impl.OnDestroy();
CDialog::OnDestroy();
}
HBRUSH CDialogEx::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
if (m_brBkgr.GetSafeHandle() != NULL || m_hBkgrBitmap != NULL)
{
#define AFX_MAX_CLASS_NAME 255
#define AFX_STATIC_CLASS _T("Static")
#define AFX_BUTTON_CLASS _T("Button")
if (nCtlColor == CTLCOLOR_STATIC)
{
TCHAR lpszClassName [AFX_MAX_CLASS_NAME + 1];
::GetClassName(pWnd->GetSafeHwnd(), lpszClassName, AFX_MAX_CLASS_NAME);
CString strClass = lpszClassName;
if (strClass == AFX_BUTTON_CLASS || strClass == AFX_STATIC_CLASS)
{
pDC->SetBkMode(TRANSPARENT);
if (m_brBkgr.GetSafeHandle() != NULL && IsAppThemed())
{
return (HBRUSH)m_brBkgr.GetSafeHandle();
}
else
{
return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
}
}
}
}
return CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
}
BOOL CDialogEx::PreTranslateMessage(MSG* pMsg)
{
if (m_Impl.PreTranslateMessage(pMsg))
{
return TRUE;
}
return CDialog::PreTranslateMessage(pMsg);
}
void CDialogEx::SetActiveMenu(CMFCPopupMenu* pMenu)
{
m_Impl.SetActiveMenu(pMenu);
}
BOOL CDialogEx::OnCommand(WPARAM wParam, LPARAM lParam)
{
if (m_Impl.OnCommand(wParam, lParam))
{
return TRUE;
}
return CDialog::OnCommand(wParam, lParam);
}
void CDialogEx::OnSysColorChange()
{
CDialog::OnSysColorChange();
if (AfxGetMainWnd() == this)
{
GetGlobalData()->UpdateSysColors();
}
}
void CDialogEx::OnSettingChange(UINT uFlags, LPCTSTR lpszSection)
{
CDialog::OnSettingChange(uFlags, lpszSection);
if (AfxGetMainWnd() == this)
{
GetGlobalData()->OnSettingChange();
}
}
我们认真的分析一下CDialogEx的公用方法,可以看到相对于基类Dialog,CDialogEx多了几个设置窗口背景颜色和背景图片等功能。
那么我们新手们看表面的都应该容易懂,但是如下的内容是不是有点云里雾里的。
//CDialogEx.h中
DECLARE_DYNAMIC(CDialogEx)
DECLARE_MESSAGE_MAP()
//CDialogEx.cpp中
IMPLEMENT_DYNAMIC(CDialogEx, CDialog)
BEGIN_MESSAGE_MAP(CDialogEx, CDialog)
ON_WM_ACTIVATE()
ON_WM_NCACTIVATE()
ON_WM_ERASEBKGND()
ON_WM_DESTROY()
ON_WM_CTLCOLOR()
ON_WM_SYSCOLORCHANGE()
ON_WM_SETTINGCHANGE()
END_MESSAGE_MAP()
我是在VS中查看定义,然后进行关键翻译。
1、 首先来看看 DECLARE_DYNAMIC(CDialogEx),查看定义我们会找到以下代码
#define DECLARE_DYNAMIC(class_name) \
public: \
static const CRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass() const; \
在#define 中 \的意思是与下内容相连,但后面的字符必须是回车。
还有一个 class##class_name的语法,可以理解为两个相连。比如,当前class_name 内容是CDialogEx,那么class##class_name的结果就是 classCDialogEx,这是一个代码名,不是字符串。
如此,我们可以将DECLARE_DYNAMIC(CDialogEx)可以替换为如下代码:
public:
static const CRuntimeClass classCDialogEx;
virtual CRuntimeClass*GetRuntimeClass() const;
会发现,不就弄了一个静态变量的动态类(运行时类),还有一个获取运行时类的函数,不过在头文件中,这里是声明的部分,相信.cpp文件中肯定还有定义。
2、DECLARE_MESSAGE_MAP()
查看定义,会发现有如下代码
#define DECLARE_MESSAGE_MAP() \
protected: \
static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
virtual const AFX_MSGMAP* GetMessageMap() const; \
//其中AFX_MSGMAP是一个消息回调相关的2个结构体
//如下是这2个结构体的内容
struct AFX_MSGMAP
{
const AFX_MSGMAP* (PASCAL * pfnGetBaseMap)();
const AFX_MSGMAP_ENTRY* lpEntries;
};
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT_PTR nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
//PASCAL 的定义
#define PASCAL __stdcall
都把定义查到了,DECLARE_MESSAGE_MAP()可以替换为:
protected:
static const AFX_MSGMAP* __stdcall GetThisMessageMap();
//AFX_MSGMAP是一个结构体
virtual const AFX_MSGMAP* GetMessageMap() const;
3、IMPLEMENT_DYNAMIC(CDialogEx, CDialog)
查看定义,会发现有如下代码
#define IMPLEMENT_DYNAMIC(class_name, base_class_name) \
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL, NULL)
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \
AFX_COMDAT const CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL, class_init }; \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); }
//AFX_COMDAT 的定义
#ifndef AFX_COMDAT
#define AFX_COMDAT __declspec(selectany)
#endif
这里会发现,尽然嵌套了两层定义
首先要把IMPLEMENT_DYNAMIC(CDialogEx, CDialog)替换为:
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL, NULL)
然后我们再来看IMPLEMENT_RUNTIMECLASS的内容。IMPLEMENT_RUNTIMECLASS中有带了5个参数,后面三个大家都看得到,前面两个传入的参数其实是CDialogEx, CDialog;知道了参数,那么我们就可以再次替换为如下代码:
// 带入参数为 class_name = CDialogEx base_class_name = CDialog
// wSchema:0xFFFF pfnNew:NULL class_init: NULL
// IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL, NULL)
__declspec(selectany) const CRuntimeClass CDialogEx::classCDialogEx=
{
"CDialogEx",
sizeof(class CDialogEx),
0xFFFF,
NULL,
RUNTIME_CLASS(CDialog),
NULL,
NULL
}
CRuntimeClass* CDialogEx::GetRuntimeClass() const
{
return RUNTIME_CLASS(CDialogEx);
}
4、BEGIN_MESSAGE_MAP(CDialogEx, CDialog)
ON_WM_PAINT()
END_MESSAGE_MAP()
这些是消息映射的地方,只拿ON_WM_PAINT()来做示范,其他的我们自己动手看看也是可以的,关键看到BEGIN~~~~到 END这2个东东的内在乾坤就好。
BEGIN_MESSAGE_MAP(CBaseDialog, CDialogEx)
END_MESSAGE_MAP()
//这2个其实是一个函数的上一半和下一半。
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
PTM_WARNING_DISABLE \
const AFX_MSGMAP* theClass::GetMessageMap() const \
{ return GetThisMessageMap(); } \
const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
{ \
typedef theClass ThisClass; \
typedef baseClass TheBaseClass; \
__pragma(warning(push)) \
__pragma(warning(disable: 4640)) \
/* message maps can only be called by single threaded message pump */ \
static const AFX_MSGMAP_ENTRY _messageEntries[] = \
{
#define ON_WM_PAINT() \
{ WM_PAINT, 0, 0, 0, AfxSig_vv, \
(AFX_PMSG)(AFX_PMSGW) \
(static_cast< void (AFX_MSG_CALL CWnd::*)(void) > ( &ThisClass :: OnPaint)) },
#define END_MESSAGE_MAP() \
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
}; \
__pragma(warning(pop)) \
static const AFX_MSGMAP messageMap = \
{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
return &messageMap; \
} \
PTM_WARNING_RESTORE
//其中的另外两个看不懂的定义如下:
#define PTM_WARNING_DISABLE \
__pragma(warning( push )) \
__pragma(warning( disable : 4867 ))
#define PTM_WARNING_RESTORE \
__pragma(warning( pop ))
//另外几个定义
#define WM_PAINT 0x000F
#define AFX_MSG_CALL
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);
按照我们找出的定义,可以翻译为:
//翻译后:带入参数值 theClass:CDialogEx baseClass:CDialog
//---------------------------------------------------------------
//BEGIN_MESSAGE_MAP(CBaseDialog, CDialogEx) 的内容
__pragma(warning( push ))
__pragma(warning( disable : 4867 ))
const AFX_MSGMAP* CDialogEx::GetMessageMap() const
{
return GetThisMessageMap();
}
const AFX_MSGMAP* __stdcall CDialogEx::GetThisMessageMap()
{
typedef CDialogEx ThisClass;
typedef CDialog TheBaseClass;
__pragma(warning(push))
__pragma(warning(disable: 4640)) /* message maps can only be called by single threaded message pump */
static const AFX_MSGMAP_ENTRY _messageEntries[] =
{
//----------------------------------------------------------------------
// ON_WM_PAINT()的内容
{
WM_PAINT, 0, 0, 0, AfxSig_vv,
(AFX_PMSG)(AFX_PMSGW)(static_cast<void( CWnd::*)(void) > ( &ThisClass :: OnPaint))
},
//----------------------------------------------------------------------
//END_MESSAGE_MAP()的内容
{ 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};
__pragma(warning(pop))
static const AFX_MSGMAP messageMap =
{
&CDialog::GetThisMessageMap, &_messageEntries[0]
};
return &messageMap;
}
__pragma(warning( pop ))
以上就是对CDialogEx类的一个解剖,如果大家有兴趣想自己动手写一个类,不妨就不要用MFC云山雾罩的写法,就把原模原样的写出来,这样我们会对Dialog类有了一层新的认识。
这篇只是本人作为MFC初学者的一篇学术研究型文章,如果有不足之处,请指正。
另外通过这一层剖析后,我就会急着写一个自己的Dialog类,基类为CDialogEx,然后也可以实现类向导添加其他消息函数和虚函数等。