MFC的每一个类都是以C开头的,表明这是一个Class。
创建工程名为aaa的工程(单文档)时,在类视图中可看见五个类:
CAboutDlg CMainFrame CAaaApp CAaaDoc CAaaView
其中:
类CAboutDlg继承自CDialog类,对话框的类
类CMainFrame继承自CFrameWnd类,创建整个程序的框架窗口
类CAaaApp继承自CWinApp类,创建唯一的应用程序对象
类CAaaDoc继承自CDocument类,数据的存储加载由Doc来完成
类CAaaView继承自CView类,数据的显示修改由View类来完成
而CDialog、CFrameWnd与CView又都继承自CWnd类,CWnd类封装了所有与窗口相关的操作。
其他类的继承关系,可以查阅MSDN的继承图表。
WinMain()
函数在哪里使用Win32 SDK编程的都知道,WinMain()
函数是Win32程序的入口点,可是在MFC的框架中,却找不到WinMain()
函数。那么,在MFC中,WinMain()
函数到底去哪了?
其实,MFC也是需要调用这个入口函数的:
在VC98\MFC\SRC中可以找到APPMODUL.CPP文件,在里面有这样一段代码:
extern int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow); extern "C" int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { //在这里加个断点试试 // call shared/exported WinMain return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow); }
再在_tWinMain()
函数上右击选"Go To Definition Of _tWinMain",可进入VC98\Include\TCHAR.H,里面有这样的定义:
#define _tWinMain WinMain
这个时候,我们就明白了:MFC先把WinMain()
函数宏定义为_tWinMain()
,然后在_tWinMain()
中调用AfxWinMain()
函数,把对WinMain()
的调用转化为了对AfxWinMain()
的调用。从这可以看出,即使在MFC中,也不能跳过对WinMain()
的调用。
可以在上面的代码中加个断点调试,程序会运行到断点处停下来。说明这个确实是程序的入口点。
每一个MFC工程,有且只能有一个由CWinApp派生出来的类,也只能有一个由这个应用程序类所实例化的对象(theApp)。
在aaa.cpp文件中,会有这样的代码:
///////////////////////////////////////////////////////////////////////////// // CAaaApp construction CAaaApp::CAaaApp() { // TODO: add construction code here, // Place all significant initialization in InitInstance } ///////////////////////////////////////////////////////////////////////////// // The one and only CAaaApp object CAaaApp theApp;
在这里,这个全局对象(theApp)就表示了整个个应用程序本身。就相当于在Win32中的WinMain()
函数中的一个实例号。而全局对象会在调用主函数之前进行内存分配,所以它会在AfxWinMain()
函数之前进行CWinApp以及CAaaApp构造函数的调用。
程序的一些初始化工作就在CWinApp的构造过程中完成了。
CWinApp是怎样构造的呢?我们可以在VC98\MFC\SRC\APPCORE.CPP文件中找到它的构造函数:
CWinApp::CWinApp(LPCTSTR lpszAppName) { if (lpszAppName != NULL) m_pszAppName = _tcsdup(lpszAppName); else m_pszAppName = NULL; // initialize CWinThread state AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE(); AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread; ASSERT(AfxGetThread() == NULL); pThreadState->m_pCurrentWinThread = this; ASSERT(AfxGetThread() == this); m_hThread = ::GetCurrentThread(); m_nThreadID = ::GetCurrentThreadId(); // initialize CWinApp state ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please pModuleState->m_pCurrentWinApp = this; ASSERT(AfxGetApp() == this); // in non-running state until WinMain m_hInstance = NULL; m_pszHelpFilePath = NULL; m_pszProfileName = NULL; ... }
我们发现它是有参数的,那么我们用的时候为什么不需要参数呢?答案是这个参数有一个缺省值:
class CWinApp : public CWinThread { DECLARE_DYNAMIC(CWinApp) public: // Constructor CWinApp(LPCTSTR lpszAppName = NULL); // app name defaults to EXE name ... ... }
APPCORE.CPP中继续往下看,我们会发现这样一行:
pThreadState->m_pCurrentWinThread = this;
这个this指针到底指向谁呢?其实指向的是CAaaApp这个派生类的对象,即我们声明的theApp这个全局对象。
再下面的代码也都是初始化工作
AfxWinMain()
函数全局对象初始化完毕,就来到了程序入口函数了。AfxWinMain()
函数可以在VC98\MFC\SRC\WINMAIN.CPP文件中找到:
///////////////////////////////////////////////////////////////////////////// // Standard WinMain implementation // Can be replaced as long as 'AfxWinInit' is called first int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { ASSERT(hPrevInstance == NULL); int nReturnCode = -1; CWinThread* pThread = AfxGetThread(); CWinApp* pApp = AfxGetApp(); // AFX internal initialization if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow)) goto InitFailure; // App global initializations (rare) if (pApp != NULL && !pApp->InitApplication()) goto InitFailure; // Perform specific initializations if (!pThread->InitInstance()) { if (pThread->m_pMainWnd != NULL) { TRACE0("Warning: Destroying non-NULL m_pMainWnd\n"); pThread->m_pMainWnd->DestroyWindow(); } nReturnCode = pThread->ExitInstance(); goto InitFailure; } nReturnCode = pThread->Run(); ... return nReturnCode; } /////////////////////////////////////////////////////////////////////////////
C++不是完全面向对象的语言,而为了各个类之间能有机的组合在一起,需要定义一些全局的函数。我们称它叫应用程序框架类的函数。它们都是以Afx开头的。由于是全局函数,所以每个类都可以调用。
CWinThread* pThread = AfxGetThread(); CWinApp* pApp = AfxGetApp();
这两句中的AfxGetThread()
、AfxGetApp()
函数获得的指针实际上都是指向theApp的指针。因为CWinApp类是由CWinThread派生出来的,CAaaApp是由CWinApp派生出来的。它们都是指向其派生类的指针。
InitApplication(); InitInstance(); Run();
这三个函数会完成作为一个应用程序所需要的几个步骤。即设计窗口类、注册窗口类、产生窗口、显示窗口、更新窗口、消息循环、窗口过程函数。
InitApplication()
它是MFC内部管理所调用的函数,在VC98\MFC\SRC\APPCORE.CPP中。
在MFC中,它已经预先为我们定义好了缺省的窗口类,我们只需要调用AfxEndDeferRegisterClass()
函数注册就行了。
按正常的顺序的话,窗口类的注册是由4.1所介绍的PreCreateWincow()
函数调用注册函数来完成的。但是由于我们创建的是一个单文档应用程序,牵扯到文档的管理,窗口类的注册被提前了(先进行了4.2的注册)。在InitInstance()
函数里面处理这个Shell命令时就开始并完成了注册:
if (!ProcessShellCommand(cmdInfo)) return FALSE;
这个应用程序实际上有两个窗口,一个是整个的框架窗口,是CMainFrame类窗口,而CMainFrame类是由CFrameWnd派生的子类,CFeameWnd又是由CWnd派生出来的;还有一个是就是其中空白的区域,它也是一个窗口,是CView类的窗口,CView也是由CWnd派生的子类(继承图表中可见)。
正常的注册是调用CMainFrame中的PreCreateWindow()
函数(产生窗口之前):
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs return TRUE; }
我们发现它调用了父类的PreCreateWindow()
函数。VC98\MFC\SRC\WINFRM.CPP中发现其父类CFrameWnd中的PreCreateWindow()
函数这样定义:
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs) { if (cs.lpszClass == NULL) { VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG)); cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background } if ((cs.style & FWS_ADDTOTITLE) && afxData.bWin4) cs.style |= FWS_PREFIXTITLE; if (afxData.bWin4) cs.dwExStyle |= WS_EX_CLIENTEDGE; return TRUE; }
if (cs.lpszClass == NULL)
当类名为空时
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
它是判断我们当前的窗口类有没有被注册,若没有注册就注册它,完成注册的AfxDeferRegisterClass()
函数在VC98\MFC\SRC\AFXIMPL.H中有这样的宏定义:
#define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)
说明它还是调用的AfxEndDeferRegisterClass()
函数来完成注册。
然后把这个已注册的窗口框架类类名赋值给lpszClass:
cs.lpszClass = _afxWndFrameOrView;
AfxEndDeferRegisterClass()
函数)它是由AfxEndDeferRegisterClass()
函数完成的。
这个函数在VC98\MFC\SRC\WINCORE.CPP中,会根据我们创建工程的向导时选择的选项调用AfxRegisterClass()
函数来注册相对应的窗口类。
而AfxRegisterClass()
函数也在VC98\MFC\SRC\WINCORE.CPP中,它会先检查这个窗口有没有被注册,如果已经注册过了它会返回一个TRUE,若没有被注册它会再调用与Win32一样的RegisterClass()
函数来注册窗口。
BOOL AFXAPI AfxRegisterClass(WNDCLASS* lpWndClass) { WNDCLASS wndcls; if (GetClassInfo(lpWndClass->hInstance, lpWndClass->lpszClassName, &wndcls)) { // class already registered return TRUE; } if (!::RegisterClass(lpWndClass)) //调用RegisterClass()函数 { TRACE1("Can't register window class named %s\n", lpWndClass->lpszClassName); return FALSE; } ... ... }
在VC98\MFC\SRC\WINFRM.CPP中PreCreateWindow()
函数的定义后面有一个CFrameWnd类的Create()
函数定义:
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, RT_MENU); if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL) { TRACE0("Warning: failed to load menu for CFrameWnd.\n"); PostNcDestroy(); // perhaps delete the C++ object return FALSE; } } m_strTitle = lpszWindowName; // save title for later //这里调用了CreateEx()函数 if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext)) { TRACE0("Warning: failed to create CFrameWnd.\n"); if (hMenu != NULL) DestroyMenu(hMenu); return FALSE; } return TRUE; }
这个函数调用了VC98\MFC\SRC\WINCORE.CPP中的CreateEx()
函数:
///////////////////////////////////////////////////////////////////////////// // CWnd creation BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, LPVOID lpParam /* = NULL */) { return CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, pParentWnd->GetSafeHwnd(), (HMENU)nID, lpParam); } 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) { // 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)) //又调用了PreCreateWindow() { 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); ... if (hWnd == NULL) return FALSE; ASSERT(hWnd == m_hWnd); // should have been set in send msg hook return TRUE; }
我们会发现,在CreateEx()
函数中又调用了PreCreateWindow()
函数,右击转进去看看,父类中是如此定义的:
// special pre-creation and window rect adjustment hooks virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
它是一个虚函数,所以转进子类中去看:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs return TRUE; }
它的参数是一个CREATESTRUCT结构体的引用,而CREATESTRUCT结构体定义如下:
typedef struct tagCREATESTRUCTA { LPVOID lpCreateParams; HINSTANCE hInstance; HMENU hMenu; HWND hwndParent; int cy; int cx; int y; int x; LONG style; LPCSTR lpszName; LPCSTR lpszClass; DWORD dwExStyle; } CREATESTRUCTA, *LPCREATESTRUCTA;
我们与CreateEx()
函数的参数相比发现,这两个的参数正好是完全相反的。为什么呢?原因是这样的:由于MFC为我们定义好了窗口类,当我们在CreateEx()
中去修改结构体的成员变量值的时候,由于是引用,这些值会相应的发生改变,这样就产生了一个能够符合我们要求的窗口。
这样能让我们在产生窗口之前有机会修改窗口的外观,所以结构体CREATESTRUCT的变量和产生窗口的CreateEx()
函数的参数是一样的。
最后,补充:Create()
这个函数又是由LoadFrame()
函数来调用的,具体的可以自己跟踪一下。
在InitInstance()
中,它完成了窗口的显示、窗口的更新。
这个函数在CWinThread类中是一个未定义的虚函数,所以我们可以在子类(CAaaApp)中查看其定义:
///////////////////////////////////////////////////////////////////////////// // CAaaApp initialization BOOL CAaaApp::InitInstance() { ... // Dispatch commands specified on the command line if (!ProcessShellCommand(cmdInfo)) return FALSE; // The one and only window has been initialized, so show and update it. m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow(); return TRUE; }
其中m_pMainWnd
是一个指向框架窗口对象的一个指针,也就是CMainFrame的一个指针。
当ProcessShellCommand()
执行完成之后,窗口实际上已经产生了。然后就调用了ShowWindow()
、UpdateWindow()
两个函数来显示和更新窗口。而这两个函数与第一章笔记中的Win32程序的两个函数相比,少了一个句柄参数,因为它已经被封装在了CWnd类中,是其公有成员变量,所以不需要传递这个参数:
class CWnd : public CCmdTarget { DECLARE_DYNCREATE(CWnd) protected: static const MSG* PASCAL GetCurrentMessage(); // Attributes public: HWND m_hWnd; // must be first data member ... }
注意:m_hWnd才是窗口的句柄,当窗口销毁时,m_pMainWnd这个对象并没有销毁,只是将它的m_hWnd变量设为NULL,所以m_pMainWnd指向的对象的生命周期与这个m_hWnd的生命周期不一致,不要混淆了。
Run()
函数)VC98\MFC\SRC\THRDCORE.CPP中有着这样一个函数:
// main running routine until thread exits int CWinThread::Run() { ... for (;;) { ... // phase2: pump messages while available do { // pump message, but quit on WM_QUIT if (!PumpMessage()) return ExitInstance(); // reset "no idle" state after pumping "normal" message if (IsIdleMessage(&m_msgCur)) { bIdle = TRUE; lIdleCount = 0; } } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)); } ASSERT(FALSE); // not reachable }
函数里面有一个do while循环,循环中有一个PumpMessage()
函数,在同一个CPP中我们可以找到其定义:
///////////////////////////////////////////////////////////////////////////// // CWinThread implementation helpers BOOL CWinThread::PumpMessage() { ASSERT_VALID(this); if (!::GetMessage(&m_msgCur, NULL, NULL, NULL)) { ... // process this message if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur)) { ::TranslateMessage(&m_msgCur); ::DispatchMessage(&m_msgCur); } return TRUE; }
在这里面我们发现了熟人:GetMessage()
、TranslateMessage()
、DispatchMessage()
三个函数。这三个函数的作用可以看上一章的笔记。
可以看出,Run()
函数完成了消息循环。
在AfxEndDeferRegisterClass()
函数中,我们可以发现它赋值的窗口处理函数是缺省的:
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister) { ... // common initialization WNDCLASS wndcls; memset(&wndcls, 0, sizeof(WNDCLASS)); // start with NULL defaults wndcls.lpfnWndProc = DefWindowProc; //窗口过程函数的赋值 wndcls.hInstance = AfxGetInstanceHandle(); wndcls.hCursor = afxData.hcurArrow; ... }
而实际上,在MFC中,不是所有的消息都交给缺省的窗口过程函数进行处理的,MFC采用了一种叫做消息映射的技术,做了转换,由消息响应函数来进行处理。这里不多说了,下面会介绍到。
MFC中的窗口创建过程与第一章Windows程序窗口的创建过程是一样的,也分为这几个步骤。
MFC中的窗口类已经预先设计好了,我们只需要去注册就行了。
而这些窗口类要进行修改的话只有在PreCreateWindow()
函数中的CreateEx()
函数中进行修改,由于CREATESTRUCT这个参数是引用传递的,会影响到其注册的窗口。
MFC中也是会有消息循环的。
设置断点跟踪的时候我们会发现它调用了很多次CreateEx()
函数,原因是一个完整的单文档应用程序会创建很多的窗口,一些控件窗口、视图窗口什么的。
MFC中不是所有的消息都交给缺省的窗口过程函数进行处理的,它采用了消息映射的技术。
要注意哪个才是窗口的句柄,m_pMainWnd
是指向的是框架窗口对象,m_hWnd
才代表一个窗口。
代码如下:
class CWnd { public: BOOL CreateExDWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle,int x,int y,int nWidth,int nHeight,HWND hWndParent,HMENU hMenu,HINSTANCE hInstance,LPVOID lpParam); BOOL ShowWindow(int nCmdShow); BOOL UpdateWindow(); protected: private: HWND m_hWnd; }; BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle,int x,int y,int nWidth,int nHeight,HWND hWndParent,HMENU hMenu,HINSTANCE hInstance,LPVOID lpParam) { m_hWnd=::CreateWindowEx(dwExStyle,lpClassName, lpWindowName, dwStyle, x, y,nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); if(m_hWnd!=NULL) return TRUE; else return FALSE; } BOOL CWnd::ShowWindow(int nCmdShow) { return ::ShowWindow(m_hWnd,nCmdShow); } BOOL CWnd::UpdateWindow() { return ::UpdateWindow(m_hWnd); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASS wndcls; wndcls.cbClsExtra=0; wndcls.cbWndExtra=0; ......; //窗口类的设计 RegisterClass(&wndcls); //MFC形式 CWnd wnd; wnd.CreateEx(...); wnd.ShowWindow(SW_SHOWNORMAL); wnd.UpdateWindow(); //Win32程序形式 HWND hwnd; hwnd=CreateWindowEx(...); ::ShowWindow(hwnd,SW_SHOWNORMAL); ::UpdateWindow(hwnd); ......; //消息循环 //假如执行到这里的时候,窗口已经销毁了,但是wnd这个对象还是可以用的 //m_hWnd只是wnd这个对象中的一个成员 } //只有在这里,C++对象wnd的生命周期才算是结束了
所以说,CMainFrame和CAaaView这两个类或者这两类的对象并不代表窗口,当窗口销毁时,这两个类的成员函数还可以调用。查阅MSDN我们可以发现CWnd类中有一个成员就是m_hWnd,它保存了一个窗口的句柄。
在类视图CAaaApp下点击InitInstance(),可以发现这样一段代码:
CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(CAaaDoc), RUNTIME_CLASS(CMainFrame), // main SDI frame window RUNTIME_CLASS(CAaaView)); AddDocTemplate(pDocTemplate);
它通过一个单文档模板将四个类有机的组合在了一起,最后又利用AddDocTemplate()
函数将这个单文档模板增加到了文档模板当中。
添加CBUTTON窗口应该在框架窗口产生之后添加,不然CBUTTON窗口没地方放。
CMainFrame中有一个响应WM_CREATE消息的函数,是OnCreate()
:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) { TRACE0("Failed to create toolbar\n"); return -1; // fail to create } if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar\n"); return -1; // fail to create } // TODO: Delete these three lines if you don't want the toolbar to // be dockable m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar); return 0; }
我们可以将创建CBUTTON按钮窗口的代码放在这个函数里面,这样程序窗口创建的时候就会把这个按钮窗口添上去了。我们先在CMinFrame类中添加一个成员变量,一个CBUTTON对象:
class CMainFrame : public CFrameWnd { ... private: CButton m_btn; }
为什么不直接在OnCreate()
函数中添加呢?因为在这里添加的只是局部变量,函数运行完毕之后,CBUTTON对象会进行析构,当对象被析构时,窗口资源会被释放,CBUTTON按钮窗口无法显示。
然后在OnCreate()
函数中添加如下代码:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { ... m_btn.Create("NAME",WS_CHILD | BS_DEFPUSHBUTTON,CRect(50,50,100,100),this,123); m_btn.ShowWindow(SW_SHOWNORMAL); return 0; }
其中:
Create()
函数创建一个窗口并将其与CBUTTON对象联系起来。
BOOL Create(LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID);
lpszCaption
按钮的名称
dwStyle
按钮的类型,以"BS_"开头。由于这个按钮还是框架窗口的子窗口,还需添加一个"WS_CHILD"说明它为子窗口。
rect结构体
它有四个成员:
typedef struct tagRECT { LONG left; LONG top; LONG right; LONG bottom; } RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;分别是左上和右下的坐标,形成了一个矩形的范围。
pParentWnd
指向父类的指针,这里填写this,指向CMainFrame这个对象。
nID
一个整型数,用以标识这个按钮窗口。
执行后就行发现一个显示着"NAME"的按钮出现在了程序窗口上。框架窗口分为客户区和非客户区,它显示在客户区上。客户区包括工具栏,所以如果你的rect参数是(0,0,100,100)的话,会覆盖掉工具栏。而非客户区包括标题栏和菜单栏。下面的章节笔记会介绍到。
其实,我们同样可以在CAaaView类中添加代码用以达到添加CBUTTON按钮窗口的目的。
以相同的方式在CAaaView类中添加m_btn的私有成员变量。
在CAaaView类中我们没有找到OnCreate()
函数,但我们可以自己创建。类视图中右击CAssView,选择"Add Windows Messa Handler",出现一个添加消息响应的子窗口:
这时会出现如下代码,然后再加两行代码就行了:
///////////////////////////////////////////////////////////////////////////// // CAaaView message handlers int CAaaView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here //添加代码 m_btn.Create("NAME",WS_CHILD | BS_DEFPUSHBUTTON,CRect(0,0,100,100),this,123); m_btn.ShowWindow(SW_SHOWNORMAL); //将这两句换成一句也可以:m_btn.Create("NAME",WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,CRect(0,0,100,100),this,123); return 0; }
这个时候,this指针指向的是CAaaView类的对象。执行后我们发现,按钮窗口出现在工具栏的下面,并没有将工具栏覆盖掉。
由于CView类也是CWnd类的派生类,我们用GetParent()函数获得其父类的指针试试,即:
m_btn.Create("NAME",WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,CRect(0,0,100,100),GetParent(),123);
我们发现按钮窗口又覆盖了工具栏。这时我们可以得到一个结论:按钮窗口出现在哪里与代码添加到哪里没有关系,它只是与CButton::Create()
函数中的指向父类窗口的指针指向哪一个对象有关系。