您的位置:首页 > 其它

魔改——MFC MDI程序 定制 文档模板 运行时全部打开 禁用关闭按钮

2015-02-09 21:13 483 查看
==================================声明==================================

本文原创,转载在正文中显要的注明作者和出处,并保证文章的完整性。

未经作者同意请勿修改(包括本声明),保留法律追究的权利。

未经作者同意请勿用于出版、印刷或学术引用。

本文不定期修正完善,为保证内容正确,建议移步原文处阅读。

本文链接:/article/5266556.html

=======================================================================

水平太低了,在青岛找个称心的工作,难——真的是不开空调啊,猎头怎么不信我呢!(这个梗请点我点我

这么晚了,还是写个博客看会儿书再睡觉吧,人丑就要多……

=======================================================================

先创建一个MFC程序。







工程创建完后,编译运行一下,看看默认是什么样子。如下图。



点击窗口-新建窗口(N),效果如图:



可以看到,两个窗口都是一样的。

=======================================================================

下面,就要开始魔改了。

=======================================================================

添加一个对话框资源,ID命名为IDD_FORMVIEW1。





给刚添加的资源添加类,CFV1,其基类为CFormView。



添加对IDC_BUTTON1的消息处理函数。

void CFV1::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
MessageBox(TEXT("1"),TEXT("CFV1"),MB_OK);
}


同样的步骤,再添加CFV2和对IDD_FORMVIEW2中IDC_BUTTON1的消息处理函数。

void CFV2::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
MessageBox(TEXT("2"),TEXT("CFV2"),MB_OK);
}


在TestMDIWLS.h中添加头文件

#include "FV1.h"
#include "FV2.h"


=======================================================================

到这里,让我们暂停一下。下面要进入重点了。

=======================================================================

对模板的注册是在CTestMDIWLSApp::InitInstance中实现的,下面是MFC的MDI中原有的代码。

// CTestMDIWLSApp 初始化

BOOL CTestMDIWLSApp::InitInstance()
{
// 如果一个运行在 Windows XP 上的应用程序清单指定要
// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
//则需要 InitCommonControlsEx()。否则,将无法创建窗口。
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
// 将它设置为包括所有要在应用程序中使用的
// 公共控件类。
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);

CWinAppEx::InitInstance();

// 初始化 OLE 库
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}

AfxEnableControlContainer();

EnableTaskbarInteraction();

// 使用 RichEdit 控件需要  AfxInitRichEdit2()
// AfxInitRichEdit2();

// 标准初始化
// 如果未使用这些功能并希望减小
// 最终可执行文件的大小,则应移除下列
// 不需要的特定初始化例程
// 更改用于存储设置的注册表项
// TODO: 应适当修改该字符串,
// 例如修改为公司或组织名
SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
LoadStdProfileSettings(0);  // 加载标准 INI 文件选项(包括 MRU)

InitContextMenuManager();

InitKeyboardManager();

InitTooltipManager();
CMFCToolTipInfo ttParams;
ttParams.m_bVislManagerTheme = TRUE;
theApp.GetTooltipManager()->SetTooltipParams(AFX_TOOLTIP_TYPE_ALL,
RUNTIME_CLASS(CMFCToolTipCtrl), &ttParams);

// 注册应用程序的文档模板。文档模板
// 将用作文档、框架窗口和视图之间的连接
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_TestMDIWLSTYPE,
RUNTIME_CLASS(CTestMDIWLSDoc),
RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架
RUNTIME_CLASS(CTestMDIWLSView));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);

// 创建主 MDI 框架窗口
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame || !pMainFrame->LoadFrame(IDR_MAINFRAME))
{
delete pMainFrame;
return FALSE;
}
m_pMainWnd = pMainFrame;
// 仅当具有后缀时才调用 DragAcceptFiles
//  在 MDI 应用程序中,这应在设置 m_pMainWnd 之后立即发生

// 分析标准 shell 命令、DDE、打开文件操作的命令行
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);

// 调度在命令行中指定的命令。如果
// 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// 主窗口已初始化,因此显示它并对其进行更新
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();

return TRUE;
}


注意这一块代码:(我手工对齐了一下)

