There’s no leaving here; ask, I’m an ear

There?s a great deal of existing rendering code built on old versions of Microsoft’s Foundation Class (MFC) model. In the old object model, you had a single CFrameWnd, a single CDocument, and one or more CView classes that were owned by the CFrameWnd class. These objects share message routing responsibilities with one another, and a great deal of code has been written with these assumptions.

Fast forward twenty or so years, and these old assumptions, upon which most of MFC was originally architected, are incompatible with the complexity of modern user interface design, and even a good deal of modern-day MFC itself. There can be multiple documents, controls floating and tabbed which may or may not be children of the CFrameWnd.

The funky new display and interface features like CDockablePane and CMFCToolBar don’t know anything about the CView class, and they don’t inherit from CView. It’s hard to upgrade an older application written based on the old MFC document-view model to dockable panes.

It?s a bit of work, but here’s one way to accomplish this without relying on proprietary libraries. The basic strategy we use is to change the old CView-based classes to be a subclass of CDockablePane, and then make the new class support enough of the major CView features in order to handle update rendering, commands, OLE drag and drop, and user-interface updates via the old interfaces in your old class.

In order to convert your own subclass of CView into a CDockablePane subclass, here’s what you need to do:

1. Create a new subclass of CDockablePane (we’ll call it CYourDockablePane) from which all your old CViews will inherit.
2. Implement update message handling and OLE support from your CYourDockablePane subclass.
3. Subclass COleDropTarget into a new class which we’ll call CYourOleDropTarget in order to handle OLE drag-and-drop in the manner that CView does.
4. Add features to your C*FrameWnd* class to support window updating and command message routing to the new panes.

 


 

YourDockablePane.h:

//------------------------------------------------------------------
#pragma once

#include "YourOleDropTarget.h"

class CYourDockablePane : public CDockablePane
{
	DECLARE_DYNCREATE(CYourDockablePane)

public:
	CYourDockablePane();
	virtual ~CYourDockablePane();

	// These are needed to get around OnUpdate()?s protected status
	virtual BOOL ReceiveNotification(WPARAM wParam, LPARAM lParam, LRESULT* pResult);
	virtual void OnUpdate( CView *pSender, LPARAM lHint, CObject *pHint );

	// Simulate CView OLE drag and drop effects
	virtual DROPEFFECT	OnDragEnter( COleDataObject *pDataObject, DWORD dwKeyState, CPoint point );
	virtual void		OnDragLeave( void );
	virtual DROPEFFECT	OnDragOver( COleDataObject *pDataObject, DWORD dwKeyState, CPoint point );
	virtual DROPEFFECT	OnDropEx( COleDataObject *pDataObject, DROPEFFECT dropDefault, DROPEFFECT dropList, CPoint point );
	virtual DROPEFFECT	OnDragScroll( DWORD dwKeyState, CPoint point);
	virtual BOOL        OnDrop( COleDataObject* pDataObject,	DROPEFFECT dropEffect, CPoint point);

protected:
	DECLARE_MESSAGE_MAP()

	// View classes know how to do this
	virtual CYourDocument *GetDocument(void) const;
	CYourOleDropTarget	m_OleDropTarget;

public:

};



YourDockablePane.cpp:
/---------------------------------------------------------------------
// YourDockablePane.cpp : implementation file

#include "stdafx.h"
#include "YourDockablePane.h"

IMPLEMENT_DYNCREATE(CYourDockablePane, CDockablePane)

CYourDockablePane::CYourDockablePane()
{

}

CYourDockablePane::~CYourDockablePane()
{
}


BEGIN_MESSAGE_MAP(CYourDockablePane, CDockablePane)
END_MESSAGE_MAP()

CYourDocument * CYourDockablePane::GetDocument( void ) const
{
	CDocTemplate* pDocTemplate;
	POSITION pos;
	pos = AfxGetApp()->GetFirstDocTemplatePosition();
	pDocTemplate = AfxGetApp()->GetNextDocTemplate(pos);
	pos = pDocTemplate->GetFirstDocPosition();
	return (CYourDocument*) pDocTemplate->GetNextDoc(pos);
}

