Download source files - 47 Kb
I like Cristi Posea's implementation of sizing docking control bars and decided to use them in a project I am working on. Unfortunately, I really needed to use them with views, and set about investigating this. I read in one of the comments that a few people have tried using a frame window to aid the MFC framework in order to use views. I decided to see how hard it would be. For what I need it for it turns out to be relatively easy.
Note I'm not describing how to deal with multiple views on multiple documents and how to link these together and manage them. This is a complex topic and highly application dependant and I feel it is better left to the particular application to resolve. (The example does allow the views to be linked to different documents).
What I am explaining is a way of incorporating a view into a sizing control bar object.
Here are the steps:
Step 1
I created a new CViewBar class that inherits from one of the CSizingControlBar classes. (#define TViewBarBase to the base class you need). This class encapsulates the frame window so you don't have to know it even exists. (A frame window is just needed to support MFC, it doesn't matter what kind.) The creation and sizing are handled within the class.
Also taken care of, is specification of the view class in the create member function. I just let MFC do all the work by passing the class type through to it. That way you don't have to derive eight billion different classes just to use a docking view. This leads to my next step.
#ifndef VIEWBAR_H
#define VIEWBAR_H
#if _MSC_VER > 1000
#pragma once
#endif
#include "sizecbar.h"
#include "scbarg.h"
#include "scbarcf.h"
#define TViewBarBase CSizingControlBarCF
class CViewBar : public TViewBarBase
{
DECLARE_DYNAMIC(CViewBar);
public:
CViewBar();
virtual ~CViewBar();
virtual BOOL Create(
CWnd* pParentWnd,
CRuntimeClass *pViewClass,
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;
public:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnSize(UINT nType, int cx, int cy);
DECLARE_MESSAGE_MAP()
};
#endif
#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);
CViewBar::CViewBar()
{
ZeroMemory(&m_Context, sizeof(m_Context));
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)
ON_WM_CREATE()
ON_WM_SIZE()
END_MESSAGE_MAP()
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);
}
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;
}
void CViewBar::OnSize(UINT nType, int cx, int cy)
{
CRect rc;
TViewBarBase::OnSize(nType, cx, cy);
GetClientRect(rc);
m_pFrameWnd->MoveWindow(rc);
}
An aside:
Ho, humm. Don't you wish there were some way to represent the direct base class rather than having to specify it explicitly ?
Something like 'base::' would be handy. You could write code more generically. Sure, you could use templates but that is more complex than I like.
Step 2
How to create the view: Do the obvious thing and pass it through to MFC and let it do all the work. View information can be passed to MFC using the CCreateContext object. This is passed to many of the window creation functions in MFC. MFC uses this when creating a view. Unfortunately, this create context is not passed in the original sizing control bar create, meaning a little subterfuge is required (unless you want to modify the original class). Consequently, a CCreateContext object is maintained in the ViewBar class simply for the sake of not modifying the base class.
It's needed to be passed when frame window Create() is called. If you don't like this, you can modify the CSizingControlBar::Create function to accept a CCreateContext. However, since it was possible to avoid, I avoided it. This way my class is decoupled from the original, meaning if the original author decides to do an update I'll have a much easier time updating my software. And I, like all good programmers am incredibly lazy.
To create a ViewBar in you app use code like the following: (see the sample)
sTitle.Format(_T("My Bar %d"), i + 1);
if (!m_wndMyBars[i].Create(this,
RUNTIME_CLASS (CAForm),
(CCreateContext *)(lpCreateStruct->lpCreateParams),
sTitle))
{
TRACE0("Failed to create ViewBar\n");
return -1;
}
Aside from the create function, it can be used exactly like the existing sizing control bar classes. You need only pass the RUNTIME_CLASS of the view you want to create.
Step 3
At this point the modifications to support views seemed to work, except for one niggly issue - menu access when the view is floating. (Even accelerator keys work!) For various reasons this is tricky to implement.
Ultimately you have to save and restore which window had the focus before the menu was activated. This is needed in case the user hits the escape key. (Why Windows / MFC doesn't do this automatically is beyond me. Where else would you set it ?) Which window is active after a menu command is completed is entirely application dependant.
To activate the main menu from the view while it's floating, override the PreTranslateMessage() function of the view and set the focus back to the main window when a menu keystroke is detected. Like this:
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);
}
Don't try and put this line of code into the PreTranslateMessage() for the control bar class, it would be nice if you could, but that doesn't work. Somewhere along the way the SYSKEY messages get eaten up.
I didn't bother to derive a special class to handle this operation for several reasons:
- Which view class do I derive from ? It would be silly to mirror the existing MFC view classes accomodate only a single line of code.
- You are probably deriving your own view class anyway, so it's relatively easy to place this code in the class.
- You might not care about being able to access the main menu when the view is floating.
*Note: you can't use CWnd pointer to track the focus and restore it with GetFocus() and SetFocus(). You must use ::Setfocus(), ::GetFocus() and track the Windows handle.
Put the following code in the CMainFrame class for the app. It restores the window that was active before escape was pressed.
Also, in a similar fashion, you will need to add code to any menu command handlers that you want to return to a floating window if it was active.
void CMainFrame::OnExitMenuLoop( BOOL bIsTrackPopupMenu )
{
if (((CSCBDemoApp *)AfxGetApp())->m_nLastKey == VK_ESCAPE) {
if (m_hWndBeforeMenu) {
::SetFocus(m_hWndBeforeMenu);
}
}
m_hWndBeforeMenu = NULL;
CWnd::OnExitMenuLoop(bIsTrackPopupMenu);
}
Notes
- This function is not avaiable for a frame window through AppWizard.
- Once you add this function to the class AppWizard freaks if you try to add more virtual functions or message handlers. So add it once you're done everything else. (At least that what happens in VC6.0 - some error about parsing...)
- If you're paranoid you'll think this was done on purpose.
Add the following code to track keystrokes for the app. (Needed to detect escape key). Add a UINT member var to the app class, m_nLastKey.
BOOL CSCBDemoApp::ProcessMessageFilter(int code, LPMSG lpMsg)
{
if (lpMsg->message == WM_KEYDOWN)
m_nLastKey = lpMsg->wParam;
return CWinApp::ProcessMessageFilter(code, lpMsg);
}
With this all done, there is still a slight anomaly in the way focus operates. Unfortunately a side effect of this mechanism is it automatically activates the window associated with the main frame window. If you have code that identifies the window as active (on GotFocus() for instance) this code will be activated, although the main menu is active. (Hitting escape deactivates this window again because escape will return focus to the original window).
Note that I think the menu handling is ugly as it is now, but it works for me. If there is a better way of doing this (I'm sure there is) I'd like to hear about it.
Other Notes:
You might think you can get away without using a frame window. I tried this and it almost works. It works for a regular form view but when I tried it with a CEditView derived class *poof* the edit view no longer registered keystrokes.
I've done most of my testing in an SDI application, although the SCBDemo does seem to work (an MDI app). Undoubtedly there will be problems or issues that I've missed.
Notes on the Example:
Data for the document is actually stored in the edit view for convenience. If you have multiple views, ideally the data should be stored in the document object, not a view.
The example is provided to simply illustrate that it works. It is not intended to be a sophisticated app. Handling multiple views and multiple types of views for multiple documents is quite complex, and there are a lot of issues involved.
The attach to document button on the ViewBar attaches to the currently active document. If you wanted to be fancy you could provide a dropdown list like on the file new.
Conclusion:
This provides a fairly simple way to incoporate views into sizing control bars. Menu actuation when the bar is floating could be improved upon.