// 注册应用程序的文档模板。文档模板
// 将用作文档、框架窗口和视图之间的连接
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_TestMDIWLSTYPE,
                    RUNTIME_CLASS(CTestMDIWLSDoc),
                    RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架
                    RUNTIME_CLASS(CTestMDIWLSView));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);


这里我们需要做的就是new一个自己新建的CFV,然后把它加入文档模板。

所以先再分析下CMultiDocTemplate(...)函数

CMultiDocTemplate(
UINT nIDResource,
CRuntimeClass* pDocClass,
CRuntimeClass* pFrameClass,
CRuntimeClass* pViewClass
);


后三个参数一目了然,形参自注释。

要注意的是第一个参数:UINT nIDResource

MSDN中这样说的:

nIDResource

Specifies the ID of the resources used with the document type. This may include menu, icon, accelerator table, and string resources.

The string resource consists of up to seven substrings separated by the '\n' character (the '\n' character is needed as a place holder if a substring is not included; however, trailing '\n' characters are not necessary); these substrings describe the document type. For information on the substrings, see CDocTemplate::GetDocString. This string resource is found in the application's resource file. For example:

// MYCALC.RC

STRINGTABLE PRELOAD DISCARDABLE

BEGIN

IDR_SHEETTYPE "\nSheet\nWorksheet\nWorksheets (*.myc)\n.myc\n MyCalcSheet\nMyCalc Worksheet"

END

Note that the string begins with a '\n' character; this is because the first substring is not used for MDI applications and so is not included. You can edit this string using the string editor; the entire string appears as a single entry in the String Editor, not as seven separate entries.

For more information about these resource types, see Resource Editors.


在这里最简单的方式就是在IDE里查找原来的IDR_TestMDIWLSTYPE所对应的字符串,然后模仿着修改。

我尝试了下直接用Sting_Table添加,并修改了一个字符串的部分字段(AAAA和BBBB),另一个未变,以便观察效果。(大晚上的我要是愿意看英文我早就去看美剧了。)



下面是在rc文件中的编码。(新手不要手工编辑,相关资料参见MSDN和罗云彬的汇编书,PS:典藏版亚马逊有售哦!)

/////////////////////////////////////////////////////////////////////////////
//
// String Table
//

STRINGTABLE
BEGIN
IDP_OLE_INIT_FAILED     "OLE 初始化失败。请确保 OLE 库是正确的版本。"
IDR_CFV1_TYPE           "\nAAAA\nBBBB\n\n\nTestMDIWLS.Document\nTestMDIWLS.Document"
IDR_CFV2_TYPE           "\nTestMDIWLS\nTestMDIWLS\n\n\nTestMDIWLS.Document\nTestMDIWLS.Document"
END

STRINGTABLE
BEGIN
IDR_MAINFRAME           "TestMDIWLS"
IDR_TestMDIWLSTYPE      "\nTestMDIWLS\nTestMDIWLS\n\n\nTestMDIWLS.Document\nTestMDIWLS.Document"
ID_WINDOW_MANAGER       "窗口(&W)..."
END

STRINGTABLE
BEGIN
AFX_IDS_APP_TITLE       "TestMDIWLS"
AFX_IDS_IDLEMESSAGE     "就绪"
END


可以看到13-18行是MFC自带的,我们手工添加的是7-11中的9、10两行。

根本就没有MSDN上面说的那个PRELOAD DISCARDABLE。看来不预加载也没问题啊。当然这是猜测,可能是资源太小了。

=======================================================================

看完刚才的资料,可以尝试着添加文档模板了。当然要是有闲心情——现在是深夜,不管你有没有,我反正没有——可以看下
https://msdn.microsoft.com/zh-cn/library/2b4xctyw%28v=vs.100%29.aspx和https://msdn.microsoft.com/zh-cn/library/feh4ww6k%28v=vs.100%29.aspx
=======================================================================

现在我们添加模板文档。先添加一个试试。

// 注册应用程序的文档模板。文档模板
// 将用作文档、框架窗口和视图之间的连接
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_TestMDIWLSTYPE,
RUNTIME_CLASS(CTestMDIWLSDoc),
RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架
RUNTIME_CLASS(CTestMDIWLSView));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);

//////////////////////////////////////////////////////////////////////////
//by wls 20150209 22:17:50
pDocTemplate = new CMultiDocTemplate(IDR_CFV1_TYPE,
RUNTIME_CLASS(CTestMDIWLSDoc),
RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架
RUNTIME_CLASS(CFV1));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);

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


