第三课 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

  1. 每一个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的构造过程中完成了。

  2. 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;
}
  1. 我们可以将创建CBUTTON按钮窗口的代码放在这个函数里面,这样程序窗口创建的时候就会把这个按钮窗口添上去了。我们先在CMinFrame类中添加一个成员变量,一个CBUTTON对象:

    class CMainFrame : public CFrameWnd
    {
    ...
    private:
        CButton m_btn;
    }

    为什么不直接在OnCreate()函数中添加呢?因为在这里添加的只是局部变量,函数运行完毕之后,CBUTTON对象会进行析构,当对象被析构时,窗口资源会被释放,CBUTTON按钮窗口无法显示。

  2. 然后在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按钮窗口的目的。

  1. 以相同的方式在CAaaView类中添加m_btn的私有成员变量。

  2. 在CAaaView类中我们没有找到OnCreate()函数,但我们可以自己创建。类视图中右击CAssView,选择"Add Windows Messa Handler",出现一个添加消息响应的子窗口:

    创建Create响应操作wKioL1R0PpChNXLUAAMdhWpA9ns367.jpg

  3. 这时会出现如下代码,然后再加两行代码就行了:

    /
    // 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()函数中的指向父类窗口的指针指向哪一个对象有关系。