目录
接上:VC++MFC框架窗口(二)AfxEndDeferRegisterClass 函数(wincore.cpp)
接下:VC++ 消息循环 Run函数 PumpMessage函数 AfxInternalPumpMessage函数
2.创建窗口
按照Win32程序编写步骤,在设计窗口类并注册窗口类之后创建窗口。在MFC程序中,窗口的创建功能是由 CWnd 类的 CreateEx 函数实现的,该函数的声明位于afxwin.h文件中,具体代码如下所示。
CreateEx 函数(afxwin.h)声明:
// advanced creation (allows access to extended styles)
// 高级创建(允许访问扩展样式)
virtual BOOL CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam = NULL);
CreateEx 函数(wincore.cpp)定义:
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
ASSERT(lpszClassName == NULL || AfxIsValidString(lpszClassName) ||
AfxIsValidAtom(lpszClassName));
ENSURE_ARG(lpszWindowName == NULL || AfxIsValidString(lpszWindowName));
// allow modification of several common create parameters
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
cs.lpszClass = lpszClassName;
cs.lpszName = lpszWindowName;
cs.style = dwStyle;
cs.x = x;
cs.y = y;
cs.cx = nWidth;
cs.cy = nHeight;
cs.hwndParent = hWndParent;
cs.hMenu = nIDorHMenu;
cs.hInstance = AfxGetInstanceHandle();
cs.lpCreateParams = lpParam;
if (!PreCreateWindow(cs))
{
PostNcDestroy();
return FALSE;
}
AfxHookWindowCreate(this);
HWND hWnd = CreateWindowEx(cs.dwExStyle, cs.lpszClass,
cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
#ifdef _DEBUG
if (hWnd == NULL)
{
TRACE(traceAppMsg, 0, "Warning: Window creation failed: GetLastError returns 0x%8.8X\n",
GetLastError());
}
#endif
if (!AfxUnhookWindowCreate())
PostNcDestroy(); // cleanup if CreateWindowEx fails too soon
if (hWnd == NULL)
return FALSE;
ASSERT(hWnd == m_hWnd); // should have been set in send msg hook
return TRUE;
}
在MFC底层代码中,CFrameWnd类的Create函数内部调用了上述CreateEx函数。而前者又由CFrameWnd类的LoadFrame函数调用。CFrameWnd类的Create函数的声明也位于afxwin.h文件中,代码如下。
Create函数(afxwin.h)声明:
virtual BOOL Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle = WS_OVERLAPPEDWINDOW,
const RECT& rect = rectDefault,
CWnd* pParentWnd = NULL, // != NULL for popups
LPCTSTR lpszMenuName = NULL,
DWORD dwExStyle = 0,
CCreateContext* pContext = NULL);
Create函数(winfrm.cpp)定义:
BOOL CFrameWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
LPCTSTR lpszMenuName,
DWORD dwExStyle,
CCreateContext* pContext)
{
HMENU hMenu = NULL;
if (lpszMenuName != NULL)
{
// load in a menu that will get destroyed when window gets destroyed
HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, ATL_RT_MENU);
if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)
{
TRACE(traceAppMsg, 0, "Warning: failed to load menu for CFrameWnd.\n");
PostNcDestroy(); // perhaps delete the C++ object
return FALSE;
}
}
m_strTitle = lpszWindowName; // save title for later
if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
{
TRACE(traceAppMsg, 0, "Warning: failed to create CFrameWnd.\n");
if (hMenu != NULL)
DestroyMenu(hMenu);
return FALSE;
}
return TRUE;
}
CFrameWnd类派生于CWnd类,根据类的继承性原理,CFrameWnd类就继承了CWnd类的CreateEx函数。因此,CFrameWnd类的Create函数内调用的实际上就是CWnd类的CreateEx函数。
提示:在调试Test程序时,会发现无法进入MFC源码设置的断点处,这是因为在Visual Studio 2017默认安装后没有带调试所需要的PDB文件(该文件主要存储了 Visual Studio 调试程序时所需要的基本信息),需要我们自己下载。在Visual Studio开发环境中,单击菜单栏上的【工具】菜单,单击【选项】菜单项,在出现的“选项”对话框中,在左边的列表框中找到“调试”节点,选中“符号”,在右侧选中“Microsoft符号服务器”,同时可以设置一个缓存符号的目录,如图所示。单击“确定”按钮,就可以开始调试运行Test程序,跟踪 MFC的源码了。第一次调试的时候可能会比较慢,这是因为在调试过程中会下载所需的PDB 文件,下载的同时会缓存到我们指定的缓存符号的目录下,后期调试将直接使用本地的PDB文件,就很快了。
下载PDB文件:
在CWnd类的CreateEx函数实现代码中,发现该函数中又调用了 PreCreateWindow 函数(虚函数)。因此,实际上调用的是子类,即CMainFrame 类的 PreCreateWindow 函数。再次调用这个函数,主要是为了在产生窗口之前让程序员有机会修改窗口外观。例如,去掉窗口的最大化按钮等,PreCreateWindow 函数的参数就是为了实现这个功能而提供的。该参数的类型是CREATETRUCT结构,我们可以把这个结构体与CreateWindowEx函数的参数做一个比较,如下图所示是CREATETRUCT结构和CreateWindowEx函数声明的对比。注意:左边结构体成员与右边函数参数的对应关系。
可以发现,CREATETRUCT结构体中的字段与CreateWindowEx函数的参数是一致的,只是先后顺序相反而已。同时,可以看到PreCreateWindow函数的这个参数是引用类型。这样,在子类中对此参数所做的修改,在其基类中是可以体现出来的。再看看前面 CWnd 类的 CreateEx 函数代码,如果在子类的PreCreateWindow 函数中修改了CREATESTRUCT 结构体的值,那么,接下来调用 CreateWindowEx 函数时,其参数就会发生相应的改变,从而创建一个符合我们要求的窗口。
知识点:在MFC中后缀名为Ex的函数都是扩展函数。
3.显示窗口和更新窗口
Test程序的应用程序类TestApp从它的基类CWinThread继承了一个名为m_pMainWnd的成员变量(公有成员)。该变量是一个 CWnd 类型的指针,它保存了应用程序框架窗口对象的指针。也就是说,是指向 CMainFrame 对象的指针。在 CTestApp 类的 InitInstance函数实现内部有如下两行代码。
// 调度在命令行中指定的命令。 如果
// 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。
if (!ProcessShellCommand(cmdInfo))
return FALSE;//此时窗口创建完成
// 唯一的一个窗口已初始化,因此显示它并对其进行更新
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
这两行代码的功能是显示应用程序框架窗口和更新这个窗口。