编译、运行,效果:



注意这里是TestMDIWLS和BBBB。

点击BBBB确定后:



这里是AAAA1。

点击按钮1后:



现在就可以更改String_Table里的东西了。我手工改,毕竟编辑器有点蹩脚。

/////////////////////////////////////////////////////////////////////////////
//
// String Table
//

STRINGTABLE
BEGIN
IDP_OLE_INIT_FAILED     "OLE 初始化失败。请确保 OLE 库是正确的版本。"
IDR_CFV1_TYPE           "\nCFV1的FormView视图\nCFV1\n\n\nTestMDIWLS.Document\nTestMDIWLS.Document"
IDR_CFV2_TYPE           "\nCFV2的FormView视图\nCFV2\n\n\nTestMDIWLS.Document\nTestMDIWLS.Document"
END


再添加上CFV2:

//////////////////////////////////////////////////////////////////////////
//by wls 20150209 22:17:50
pDocTemplate = new CMultiDocTemplate(IDR_CFV1_TYPE,
RUNTIME_CLASS(CTestMDIWLSDoc),
RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架
RUNTIME_CLASS(CFV1));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);

pDocTemplate = new CMultiDocTemplate(IDR_CFV2_TYPE,
RUNTIME_CLASS(CTestMDIWLSDoc),
RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架
RUNTIME_CLASS(CFV2));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);

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


编译运行:



点击CFV1后确定:



点击新建,点击CFV2后确定:



发现确实是创建了2个模板。

=======================================================================

现在问题来了,如何让程序一开始运行时,就打开了2个模板?毕竟让用户自己手工点击,99.999%是会有问题的。

=======================================================================

让我们来回忆一下,我们是如何创建模板窗口的?第一个是程序默认给出的选择框。没错。

那第二个呢?第二个是我们手工点击的新建!而且出来的是一模一样的选择框!

现在我们要做的就是先看看这个点击新建的响应函数是怎样的。

=======================================================================

下面是MDI中默认的响应函数的映射宏:

// CTestMDIWLSApp

BEGIN_MESSAGE_MAP(CTestMDIWLSApp, CWinAppEx)
ON_COMMAND(ID_APP_ABOUT, &CTestMDIWLSApp::OnAppAbout)
// 基于文件的标准文档命令
ON_COMMAND(ID_FILE_NEW, &CWinAppEx::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, &CWinAppEx::OnFileOpen)
// 标准打印设置命令
ON_COMMAND(ID_FILE_PRINT_SETUP, &CWinAppEx::OnFilePrintSetup)
END_MESSAGE_MAP()


可以看到第6行:

ON_COMMAND(ID_FILE_NEW, &CWinAppEx::OnFileNew)


使用的是CWinAppEx的OnFileNew函数。显然MFC框架的代码是最好不修改的,只能自己模仿CWinAppEx的OnFileNew函数写一个自己的实现,姑且叫OnFileNewWLS吧。

在动手前,先看看OnFileNew是如何实现的:

/////////////////////////////////////////////////////////////////////////////
// WinApp features for new and open

void CWinApp::OnFileNew()
{
if (m_pDocManager != NULL)
m_pDocManager->OnFileNew();
}


那CDocManager的实现呢?如下:

void CDocManager::OnFileNew()
{
if (m_templateList.IsEmpty())
{
TRACE(traceAppMsg, 0, "Error: no document templates registered with CWinApp.\n");
AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);
return;
}

CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();
if (m_templateList.GetCount() > 1)
{
// more than one document template to choose from
// bring up dialog prompting user
CNewTypeDlg dlg(&m_templateList);
INT_PTR nID = dlg.DoModal();
if (nID == IDOK)
pTemplate = dlg.m_pSelectedTemplate;
else
return;     // none - cancel operation
}

ASSERT(pTemplate != NULL);
ASSERT_KINDOF(CDocTemplate, pTemplate);

pTemplate->OpenDocumentFile(NULL);
// if returns NULL, the user has already been alerted
}


看完了这些,我们就知道如何实现自己的OnFileNewWLS了,这是我的代码,仅供参考。