BOOL CYourDockablePane::ReceiveNotification( WPARAM wParam, LPARAM lParam, LRESULT* pResult )
{
	return OnNotify( wParam, lParam, pResult );
}

DROPEFFECT CYourDockablePane::OnDragEnter( COleDataObject *pDataObject, DWORD dwKeyState, CPoint point )
{
	return DROPEFFECT_NONE;
}

void CYourDockablePane::OnDragLeave( void )
{

}

DROPEFFECT CYourDockablePane::OnDragOver( COleDataObject *pDataObject, DWORD dwKeyState, CPoint point )
{
	return DROPEFFECT_NONE;
}

DROPEFFECT CYourDockablePane::OnDropEx( COleDataObject *pDataObject, DROPEFFECT dropDefault, DROPEFFECT dropList, CPoint point )
{
	return DROPEFFECT_NONE;
}

DROPEFFECT CYourDockablePane::OnDragScroll( DWORD dwKeyState, CPoint point )
{
	return DROPEFFECT_NONE;
}

BOOL CYourDockablePane::OnDrop( COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point )
{
	return false;
}

void CYourDockablePane::OnUpdate( CView *pSender, LPARAM lHint, CObject *pHint )
{

}



YourOleDropTarget.h:
/------------------------------------------------------------------------
#pragma once

class CYourDockablePane;

// CYourOleDropTarget command target

class CYourOleDropTarget : public COleDropTarget
{
	DECLARE_DYNAMIC(CYourOleDropTarget)

public:
	CYourOleDropTarget();
	virtual ~CYourOleDropTarget();
	BOOL Register(CWnd* pWnd);

protected:
	CYourDockablePane *m_pPane;
	DECLARE_MESSAGE_MAP()
public:
	virtual DROPEFFECT OnDragEnter(CWnd* pWnd, COleDataObject* pDataObject,
		DWORD dwKeyState, CPoint point);
	virtual DROPEFFECT OnDragOver(CWnd* pWnd, COleDataObject* pDataObject,
		DWORD dwKeyState, CPoint point);
	virtual BOOL OnDrop(CWnd* pWnd, COleDataObject* pDataObject,
		DROPEFFECT dropEffect, CPoint point);
	virtual DROPEFFECT OnDropEx(CWnd* pWnd, COleDataObject* pDataObject,
		DROPEFFECT dropDefault, DROPEFFECT dropList, CPoint point);
	virtual void OnDragLeave(CWnd* pWnd);
	virtual DROPEFFECT OnDragScroll(CWnd* pWnd, DWORD dwKeyState,
		CPoint point);

};



YourOleDropTarget.cpp:
/--------------------------------------------------------------
// YourOleDropTarget.cpp : implementation file
//

#include "stdafx.h"
#include "YourDockablePane.h"
#include "YourOleDropTarget.h"

// CYourOleDropTarget

IMPLEMENT_DYNAMIC(CYourOleDropTarget, COleDropTarget)


CYourOleDropTarget::CYourOleDropTarget() :
	m_pPane( NULL )
{
}

CYourOleDropTarget::~CYourOleDropTarget()
{
}

BEGIN_MESSAGE_MAP(CYourOleDropTarget, COleDropTarget)
END_MESSAGE_MAP()

BOOL CYourOleDropTarget::Register( CWnd* pWnd )
{
	m_pPane = dynamic_cast< CYourDockablePane * >( pWnd );
	return COleDropTarget::Register( pWnd );
}


// CYourOleDropTarget message handlers

