VC中单文档框架删除菜单的调试经历

来源:互联网 发布:淘宝上刷心悦的原理 编辑:程序博客网 时间:2024/06/10 05:20

  我的技术博客已搬家至: http://www.kai-zhou.com,  其他博客已停止更新,欢迎访问查看文章的最新版本.




 最近想在VC中单文档框架中删除菜单,状态栏,工具栏 。状态栏,工具栏在CMainFrame::OnCreate中就可以注释调,但是怎么样将菜单删掉呢?百度了一下,不太好找到。只好依靠现有知识,自己找到办法了。通过跟踪调试MFC的源代码居然让我找到了办法。

  首先,我们先想办法达到在单文档界面中不显示菜单的效果。

  通过现有知识,我们知道MFC是在CMainFrame::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(
"未能创建工具栏");
        
return -1;      // 未能创建
    }

    
if (!m_wndStatusBar.Create(this) ||
        
!m_wndStatusBar.SetIndicators(indicators,
          
sizeof(indicators)/sizeof(UINT)))
    {
        TRACE0(
"未能创建状态栏");
        
return -1;      // 未能创建
    }

    
// TODO: 如果不需要工具栏可停靠,则删除这三行
    m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
    EnableDocking(CBRS_ALIGN_ANY);
    DockControlBar(
&m_wndToolBar);
                     
return 0;
}

  所以如果不想要状态栏,工具栏,直接在CMainFrame::OnCreate中将相关代码注释调就行了。那么,菜单相关的代码在哪呢?遍历一遍CMainFrame类,没找到。根据代码相关性,既然状态栏,工具栏是在 CMainFrame::OnCreate中生成的,那么菜单估计也是在这个函数里面生成的。

  首先在 if (CFrameWnd::OnCreate(lpCreateStruct) == -1) 处下断点,进入函数内部,代码如下:

int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs)
{
    ENSURE_ARG(lpcs 
!= NULL);
    CCreateContext
* pContext = (CCreateContext*)lpcs->lpCreateParams;
    
return OnCreateHelper(lpcs, pContext);
}

  继续进入OnCreateHelper函数,代码如下:

int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
    
if (CWnd::OnCreate(lpcs) == -1)
        
return -1;

    
// create special children first
    if (!OnCreateClient(lpcs, pContext))
    {
        TRACE(traceAppMsg
, 0, "Failed to create client pane/view for frame.");
        
return -1;
    }

    
// post message for initial message string
    PostMessage(WM_SETMESSAGESTRING, AFX_IDS_IDLEMESSAGE);

    
// make sure the child windows have been properly sized
    RecalcLayout();

    
return 0;   // create ok
}

  下一步进入OnCreateClient,代码如下:

BOOL CFrameWnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext)
{
    
// default create client will create a view if asked for it
    if (pContext != NULL && pContext->m_pNewViewClass != NULL)
    {
        
if (CreateView(pContext, AFX_IDW_PANE_FIRST) == NULL)
            
return FALSE;
    }
    
return TRUE;
}
  进入CreateView,代码如下:
CWnd* CFrameWnd::CreateView(CCreateContext* pContext, UINT nID)
{
    
ASSERT(m_hWnd != NULL);
    
ASSERT(::IsWindow(m_hWnd));
    ENSURE_ARG(pContext 
!= NULL);
    ENSURE_ARG(pContext
->m_pNewViewClass != NULL);

    
// Note: can be a CWnd with PostNcDestroy self cleanup
    CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();
    
if (pView == NULL)
    {
        TRACE(traceAppMsg
, 0, "Warning: Dynamic create of view type %hs failed.",
            pContext
->m_pNewViewClass->m_lpszClassName);
        
return NULL;
    }
    ASSERT_KINDOF(CWnd
, pView);

    
// views are always created with a border!
    if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
        CRect(
0,0,0,0), this, nID, pContext))
    {
        TRACE(traceAppMsg
, 0, "Warning: could not create view for frame.");
        
return NULL;        // can't continue without a view
    }

    
if (pView->GetExStyle() & WS_EX_CLIENTEDGE)
    {
        
// remove the 3d style from the frame, since the view is
        //  providing it.
        // make sure to recalc the non-client area

        ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_FRAMECHANGED);
    }
    