void CTestMDIWLSApp::OnFileNewWLS()
{
CDocTemplate* pTemplate = NULL;

POSITION pos = GetFirstDocTemplatePosition();
while(pos)
{
pTemplate = GetNextDocTemplate(pos);

ASSERT(pTemplate != NULL);
ASSERT_KINDOF(CDocTemplate, pTemplate);

pTemplate->OpenDocumentFile(NULL);
}
}


不要忘了修改映射宏。

// CTestMDIWLSApp

BEGIN_MESSAGE_MAP(CTestMDIWLSApp, CWinAppEx)
ON_COMMAND(ID_APP_ABOUT, &CTestMDIWLSApp::OnAppAbout)
// 基于文件的标准文档命令
//ON_COMMAND(ID_FILE_NEW, &CWinAppEx::OnFileNew)
ON_COMMAND(ID_FILE_NEW, &CTestMDIWLSApp::OnFileNewWLS)
ON_COMMAND(ID_FILE_OPEN, &CWinAppEx::OnFileOpen)
// 标准打印设置命令
ON_COMMAND(ID_FILE_PRINT_SETUP, &CWinAppEx::OnFilePrintSetup)
END_MESSAGE_MAP()


编译链接看效果:



注意到三个模板都实现了,但是界面有卡顿。

是时候表演真正的记性了!

手工修改String_Table。拆分第一个。添加PRELOAD DISCARDABLE。

STRINGTABLE
BEGIN
IDP_OLE_INIT_FAILED     "OLE 初始化失败。请确保 OLE 库是正确的版本。"
END

STRINGTABLE    PRELOAD DISCARDABLE
BEGIN
IDR_TestMDIWLSTYPE      "\nTestMDIWLS\nTestMDIWLS\n\n\nTestMDIWLS.Document\nTestMDIWLS.Document"
IDR_CFV1_TYPE           "\nCFV1的FormView视图\nCFV1\n\n\nTestMDIWLS.Document\nTestMDIWLS.Document"
IDR_CFV2_TYPE           "\nCFV2的FormView视图\nCFV2\n\n\nTestMDIWLS.Document\nTestMDIWLS.Document"
END

STRINGTABLE
BEGIN
IDR_MAINFRAME           "TestMDIWLS"
ID_WINDOW_MANAGER       "窗口(&W)..."
END


可能是心理效果,我感觉略微快了那么一点点。(处女座请自重。)

=======================================================================

下面屏蔽各种新建按钮,防止创建过多的模板视图。

更改之前的OnFileNewWLS代码,利用局部Static变量。

void CTestMDIWLSApp::OnFileNewWLS()
{
static BOOL bNew=FALSE;

if (bNew==FALSE)
{
bNew=TRUE;

CDocTemplate* pTemplate = NULL;

POSITION pos = GetFirstDocTemplatePosition();
while(pos)
{
pTemplate = GetNextDocTemplate(pos);

ASSERT(pTemplate != NULL);
ASSERT_KINDOF(CDocTemplate, pTemplate);

pTemplate->OpenDocumentFile(NULL);
}
}
}


下面要屏蔽新建窗口按钮



在rc文件中有这么一行

ID_WINDOW_NEW           "为活动文档打开另一个窗口\n新建窗口"


所以需要屏蔽的就是对ID_WINDOW_NEW的响应,所以添加第10行代码,重写自己的响应函数。

// CMainFrame

IMPLEMENT_DYNAMIC(CMainFrame, CMDIFrameWndEx)

BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWndEx)
ON_WM_CREATE()
ON_COMMAND(ID_WINDOW_MANAGER, &CMainFrame::OnWindowManager)
ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnApplicationLook)
ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnUpdateApplicationLook)
ON_COMMAND(ID_WINDOW_NEW,&CMainFrame::OnWindowNewWLS)
END_MESSAGE_MAP()


OnWindowsWLS的实现:

void CMainFrame::OnWindowNewWLS()
{
//no op
}


现在已经能够在运行时一次性创建3个视图,并且不再创建额外的视图。

=======================================================================

既然能且只能创建3个视图,那么就不需要在选项卡上显示表示实例数目的阿拉伯字母了。

BOOL CTestMDIWLSDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;

// TODO: 在此添加重新初始化代码
// (SDI 文档将重用该文档)
CString strTitle=GetTitle();
strTitle=strTitle.Left(strTitle.GetLength()-1);
SetTitle(strTitle);

return TRUE;
}


