And I’m so sad, like a good book

Recently, I’ve been reading Grimm Fairy Tales to get to sleep. Here’s a new story for you in their style. Like all fairy tales, half is new and two-thirds is borrowed.

“Pasalo and Shala”

There was a handsome and hardworking farm boy. Because his hair was bright as gold, he was called Pasalo (“gold-headed boy”). Pasalo woke one morning to hear beautiful singing. As he went to the road, he saw a pretty girl singing while she carried pails of milk to town. Pasalo went to her. Her name was Shala (“nightingale”), and her pails were heavy, and she sang to lighten the load. Pasalo carried her pails for her. After walking a mile or two, they fell in love, and Pasalo promised to marry Shala.

Whereupon an enchantress swooped down upon them, bearing a gilded bird cage. She had heard Shala singing too. The enchantress raised her dark cloak and turned Shala into a nightingale; and the enchantress stole the nightingale into her cage. The enchantress cursed Pasalo, and he was frozen as stone, and could not move. The enchantress flew away, and Pasalo collapsed, free of the curse.

“I will bring my love back,” thought Pasalo. He got his pitchfork and went to the great dark castle on the hill where the enchantress lived. Through the top window he could hear the nightingale singing in the gilded cage. A great gargoyle perched on top of the castle. Pasalo attacked it with the pitchfork. The gargoyle laughed, snapped the pitchfork easily between his teeth, and pressed Pasalo to the ground with one claw. The gargoyle said, “You may not enter the castle unless you show me the most beautiful thing in the world.” He let Pasalo up. Sad and beaten, Pasalo walked down the hill.

As Pasalo returned to his farm, he spied a wise old fox, fainted from pain and hunger. “The enchantress has injured me,” said the fox. “Heal me, and I will help you.” Pasalo fed the fox chicken and let him heal in his bed. When the fox was well, the fox bowed and said, “How may your love be returned?”

Pasalo said, “The gargoyle said, I may not enter the castle unless I show it the most beautiful thing in the world.”

“Ah,” said the fox. “It has never been done before, but it may be done yet. You must first find the place where water turns to air. Then, you must pass the unpassable door. There, you will find the most beautiful thing in the world.” Pasalo asked the fox what he meant, but the fox sprang out the window and ran away.

For many years, Pasalo searched for a place where water turns to air. He searched many countries, many towns and many forests, but he could find no such place. He endured many misfortunes, and his hair turned from gold to silver and then to gold again with the effort. At last, when he could go no further, he collapsed by the top of a waterfall. “I shall never find the place where the water turns to air,” said Pasalo.

Pasalo looked over the cliff, and found that he had walked all the way to the edge of the earth. Pasalo watched as the river drifted off the earth, and the water cascaded into infinite sky, and the river came to reform as clouds above the earth. “Is this the place the fox spoke of?” said Pasalo.

Pasalo was dry, so he put his face in the river to drink. As he did so, underneath the surface of the water, he could see clearly. Upside down, he saw an underground cave. He climbed through the water’s surface, pulled himself onto a ledge, and stood. Pasalo saw an underground passageway. A river flowed at his feet, the sun shone brightly through the river, and the light danced on the cavern ceiling.

“I can breathe in here,” said Pasalo, “and thus have I seen water turn to air!” Pasalo followed the river of light until he came to a small shore of sand before a great hewn wall. There was a single door in the wall. On the door, in red, were the following letters:

THE UNPASSABLE DOOR.

“Not unpassable for me,” said Pasalo, but though he beat, shoved and pried at the door, it would not move a bit. Pasalo slumped on the sand, dejected. As he sat on the sand, he could see a faint light under the door. Pasalo said, “If I might not pass through it, I might pass under it,” and dug sand from under the door. At last he removed enough that he could slide himself underneath the door.

When Pasalo emerged, he saw a vast and rich treasure room with every imaginable kind of wealth: rubies, emeralds and diamonds as big as porcupines, beds and sheets and linens spun of pure gold, encrusted crowns and swords and staves, each one more exquisite than the last. “Everything here is a wonder, and there are so many wonders here,” said Pasalo. “How can I possibly choose the most beautiful thing?” As he said this he spied a single tulip growing in a patch in the corner. A single droplet of dew sparkled from the center of the tulip. As Pasalo took the flower, a spell lifted and all the jewels and fineries were revealed as rocks and wood.

“All my treasures are vanished,” said Pasalo, “but at least I have this.” Pasalo went back under the impassable door, through the river of light at the edge of the world, and over many many miles to the dark castle on the hill.

The gargoyle glowered at him. “You may not enter the castle unless you show me the most beautiful thing in the world.”

Pasalo showed the beautiful tulip with the droplet of dew. The gargoyle laughed and said, “I tell you to show me the most beautiful thing in the world, and you show me a simple common flower?”

Pasalo said, “No. I have travelled far and wide over many years, and I have seen the edge of the earth. I have traversed the place where water turns to air, and passed the impassable door. I have rejected every earthly treasure to find this tulip and bring it here, but not to you. Please give it to Shala the nightingale on my behalf, and tell her that I love her, and always will.” Whereupon the gargoyle bowed deeply and permitted Pasalo to enter.

Pasalo ran to the top window. He saw the dark enchantress dangle the gilded cage out of the window with one arm, with Shala the nightingale therein. “If you come at me, I’ll dash your nightingale to the rocks below,” said the enchantress, “and that will be the end of your true love.” Whereupon, from outside the window, the gargoyle seized the cage and the enchantress’s arm with it, and pulled them both outside the window. The enchantress swang from the teeth of the gargoyle. The gargoyle gave a small chomp, and the enchantress (less one arm) fell twenty stories to the sharp rocks below. As she died, the sun lifted, and roses climbed the walls of the castle.

Pasalo despaired of Shala the nightingale in the jaws of the gargoyle, when the gargoyle placed his great head in the window and smiled. Through his teeth stepped Shala. The curse was lifted, and she was as beautiful as the day when Pasalo first saw her. They were married and lived happily in the castle with the great gargoyle guarding them; the last I heard, the three live there still.

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.