return pView;
}
  进入 if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,CRect(0,0,0,0), this, nID, pContext)),代码如下:
BOOL CWnd::Create(LPCTSTR lpszClassName,
    LPCTSTR lpszWindowName
, DWORD dwStyle,
    
const RECT& rect,
    CWnd
* pParentWnd, UINT nID,
    CCreateContext
* pContext)
{
    
// can't use for desktop or pop-up windows (use CreateEx instead)
    ASSERT(pParentWnd != NULL);
    
ASSERT((dwStyle & WS_POPUP) == 0);

    
return CreateEx(0, lpszClassName, lpszWindowName,
        dwStyle 
| WS_CHILD,
        rect
.left, rect.top,
        rect
.right - rect.left, rect.bottom - rect.top,
        pParentWnd
->GetSafeHwnd(), (HMENU)(UINT_PTR)nID, (LPVOID)pContext);
}

  进入CreateEx,代码如下:

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;
    cs
.= 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 
= ::AfxCtxCreateWindowEx(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",
            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;
}

  注意 cs.hMenu = nIDorHMenu;  这段代码明显是与菜单有关的代码,那么找到了框架是如何生成菜单的,将菜单加入到框架中的,我们又如何在框架中将菜单删除呢?菜单的赋值是赋给CREATESTRUCT结构,所以猜测CREATESTRUCT结构可以控制菜单。返回到CMainFrame中,我们可以看到CMainFrame::OnCreate()的参数是LPCREATESTRUCT,所以先修改CMainFrame::OnCreate()如下:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    lpCreateStruct
->hMenu = NULL;
    
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        
return -1;
  
return 0;l
}

  编译,运行。还有菜单。 再回到CMainFrame中,发现PreCreateWindow也有CREATESTRUCT结构,修改代码如下:

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    cs
.hMenu = NULL;
    
if!CFrameWnd::PreCreateWindow(cs) )
        
return FALSE;
    
// TODO: 在此处通过修改
    //  CREATESTRUCT cs 来修改窗口类或样式


    
return TRUE;
}

  编译运行,成功了。菜单没了。

  第二步,既然菜单没用了,那么我们可不可以把wizard自动生成的菜单删除调呢?说干就干,删除菜单IDR_MAINFRAME,编译运行,什么“建立空文档失败”,程序直接退出。继续跟踪,调试吧。

  首先估计是在CMainFrame中出的问题,在CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)中下断点,编译运行,结果没进入断点,程序就出错了,那在App类的InitInstance()中下断点吧。跟踪,发现是在

    if (!ProcessShellCommand(cmdInfo))
        
return FALSE;
 中出错的。再次调试,进入ProcessShellCommand()函数内部,这次出错地方在
    case CCommandLineInfo::FileNew:
        
if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))
            OnFileNew();
  同理,一步一步的,我们就可以找到最后出错的确切地点,中间过程省略。最后发现出错代码如下:
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.");
            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.");
        
if (hMenu != NULL)
            DestroyMenu(hMenu);
        
return FALSE;
    }

    
return TRUE;
}

  在LoadMenu的时候出错了,所以执行PostNcDestroy()函数了。找到了错误所在,怎么修改代码呢?

  仔细阅读,发现Create()函数是在CFrameWnd类中,而CMainFrame类的父类就是CFrameWnd类,再一查,发现了CFrameWnd::Create()是虚函数,所以只要我们在CMainFrame中实现Create(),并将其中加载菜单的相关代码去掉应该就可以了,修改代码如下:

BOOL CMainFrame::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*/)
{
    HMENU hMenu 
= NULL;
    
if (lpszMenuName != NULL)
    {
    }
    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.");
        
if (hMenu != NULL)
            DestroyMenu(hMenu);
        
return FALSE;
    }
}

  编译,运行,成功了。

  以前写代码总是查上网,翻书,看帮助查资料,现在才发现源代码也是很好的资料。上网,翻阅书籍查资料固然不错,但是那是学习别人已有的知识,自己解决问题的能力没什么大的提高。通过研究源代码,我们可以在没有任何书籍,文档的情况下解决问题。这是我第一次深入跟踪到MFC源代码内部进行调试,希望对像我一样的初学者有帮助。