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