MDI 框架的自动标签栏






4.80/5 (12投票s)
一个可停靠的栏,
1. 引言
此代码实现了 MDI 标签视图,方便导航。视图支持在控件条上,该控件条可以浮动(当然也可以停靠!)。2. 已实现的功能
- 基于控件条的所有者绘制的标签视图。支持字体和颜色设置,默认如下:
- 正常/非活动标签文本为黑色,
- 活动标签文本为蓝色,
- 修改过的文档的标签文本为红色。
- 控件条可以停靠(目前仅支持顶部和底部)或浮动。
- 自定义 MDI 窗口列表对话框,类似于 VC++。
- 全屏模式。
- 炫酷菜单弹出,三种类型。
- 从标签控件本身右键单击,如上图所示。
- 从标签控件条右键单击。
- 从 MDI 主窗口客户区右键单击,仅为占位符(演示),请自行构建。
- 在 MDI 主窗口客户区显示公司 Logo 文本。
- MDI 客户区背景文本 Logo 和横幅绘制。
- 保存和恢复 MDI 子窗口的状态,即正常或最大化,以及控件条和主窗口框架的位置。
- 工具提示和标签图标(图标!...只是个演示,不知道什么更好,请告诉我您的看法)。
- 对现有项目的修改最少,可以实现自动化!只需修改现有主框架类和可能的应用程序类,无需派生。
3. Unicode?
没有理由它不能正常工作!4. 所需文件
- WindowManager.cpp/h: 管理窗口列表,并子类化 MDI 客户端以创建标签视图栏。它还包含 `CDocumentList` 类,列出所有打开的文档。这才是真正的地方...
注意: `CDocumentList` 类对许多应用程序都非常有用,请仔细查看。 - ViewManager.cpp/h: 管理应用程序的视图,创建视图标签。
- WindowTabCtrl.cpp/h: 实现标签控件的代码。
- PopupMenu.cpp/h: 实现弹出菜单的代码。
其他
- tabview.rc: 全屏工具栏、弹出菜单和窗口列表对话框资源。
- tabview.h: 包含控件所需的资源 ID。(注意:不直接使用,稍后删除)。
- tabview.bmp: 全屏和弹出菜单位图。
5. 如何使用?
将资源文件集成到您的项目中需要做一些工作。我们先来做最难的部分...- 将位图文件 `tabview.bmp` 移动到您的 `res` 目录,将 `tabview.rc` 移动到项目目录。
- 将资源文件 `tabview.rc` 添加到项目的 `*.rc2` 文件中,并将资源 ID 文件 `tabview.h` 合并到您的资源文件 `resource.h` 中。如果您从未手动修改过资源文件,请阅读此内容...
- 在您的 `resource.h` 文件中,找到最后一个资源类型(编号从 128 开始),复制并粘贴 `tabview.h` 中的资源类型标识符(2),为每个标识符分配一个递增的 ID,最后将 VC++ 对象 `_APS_NEXT_RESOURCE_VALUE` 增加 2,即比最后一个递增 ID 多 1。
- 同样,复制并粘贴对话框控件 ID(8)(编号从 1000 开始),并将 `_APS_NEXT_CONTROL_VALUE` 增加 8。
- 最后,复制并粘贴菜单项 ID(9)(编号从 32771 开始),并将 `_APS_NEXT_COMMAND_VALUE` 增加 9。项目现在应该可以毫无问题地编译,您可以删除 `tabview.h` 文件了。
- 现在,将 `WindowManager.cpp/h`、`ViewManager.cpp/h`、`WindowTabCtrl.cpp/h` 和 `PopupMenu.cpp/h` 文件移动到项目目录,并将实现文件添加到项目中。
- 打开 `WindowManager.h` 文件,在 **前向声明** 部分将 CMainFrame 更改为您主窗口框架的类名(参见 TODO)。同时在 `WindowManager.cpp` 中包含您主框架类的头文件。
- 在您的主框架头文件中包含 `WindowManager.h` 和 `ViewManager.h` 头文件,并在 **public** 部分声明以下内容:
CMDIClient m_MDIClient; void GetControlBarsEx(CArray<CControlBar*, CControlBar*>& arrBars);
- m_MDIClient,它是窗口管理器的一个实例,所有事件都在此协调。
- GetControlBarsEx,一个用于全屏模式的新方法。为此方法提供一个类似以下的实现:(在数组参数中添加所有您希望在全屏模式下管理的控件条)
void CMainFrame::GetControlBarsEx(CArray<CControlBar*, CControlBar*>& arrBars) { if (::IsWindow(m_wndToolBar.m_hWnd)) arrBars.Add(&m_wndToolBar); if (::IsWindow(m_wndStatusBar)) arrBars.Add(&m_wndStatusBar); //...more here }
- 在主框架的 `OnCreate()` 函数的末尾添加以下内容:
VERIFY(m_MDIClient.SubclassMDIClient(this));
`SubclassMDIClient()` 的原型如下:BOOL SubclassMDIClient(CMDIFrameWnd* pMDIFrameWnd, CViewManager* pViewManager, UINT uID = ID_VIEW_VIEWTAB);
- pMDIFrameWnd,指向应用程序主框架的指针。
- pViewManager,指向 viewmanager 实例的指针。viewmanager、控件条和标签控件由窗口管理器创建。
- uID,控件条的 ID。使用默认值,控件条的隐藏和显示已实现。但是,如果您决定使用其他 ID,例如 ID_THE_NEWVALUE,您可以通过在主框架的消息映射中插入以下内容轻松实现标签控件条的隐藏和显示。 **无需进一步的代码**。这正是 AppWizard 为默认状态栏和工具栏所做的...
ON_COMMAND_EX(ID_THE_NEWVALUE, OnBarCheck) ON_UPDATE_COMMAND_UI(ID_THE_NEWVALUE, OnUpdateControlBarMenu)
- 为了让命令消息直接在 `CWindowManager` 中处理,请使用 ClassWizard 重写主框架的 `OnCmdMsg()` 方法,并将其修改为类似以下内容:
// This function routes commands to window manager, then to rest of system. BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { // Without this, the window manager menu commands will be disabled, // this is because without routing the command to the window manager, // MFC thinks there is no handler for it. if (m_MDIClient.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; return CMDIFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); }
- 累了吧!编译并享受乐趣...
- 好吧,您还需要这个...构建菜单,所有菜单 ID 都已定义,所以只需在属性表的 **ID** 组合框中选择它们。
- 在“视图”菜单上构建以下内容:
菜单项 ID 菜单项字符串 ID_VIEW_VIEWTAB 打开文件标签 (&O) ID_VIEW_FULLSCREEN 全屏 (&F) - 在“窗口”菜单上构建以下内容:
ID_WINDOW_NEXT 下一个窗口 (&N) ID_WINDOW_PREVIOUS 上一个窗口 (&P) 关闭全部 (&C) ID_WINDOW_CLOSE_ALL 保存全部 (&S) ID_WINDOW_SAVE_ALL - 注意:“窗口...”菜单会自动为您构建。
- 在“视图”菜单上构建以下内容:
- 好吧,好吧,好吧...如果您需要支持位置和控件条的恢复,那么就这样做...(不幸的是,MFC 框架不会调用主框架类析构函数或窗口管理器类析构函数!)
- 为您的主框架添加 `WM_CLOSE` 消息处理程序,或修改现有的处理程序并添加以下一行代码,调用 `SaveMainFrameState()` 方法...
void CMainFrame::OnClose() { ...... m_MDIClient.SaveMainFrameState(); ...... CMDIFrameWnd::OnClose(); }
- 最后!将应用程序类 `InitInstance()` 末尾的主框架显示代码替换为 `RestoreMainFrameState()`,如下所示:
BOOL CDemoApp::InitInstance() { ............................. // The main window has been initialized, so show and update it. // pMainFrame->ShowWindow(m_nCmdShow); ///// <--- we do not need this one! pMainFrame->m_MDIClient.RestoreMainFrameState(m_nCmdShow); pMainFrame->UpdateWindow(); return TRUE; }
- 为您的主框架添加 `WM_CLOSE` 消息处理程序,或修改现有的处理程序并添加以下一行代码,调用 `SaveMainFrameState()` 方法...
6. 代码片段
标签中的图标支持是应用程序特定的。我的意思是,您需要为您的应用程序构建一个更合适的解决方案。但是,如果您有关于如何实现通用方案的想法,请告诉我。对于当前的实现...
`CViewManager` 的 `OnCreate()` 函数(它创建了标签本身和标签栏)只是创建了一个占位符图标来填充图像列表,然后将该图像列表附加到标签。
int CViewManager::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CControlBar::OnCreate(lpCreateStruct) == -1) return -1; m_ViewTabImages.Create(16, 16, ILC_MASK, 5, 5); m_ViewTabCtrl.Create(WS_CHILD | WS_VISIBLE | WS_EX_NOPARENTNOTIFY | TCS_TOOLTIPS | TCS_SINGLELINE | TCS_FOCUSNEVER | TCS_FORCELABELLEFT, CRect(0, 0, 0, 0), this, ID_VIEWTAB); m_ViewTabCtrl.SetImageList(&m_ViewTabImages); // Build the image list here HICON hIcon = AfxGetApp()->LoadStandardIcon(IDI_APPLICATION); // HICON hIcon = AfxGetApp()->LoadIcon(IDR_DEMOTYPE); m_ViewTabImages.Add(hIcon); // Enable tooltips for all controls EnableToolTips(TRUE); return 0; }`LoadStandardIcon()` 用于加载系统图标。您可能希望将其替换为注释掉的代码,`IDR_DEMOTYPE` 是您的应用程序特定的资源类型。
HICON hIcon = AfxGetApp()->LoadIcon(IDR_DEMOTYPE);在同一个类的 `AddView()` 函数中,标签的图像索引设置为 0(零),这是图像列表中唯一的图像。最后,在标签 `CWindowTabCtrl` 的 `DrawItem()` 函数中,占位符图标被替换为附加到子窗口(视图的父窗口)框架的小图标。
void CWindowTabCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { .... CView* pView = reinterpret_cast<CView*>(tci.lParam); CDocument* pDoc = pView->GetDocument(); // Draw image if (m_bDisplayIcons) { CImageList* pImageList = GetImageList(); CMDIChildWnd* pViewFrame = static_cast<CMDIChildWnd*>(pView->GetParent()); HICON hIcon = reinterpret_cast<HICON>(GetClassLong(pViewFrame->m_hWnd, GCL_HICONSM)); pImageList->Replace(nTabIndex, hIcon); .... } .... }最初,我考虑根据注册的文件扩展名 `SHGetFileInfo()` API 从 Windows Shell 获取图标。但是,视图框架图标与标签视图图标不同,看起来不太好。通过当前的实现,只需要良好的公民意识,就像 VC++ 本身一样。让您的应用程序子窗口系统图标反映文件类型,就不需要编写额外的代码了。
7. 致谢
该代码基于以下人员的代码和想法:- Iuri Apollonio,他使用状态栏编写了基础代码。他的新电子邮件地址是什么?
- Ivan Zhakov,他的“MDI Windows Manager 对话框”比 Iuri 的更好。窗口管理器类现在是驱动视图管理器的主要引擎。
- Chris Maunder,他的所有者绘制标签控件代码片段被用来改进 Iuri 的代码。
- Adolf Szabo,他的全屏模式理念比 MS 和 MS 的 Mike B 实现的更简单。
- 您,以及许多其他人...
可以在任何项目中使用此代码,没有任何限制!在评论部分写下您对这段代码的任何喜好或不喜欢,我会记录下来。 祝你编码愉快... Paul Selormey, Japan。 |
8. 待办事项
- 能够停靠在主框架的所有侧面,这需要更多的工作,因为标签控件是所有者绘制的。
- 改进主框架窗口位置的保存和恢复...
- 支持多显示器 - 我现在没有操作系统(Win98/Win2000)来测试这个(即使有,也没有显卡和显示器!)。
- 支持屏幕分辨率更改。我有用于我的 C/SDK 应用程序的 API 代码,在 Win95 上使用 QuickRes 程序进行了充分测试。我目前不使用 QuickRes,所以有点不愿意实现它。
- 您的愿望...
9. 已知问题
- 弹出菜单目前不支持加速键(但需要吗?)。
- 修改标志未立即重绘,我不希望引入闪烁! (实现仍然足够好!)。
- 添加您自己的...
10. 本次更新
- 许多部分被重写,以解决评论区的大部分问题。
- “标签视图”现在是 CMDIChildWnd 类,所以分割器等应该可以工作。
- 许多错误修复。
- 应该可以与现有代码无需过多修改即可工作,参见第 5 步。