您的位置:首页 > 编程语言 > C语言/C++

VC++编程之第三课笔记――MFC窗口创建过程以及窗口类的封装

2014-11-25 16:26 537 查看

第三课 MFC窗口创建过程以及窗口类的封装

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的继承图表。

MFC中的
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()
的调用。可以在上面的代码中加个断点调试,程序会运行到断点处停下来。说明这个确实是程序的入口点。

CWinApp

每一个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;
}

/////////////////////////////////////////////////////////////////////////////

1. Afx的含义

C++不是完全面向对象的语言,而为了各个类之间能有机的组合在一起,需要定义一些全局的函数。我们称它叫应用程序框架类的函数。它们都是以Afx开头的。由于是全局函数,所以每个类都可以调用。

2. pThread与pApp指针

CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
这两句中的
AfxGetThread()
AfxGetApp()
函数获得的指针实际上都是指向theApp的指针。因为CWinApp类是由CWinThread派生出来的,CAaaApp是由CWinApp派生出来的。它们都是指向其派生类的指针。

3. 三个函数

InitApplication();
InitInstance();
Run();
这三个函数会完成作为一个应用程序所需要的几个步骤。即设计窗口类、注册窗口类、产生窗口、显示窗口、更新窗口、消息循环、窗口过程函数。

窗口的创建过程

1.
InitApplication()

它是MFC内部管理所调用的函数,在VC98\MFC\SRC\APPCORE.CPP中。

2. 窗口类的设计

在MFC中,它已经预先为我们定义好了缺省的窗口类,我们只需要调用
AfxEndDeferRegisterClass()
函数注册就行了。

3. 单文档的注册不一致原因

按正常的顺序的话,窗口类的注册是由4.1所介绍的
PreCreateWincow()
函数调用注册函数来完成的。但是由于我们创建的是一个单文档应用程序,牵扯到文档的管理,窗口类的注册被提前了(先进行了4.2的注册)。在
InitInstance()
函数里面处理这个Shell命令时就开始并完成了注册:
if (!ProcessShellCommand(cmdInfo))
return FALSE;

4.1 窗口类的注册(正常的注册)

这个应用程序实际上有两个窗口,一个是整个的框架窗口,是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;

4.2 窗口类的注册(
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;
}
...
...
}

5. 窗口的产生

在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()
函数来调用的,具体的可以自己跟踪一下。

6. 显示窗口、更新窗口

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的生命周期不一致,不要混淆了。

7. 消息循环(
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()
函数完成了消息循环。

8. 消息处理函数(窗口过程函数)

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采用了一种叫做消息映射的技术,做了转换,由消息响应函数来进行处理。这里不多说了,下面会介绍到。

9. 总结

MFC中的窗口创建过程与第一章Windows程序窗口的创建过程是一样的,也分为这几个步骤。

MFC中的窗口类已经预先设计好了,我们只需要去注册就行了。

而这些窗口类要进行修改的话只有在
PreCreateWindow()
函数中的
CreateEx()
函数中进行修改,由于CREATESTRUCT这个参数是引用传递的,会影响到其注册的窗口。

MFC中也是会有消息循环的。

设置断点跟踪的时候我们会发现它调用了很多次
CreateEx()
函数,原因是一个完整的单文档应用程序会创建很多的窗口,一些控件窗口、视图窗口什么的。

MFC中不是所有的消息都交给缺省的窗口过程函数进行处理的,它采用了消息映射的技术。

要注意哪个才是窗口的句柄,
m_pMainWnd
是指向的是框架窗口对象,
m_hWnd
才代表一个窗口。

10.模拟

代码如下:
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窗口(在CMainFrame类中添加)

添加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)的话,会覆盖掉工具栏。而非客户区包括标题栏和菜单栏。下面的章节笔记会介绍到。

添加CBUTTON窗口(在CAaaView类中添加)

其实,我们同样可以在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()
函数中的指向父类窗口的指针指向哪一个对象有关系。

本文出自 “zero4eva” 博客,请务必保留此出处http://zero4eva.blog.51cto.com/7558298/1582453
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