DROPEFFECT CYourOleDropTarget::OnDragEnter(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
{
	return m_pPane->OnDragEnter( pDataObject, dwKeyState, point);
}

void CYourOleDropTarget::OnDragLeave(CWnd* pWnd)
{
	m_pPane->OnDragLeave();
}

DROPEFFECT CYourOleDropTarget::OnDragOver(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
{
	return m_pPane->OnDragOver( pDataObject, dwKeyState, point);
}

DROPEFFECT CYourOleDropTarget::OnDragScroll(CWnd* pWnd, DWORD dwKeyState, CPoint point)
{
	return m_pPane->OnDragScroll( dwKeyState, point);
}

BOOL CYourOleDropTarget::OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
{
	return m_pPane->OnDrop( pDataObject, dropEffect, point);
}

DROPEFFECT CYourOleDropTarget::OnDropEx( CWnd *pWnd, 
							COleDataObject* pDataObject,
							DROPEFFECT dropDefault,
							DROPEFFECT dropList,
							CPoint point 
							)
{
	DROPEFFECT effect;
	
	effect = m_pPane->OnDropEx( pDataObject, dropDefault, dropList, point );

	if ( effect == DROPEFFECT_NONE )
		return m_pPane->OnDrop( pDataObject, dropDefault, point );

	return effect;
}



Add this snippet to the definition file (the .h file) of your application?s CFrameWnd or CFrameWndEx subclass:
. . .
		typedef list< CPane *> CYourPanes;
		typedef CYourPanes::iterator  CYourPaneIter;

		typedef list< CYourDockablePane *> CYourDockablePanes;
		typedef CYourDockablePanes::iterator CYourDockablePanesIter;

		CYourPanes			m_Panes;
		CYourDockablePanes 	m_YourDockablePanes;

Add this to the implementation file (the .cpp file) of your application's CFrameWnd or CFrameWndEx subclass, changing the CYourFrameWnd class name to the name of your frame window subclass:
void CYourFrameWnd::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint )
{
	for ( CYourDockablePanesIter it = m_YourDockablePanes.begin(); 
		it != m_YourDockablePanes.end(); it++ )
       {
		(*it)->OnUpdate( pSender, lHint, pHint );
	}
}
BOOL CYourFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
	/* We intercept all the commands sent to the frame here, and give all 
	 * our child panes first crack at handling them.
	 */
	for ( CYourPaneIter it = m_Panes.begin(); it != m_Panes.end(); it++ )
	{
         	if ( (*it)->OnCmdMsg( nID, nCode, pExtra, pHandlerInfo ))
			return true;
	}

	return CFrameWndEx::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
BOOL CYourFrameWnd::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
	for ( CYourDockablePanesIter it = m_YourDockablePanes.begin(); it != m_YourDockablePanes.end(); it++ )
	{
		if ( (*it)->ReceiveNotification( wParam, lParam, pResult ) )
			return true;
	}

	return CFrameWndEx::OnNotify(wParam, lParam, pResult);
}
At this point, you should search through your old CView class to find any references to a COleDropTarget. These references should be changed to refer to the m_OleDropTarget member that you put within the CYourDockablePane class.

A couple final things you need to do: inform your main frame class about the locations of your CYourDockablePane-derived classes, as well as your toolbars and other non-view features, which are generally children of the CPane class.

Wherever you instantiate and call Create() on your CYourDockablePane descendant, add it to your list of CYourDockablePanes:

  m_YourDockablePanes.push_back( &yourNewlyCreatedDockablePane );

And wherever you instantiate and Create() toolbars or other things that need to process commands or UI updates, including your dockable panes, add them to the list of CPanes as well:

  m_Panes.push_back( &yourNewlyCreatedControlOrPane );

This basic strategy can be used to convert old CView-based classes into any modern control-based user interface architecture, or merely to have your classes derive from CWnd directly.

There may be other side effects of this class conversion that I’ve not considered. Please feel free to modify the above code to your specific situation.

Information and code from this blog entry may be reused and redistributed under the following license.

Creative Commons License
There’s no leaving here; ask, I’m an ear by John Byrd is licensed under a Creative Commons Attribution 3.0 United States License.
Based on a work at www.johnbyrd.org.

4 thoughts on “There’s no leaving here; ask, I’m an ear

  1. What about Updating Mechanism?
    Who calls CYourFrameWnd::OnUpdate so that the Views are updated and what about OnInitialUpdate?

Leave a Reply