大小可调停靠控件栏中的视图 (CSizingControlBar)






4.70/5 (18投票s)
2000 年 3 月 15 日

271479

8006
一种相对简单的方法,将视图整合到可调大小的控制栏中
我喜欢 Cristi Posea 对可调整大小的停靠控制条的 实现,并决定在我正在进行的一个项目中使用它们。不幸的是,我确实需要将它们与视图一起使用,并着手研究这个问题。我在其中一条评论中读到,有几个人尝试使用框架窗口来辅助 MFC 框架以使用视图。我决定看看这有多难。就我需要的功能而言,事实证明它相对容易。
请注意,我不会描述如何处理多文档上的多个视图以及如何将它们链接在一起并进行管理。这是一个复杂的主题,并且高度依赖于应用程序,我认为最好留给特定应用程序来解决。(该示例确实允许将视图链接到不同的文档)。
我正在解释的是一种将视图合并到可调整大小的控制条对象中的方法。
以下是步骤:
步骤 1
我创建了一个新的 CViewBar 类,它继承自 CSizingControlBar 类之一。(#define TViewBarBase 为您需要的基类)。此类封装了框架窗口,因此您不必知道它的存在。(MFC 支持只需要一个框架窗口,它是什么类型的并不重要。)创建和大小调整都在类中处理。
另外,还在 create 成员函数中处理了视图类的指定。我只是让 MFC 通过将类类型传递给它来完成所有工作。这样,您就不必派生数以亿计的不同类来使用停靠视图。这引出了我的下一步。
// ViewBar.h: interface for the CViewBar class. // ////////////////////////////////////////////////////////////////////// #ifndef VIEWBAR_H #define VIEWBAR_H #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include "sizecbar.h" #include "scbarg.h" #include "scbarcf.h" // You must #define this for viewbar to compile properly #define TViewBarBase CSizingControlBarCF class CViewBar : public TViewBarBase { DECLARE_DYNAMIC(CViewBar); public: CViewBar(); virtual ~CViewBar(); virtual BOOL Create( CWnd* pParentWnd, // mandatory CRuntimeClass *pViewClass, // mandatory CCreateContext *pContext = NULL, LPCTSTR lpszWindowName = NULL, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_TOP, UINT nID = AFX_IDW_PANE_FIRST); protected: CFrameWnd *m_pFrameWnd; CCreateContext m_Context; // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CViewBar) public: //}}AFX_VIRTUAL //{{AFX_MSG(CViewBar) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnSize(UINT nType, int cx, int cy); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; #endif // ViewBar.cpp: implementation of the CViewBar class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "ViewBar.h" #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif IMPLEMENT_DYNAMIC(CViewBar, TViewBarBase); ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CViewBar::CViewBar() { ZeroMemory(&m_Context, sizeof(m_Context)); // Create a frame object. CRuntimeClass *pRuntimeClass = RUNTIME_CLASS(CFrameWnd); CObject* pObject = pRuntimeClass->CreateObject(); ASSERT( pObject->IsKindOf( RUNTIME_CLASS( CFrameWnd ) ) ); m_pFrameWnd = (CFrameWnd *)pObject; } CViewBar::~CViewBar() { } BEGIN_MESSAGE_MAP(CViewBar, TViewBarBase) //{{AFX_MSG_MAP(CRegBar) ON_WM_CREATE() ON_WM_SIZE() //}}AFX_MSG_MAP END_MESSAGE_MAP() ////////////////////////////////////////////////////////////////////// // CViewBar message handlers /* --------------------------------------------------------------- Overridden to set the view class before calling the base create function. For an SDI application the main frame window is created when the document template is created and a valid CreateContext is passed through (hurray!). Meaning there is no problem creating the ViewBar in the frame window create. However, for an MDI application the main frame window is constructed outside the document creation, so the CreateContext isn't present. In this case you can either create and setup a CreateContext object with the desired characteristics (doc template, frame window, etc.) and pass it, or let the CViewBar::Create() create a CreateContext with only the runtime class of the view, and the main frame window set. --------------------------------------------------------------- */ BOOL CViewBar::Create( CWnd* pParentWnd, CRuntimeClass *pViewClass, CCreateContext *pContext, LPCTSTR lpszWindowName, DWORD dwStyle, UINT nID) { ASSERT(pViewClass != NULL); ASSERT(pViewClass->IsDerivedFrom(RUNTIME_CLASS(CWnd))); if (pContext) memcpy(&m_Context, pContext, sizeof(m_Context)); else { CFrameWnd *fw = (CFrameWnd *)AfxGetMainWnd(); if (fw) { m_Context.m_pCurrentDoc = fw->GetActiveDocument(); m_Context.m_pCurrentFrame = fw; } } m_Context.m_pNewViewClass = pViewClass; return TViewBarBase::Create( lpszWindowName, pParentWnd, nID, dwStyle); } /* --------------------------------------------------------------- Create the frame window associated with the view bar. --------------------------------------------------------------- */ int CViewBar::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (TViewBarBase::OnCreate(lpCreateStruct) == -1) return -1; if (!m_pFrameWnd->Create(NULL, NULL, WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE, CRect(0,0,0,0), this, NULL, 0, &m_Context)) return -1; return 0; } /* --------------------------------------------------------------- Must remember to move the frame window as well... --------------------------------------------------------------- */ void CViewBar::OnSize(UINT nType, int cx, int cy) { CRect rc; TViewBarBase::OnSize(nType, cx, cy); GetClientRect(rc); m_pFrameWnd->MoveWindow(rc); }
题外话
唉,乏味。您不希望有某种方法来表示直接基类,而不必显式指定它吗?
像 'base::' 这样的东西会很方便。您可以更通用地编写代码。当然,您可以使用模板,但这比我喜欢的要复杂。
第二步
如何创建视图:做显而易见的事情,将其传递给 MFC,让它完成所有工作。视图信息可以通过 CCreateContext 对象传递给 MFC。此对象被传递给 MFC 中的许多窗口创建函数。MFC 在创建视图时会使用它。不幸的是,这个创建上下文在原始的可调整大小的控制条创建中没有被传递,这意味着需要一点诡计(除非您想修改原始类)。因此,ViewBar 类中维护了一个 CCreateContext 对象,仅仅是为了不修改基类。
在调用框架窗口 Create() 时需要传递它。如果您不喜欢这一点,可以修改 CSizingControlBar::Create 函数以接受 CCreateContext。但是,既然可以避免,我就避免了。这样我的类就与原始类解耦了,这意味着如果原始作者决定进行更新,我更新我的软件会更容易。而且,我像所有优秀的程序员一样,非常懒惰。
要在您的应用程序中使用 ViewBar,请使用类似以下的代码:(参见示例)
sTitle.Format(_T("My Bar %d"), i + 1); if (!m_wndMyBars[i].Create(this, RUNTIME_CLASS (CAForm), (CCreateContext *)(lpCreateStruct->lpCreateParams), sTitle))//AFX_IDW_CONTROLBAR_FIRST + 33 + i)) { TRACE0("Failed to create ViewBar\n"); return -1; // fail to create }
除了 create 函数之外,它的用法与现有的可调整大小的控制条类完全相同。您只需要传递您想创建的视图的 RUNTIME_CLASS。
步骤 3
此时,支持视图的修改似乎起作用了,除了一个棘手的问题——菜单访问(当视图是浮动的)。(即使是快捷键也有效!)出于各种原因,这很难实现。
最终,您必须保存并恢复菜单激活之前的焦点所在的窗口。这对于用户按下 Esc 键的情况是必需的。(Windows/MFC 不自动执行此操作的原因超出了我的理解。您还能在哪里设置它?)菜单命令完成后哪个窗口是活动的完全取决于应用程序。
要从浮动的视图激活主菜单,请重写视图的 PreTranslateMessage() 函数,并在检测到菜单按键时将焦点设置回主窗口。像这样:
/* --------------------------------------------------------------- Change the input focus to the main window when a menu key is selected. --------------------------------------------------------------- */ BOOL CAForm::PreTranslateMessage(MSG* pMsg) { if (pMsg->message == WM_SYSKEYDOWN && pMsg->wParam == VK_MENU && ((CViewBar *)(GetParent()->GetParent()))->IsFloating()) { ((CMainFrame *)AfxGetMainWnd())->m_hWndBeforeMenu = ::GetFocus(); AfxGetMainWnd()->SetFocus(); } return CFormView::PreTranslateMessage(pMsg); }
不要尝试将此行代码放入控制条类的 PreTranslateMessage() 中,如果您能这样做会很好,但这不起作用。在某个地方 SYSKEY 消息会被“吃掉”。
出于几个原因,我没有费心派生一个特殊的类来处理此操作:
- 我应该从哪个视图类派生?为仅支持一行代码而镜像现有的 MFC 视图类是很愚蠢的。
- 您很可能已经在派生自己的视图类,因此将其放在类中相对容易。
- 您可能不在乎在视图是浮动的时候访问主菜单。
*注意:您不能使用 CWnd 指针来跟踪焦点并使用 GetFocus() 和 SetFocus() 进行恢复。您必须使用 ::Setfocus()、::GetFocus() 并跟踪窗口句柄。
将以下代码放入应用程序的 CMainFrame 类中。它会恢复按下 Esc 键之前处于活动状态的窗口。
同样,以类似的方式,您需要将代码添加到您希望返回到浮动窗口(如果它之前是活动的)的任何菜单命令处理程序中。
/* --------------------------------------------------------------- If the last key pressed before the menu is exited was the escape key, then set the focus back to the window that was active before the menu was activated. * This message is not available in class wizard as a frame window message. * --------------------------------------------------------------- */ void CMainFrame::OnExitMenuLoop( BOOL bIsTrackPopupMenu ) { if (((CSCBDemoApp *)AfxGetApp())->m_nLastKey == VK_ESCAPE) { if (m_hWndBeforeMenu) { ::SetFocus(m_hWndBeforeMenu); } } m_hWndBeforeMenu = NULL; CWnd::OnExitMenuLoop(bIsTrackPopupMenu); }
注释
- AppWizard 不提供此功能供框架窗口使用。
- 在将此函数添加到类后,如果您尝试添加更多虚拟函数或消息处理程序,AppWizard 会出现故障。所以请在完成所有其他工作后再添加它。(至少在 VC6.0 中是这样——出现关于解析的错误……)
- 如果您多疑,您会认为这是故意的。
将以下代码添加到跟踪应用程序的按键操作。(需要检测 Esc 键)。在应用程序类中添加一个 UINT 成员变量 m_nLastKey。
BOOL CSCBDemoApp::ProcessMessageFilter(int code, LPMSG lpMsg) { if (lpMsg->message == WM_KEYDOWN) m_nLastKey = lpMsg->wParam; return CWinApp::ProcessMessageFilter(code, lpMsg); }
完成这一切后,焦点操作仍然存在轻微异常。不幸的是,此机制的一个副作用是它会自动激活与主框架窗口关联的窗口。如果您有识别活动窗口的代码(例如在 GotFocus() 中),该代码将被激活,尽管主菜单是活动的。(按下 Esc 会因为 Esc 会将焦点返回到原始窗口而使此窗口再次非活动)。
请注意,我认为目前的菜单处理方式很丑陋,但对我来说是有效的。如果有更好的方法(我确定有),我很想听听。
其他说明
您可能认为您可以在不使用框架窗口的情况下做到这一点。我试过了,几乎可行。它适用于常规的表单视图,但当我尝试将其与 CEditView 派生类一起使用时,*砰*,编辑视图不再注册按键。
我在 SDI 应用程序中完成了大部分测试,尽管 SCBDemo 似乎也能工作(一个 MDI 应用程序)。无疑会存在我遗漏的问题或故障。
关于示例的说明
为了方便起见,文档数据实际上存储在编辑视图中。如果您有多个视图,理想情况下数据应存储在文档对象中,而不是视图中。
该示例仅用于说明其有效性。它并非旨在成为一个复杂的应用程序。处理多个视图和多种类型的视图来处理多个文档非常复杂,并且涉及许多问题。
ViewBar 上的“附加到文档”按钮会附加到当前活动的文档。如果您想做得更花哨,可以提供一个下拉列表,就像在“新建文件”中一样。
结论
这提供了一种相对简单的方法将视图集成到可调整大小的控制条中。当条形图浮动时,菜单的激活可以进一步改进。