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

  我的技术博客已搬家至: 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源代码内部进行调试,希望对像我一样的初学者有帮助。

你可能感兴趣的:(框架,null,文档,mfc,工具)