效果:



=======================================================================

下面要做的就是屏蔽彩色选项卡的关闭按钮。

那么这个一直被我称为选项卡/标签页的东西到底是什么呢?使用Spy++查看下。



显示的是TabWnd,那么是一个类似Tab窗口的东西。这些都在CMainFrame::OnCreate里有初始化设置。

先看看MDI原有的代码:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIFrameWndEx::OnCreate(lpCreateStruct) == -1)
return -1;

BOOL bNameValid;
// 基于持久值设置视觉管理器和样式
OnApplicationLook(theApp.m_nAppLook);

CMDITabInfo mdiTabParams;
mdiTabParams.m_style = CMFCTabCtrl::STYLE_3D_ONENOTE; // 其他可用样式...
mdiTabParams.m_bActiveTabCloseButton = TRUE;      // 设置为 FALSE 会将关闭按钮放置在选项卡区域的右侧
mdiTabParams.m_bTabIcons = FALSE;    // 设置为 TRUE 将在 MDI 选项卡上启用文档图标
mdiTabParams.m_bAutoColor = TRUE;    // 设置为 FALSE 将禁用 MDI 选项卡的自动着色
mdiTabParams.m_bDocumentMenu = TRUE; // 在选项卡区域的右边缘启用文档菜单
EnableMDITabbedGroups(TRUE, mdiTabParams);

m_wndRibbonBar.Create(this);
m_wndRibbonBar.LoadFromResource(IDR_RIBBON);

if (!m_wndStatusBar.Create(this))
{
TRACE0("未能创建状态栏\n");
return -1;      // 未能创建
}

CString strTitlePane1;
CString strTitlePane2;
bNameValid = strTitlePane1.LoadString(IDS_STATUS_PANE1);
ASSERT(bNameValid);
bNameValid = strTitlePane2.LoadString(IDS_STATUS_PANE2);
ASSERT(bNameValid);
m_wndStatusBar.AddElement(new CMFCRibbonStatusBarPane(ID_STATUSBAR_PANE1, strTitlePane1, TRUE), strTitlePane1);
m_wndStatusBar.AddExtendedElement(new CMFCRibbonStatusBarPane(ID_STATUSBAR_PANE2, strTitlePane2, TRUE), strTitlePane2);

// 启用 Visual Studio 2005 样式停靠窗口行为
CDockingManager::SetDockingMode(DT_SMART);
// 启用 Visual Studio 2005 样式停靠窗口自动隐藏行为
EnableAutoHidePanes(CBRS_ALIGN_ANY);

// 启用增强的窗口管理对话框
EnableWindowsDialog(ID_WINDOW_MANAGER, ID_WINDOW_MANAGER, TRUE);

// 将文档名和应用程序名称在窗口标题栏上的顺序进行交换。这
// 将改进任务栏的可用性,因为显示的文档名带有缩略图。
ModifyStyle(0, FWS_PREFIXTITLE);

return 0;
}


没错,就是CMDITabInfo!找到她了!

10-16是对彩色的选项卡进行的设置。想要屏蔽关闭按钮,要修改为:

CMDITabInfo mdiTabParams;
mdiTabParams.m_style = CMFCTabCtrl::STYLE_3D_ONENOTE; // 其他可用样式...
//mdiTabParams.m_bActiveTabCloseButton = TRUE;      // 设置为 FALSE 会将关闭按钮放置在选项卡区域的右侧
mdiTabParams.m_bTabCloseButton=FALSE;
mdiTabParams.m_bTabIcons = FALSE;    // 设置为 TRUE 将在 MDI 选项卡上启用文档图标
mdiTabParams.m_bAutoColor = TRUE;    // 设置为 FALSE 将禁用 MDI 选项卡的自动着色
mdiTabParams.m_bDocumentMenu = TRUE; // 在选项卡区域的右边缘启用文档菜单
EnableMDITabbedGroups(TRUE, mdiTabParams);


这样,就完成了。



=======================================================================

网上有关禁用MDI选项卡关闭按钮的文章,大多抄袭自MSDN的这篇文档http://support.microsoft.com/kb/201553/zh-cn

但是,不适用于VS2010下使用Office风格的MDI程序——该文档只更新到2006年11月21日。

=======================================================================

本文一步一步的解析实现,故暂不提供示例工程源代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