How to put CFormView on CDockableView

At StackOverflow, there is an inquiry,
How can I place a MFC CFormView inside a CDockablePane?

It is very popular question on the net. Try searching “CFormView on CDockablePane”, “How to put CFormView in CDockableView”, “CView on CDockableView” and so on. ( If CView can be on CDockablePane, CFormView can be too, because CFormView is a child class of CView. )
However I didn’t find anyone who was successful in doing so.

CDockablePane is part of MFC Feature Pack. Although MS distributes it as a part of Visual Studio Service Pack 1, but it was not made by MS. It was made by BCGsoft. I think the author did quite amazing job. However, as he said in the forum, it doesn’t support hosting of CView and its derivatives. One of the reason is said to be routing of window message. If you check all the CDockablePane related classes and all the panes on Visual Studio carefully, you will notice that all the widget on CDockablePane are controls not views.

(This is significant difference between Cocoa and MFC. With Cocoa NSView derivatives can be on any NSWindow/NSView. Actually NSView is derivative of NSWindow on Mac and vice versa on iPhone.

However on Windows, CView and its derivatives are not designed to be on CDialog and so on. So, that is why there are separate CView derivatives like CEditView, CListView, CRichEditView, CFormView, CHtmlEditView, CHtmlView, CTreeView and controls like CEdit, CListCtrl, CTreeCtrl. On dialog boxes, CView derivatives are not supposed to be put.

On Mac, on the other hand, any NSView derivatives can be put on any window and any NSView derivatives. Although NSOutlineView ( or whatever ) which contains a tree controller inside can be seen like the relationship between CListView and CListCtrl, but they are totally different. You use CListCtrl to put it on a dialog box, while you can make a new document window or some window as CListView with its own frame window. Also C*View utilizes the document template architecture, while *Ctrl don’t. However, on a Mac, to use tree control, COutlineView is used instead of its tree control. You can make your own view using the tree control, but if you want to use them as they are, you don’t need to handle the controls directly.

This makes GUI programming very easy on Mac. Very flexible, very powerful and very clean source codes are what you get.

In Cocoa Head LA, I was asked by others why I thought Mac was better than Windows. I would like to explain all this kind of things, but I couldn’t. First, I’m not good at casual talking in English. Second, they are subtle, so I couldn’t think of good example on MFC and Cocoa to compare. Once I run into this kind of issue, I can say “Ah.. this is it!”.

There is another good thing on Cocoa. With Interface Builder, you can see the containment of views visually. You will know how stressful to check someone else’s code which deals with GUI element connected as parent-child relationship a lot and to figure out what causes malfunctioning of the code. )

Anyway, lets go back to original topic.
However, it doesn’t mean that you CANNOT put CView derivatives on CDockablePane.

Here is how to.

For CMainFrame

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CMDIFrameWndEx::OnCreate(lpCreateStruct) == -1)
		return -1;

	// enable Visual Studio 2005 style docking window behavior
	CDockingManager::SetDockingMode(DT_SMART);
	// enable Visual Studio 2005 style docking window auto-hide behavior
	EnableAutoHidePanes(CBRS_ALIGN_ANY);

	// create docking windows
	if (!CreateDockingWindows())
	{
		TRACE0("Failed to create docking windows\n");
		return -1;
	}

	// JongAm
	m_myFormViewPane.EnableDocking( CBRS_ALIGN_ANY );
	DockPane( &m_myFormViewPane );
	////////

	return 0;
}

BOOL CMainFrame::CreateDockingWindows()
{
	BOOL bNameValid;

	// JongAm
	// Create MyFormViewPane

	CString strMyFormViewPane;
	bNameValid = strMyFormViewPane.LoadStringW( IDS_MYFORMVIEW_PANE );
	ASSERT( strMyFormViewPane );
	if( !m_myFormViewPane.Create( strMyFormViewPane, this, CRect( 0, 0, 205, 157 ), TRUE,
								  ID_FILE_NEWFORMVIEW,
								  WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN |
								  CBRS_BOTTOM | CBRS_FLOAT_MULTI ) )
	{
		TRACE0( "Failed to create My Form View Pane" );
		return -1;
	}

	////////////

	SetDockingWindowIcons(theApp.m_bHiColorIcons);
	return TRUE;
}

Now for the Dockable pane

CDockableFormViewPane::CDockableFormViewPane()
: m_pMyFormView(NULL)
{
}

int CDockableFormViewPane::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CDockablePane::OnCreate(lpCreateStruct) == -1)
		return -1;

	// TODO:  Add your specialized creation code here

	m_pMyFormView = CMyFormView::CreateOne(this);

	return 0;
}

void CDockableFormViewPane::OnSize(UINT nType, int cx, int cy)
{
	CDockablePane::OnSize(nType, cx, cy);

	// TODO: Add your message handler code here

	if( m_pMyFormView )
		m_pMyFormView->SetWindowPos( NULL, 0, 0, cx, cy, SWP_NOZORDER );
}

And finally for CFormView

void CMyFormView::OnInitialUpdate()
{
	CFormView::OnInitialUpdate();

	// TODO: Add your specialized code here and/or call the base class
	GetParentFrame()->RecalcLayout();
	SetScrollSizes( MM_TEXT, CSize( 205, 157 ) );

	ResizeParentToFit(FALSE);
}

CMyFormView *CMyFormView::CreateOne( CWnd *pParent )
{
	CMyFormView *pFormView = new CMyFormView;
	//CMyFormView *pFormView = NULL;
	//CRuntimeClass *pRuntimeClass = RUNTIME_CLASS(CMyFormView);
	//pFormView = (CMyFormView *)pRuntimeClass->CreateObject();

	CDockableFormViewAppDoc *pDoc = CDockableFormViewAppDoc::CreateOne();
	pFormView->m_pDocument = pDoc;

	CCreateContext *pContext = NULL;

#if 0
	if( !pFormView->CreateEx(0, NULL, NULL, WS_CHILD | WS_VISIBLE, CRect(0,0,205,157),
		pParent, -1, pContext ) )
#else
	if (!pFormView->Create(NULL, NULL, WS_CHILD | WS_VISIBLE, CRect(0, 0, 205, 157), pParent, 0, pContext))
#endif
	//if( !pFormView->CreateEx( 0, AfxRegisterWndClass(0, 0, 0, 0), NULL,
	//	WS_CHILD | WS_VISIBLE, CRect( 0, 0, 205, 157), pParent, -1, pContext) )
	{
		AfxMessageBox( _T("Failed in creating CMyFormView") );
	}

	pFormView->OnInitialUpdate();

	return pFormView;
}

int CMyFormView::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message)
{
	// TODO: Add your message handler code here and/or call default

	int nResult = 0;

	CFrameWnd* pParentFrame = GetParentFrame();

	if( pParentFrame == pDesktopWnd )
	{
		// When this is docked
		nResult = CFormView::OnMouseActivate(pDesktopWnd, nHitTest, message);
	}
	else
	{
		// When this is not docked

		BOOL isMiniFrameWnd = pDesktopWnd->IsKindOf( RUNTIME_CLASS( CMiniFrameWnd ) );
		BOOL isPaneFrameWnd = pDesktopWnd->IsKindOf( RUNTIME_CLASS( CPaneFrameWnd ) );
		BOOL isMultiPaneFrameWnd = pDesktopWnd->IsKindOf( RUNTIME_CLASS( CMultiPaneFrameWnd ) );

		// pDesktopWnd is the frame window for CDockablePane

		nResult = CWnd::OnMouseActivate( pDesktopWnd, nHitTest, message );

		//nResult = CWnd::OnMouseActivate( pDesktopWnd, nHitTest, message );
		//if( nResult == MA_NOACTIVATE || nResult == MA_NOACTIVATEANDEAT )
		//	return nResult;

		//if (pDesktopWnd != NULL)
		//{
		//	// either re-activate the current view, or set this view to be active
		//	//CView* pView = pDesktopWnd->GetActiveView();
		//	//HWND hWndFocus = ::GetFocus();
		//	//if (pView == this &&
		//	//	m_hWnd != hWndFocus && !::IsChild(m_hWnd, hWndFocus))
		//	//{
		//	//	// re-activate this view
		//	//	OnActivateView(TRUE, this, this);
		//	//}
		//	//else
		//	//{
		//	//	// activate this view
		//	//	pDesktopWnd->SetActiveView(this);
		//	//}
		//}
	}

	return nResult;
}

However, as the author of MFC Feature Pack mentioned, there are many issues about windows message routing. For example, OnMouseActivate() should be customized to prevent crash when you click a mouse button when the dockable pane on which CFormView resides is floating. Also, if the View on the dockable pane has more complicated structure like CSplitView, which contains multiple GUI widget which are needed to be updated to refresh their content, it becomes very complicated.

Currently I try to solve such problems.
However, here I at least showed how to put CFormView/View on CDockablePane. I hope this can save many people’s time to investigate how to solve this issue.

ADDED : Because one guy asked me how to implement the CreateOne() member function, I added it above and its header file here.

#pragma once

// CMyFormView form view

class CDockableFormViewAppDoc;

class CMyFormView : public CFormView
{
	DECLARE_DYNCREATE(CMyFormView)

public:
	CMyFormView();           // protected constructor used by dynamic creation
	virtual ~CMyFormView();

	static CMyFormView *CreateOne( CWnd *pParent );

	// return associated document
	CDockableFormViewAppDoc * GetDocument(void);

public:
	enum { IDD = IDD_CONTENT_PANE };
#ifdef _DEBUG
	virtual void AssertValid() const;
#ifndef _WIN32_WCE
	virtual void Dump(CDumpContext& dc) const;
#endif
#endif

protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

	DECLARE_MESSAGE_MAP()
	// points to linked document
	CDockableFormViewAppDoc *m_pDocument;

public:
	virtual void OnInitialUpdate();
	afx_msg int OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message);
	afx_msg void OnSizing(UINT fwSide, LPRECT pRect);
};

Sources : DockableFormViewApp-Modified.zip

16 responses to this post.

  1. Posted by KM on August 26, 2010 at 4:30 PM

    Hi.
    Very helpful post!

    But I want to know about CDockableFormViewAppDoc.

    I have to make new CMDIDoc class for this formview??

    // return associated document
    CDockableFormViewAppDoc * GetDocument(void);

    What does this line mean??

    and How to make this Doc?

    CDockableFormViewAppDoc *pDoc = CDockableFormViewAppDoc::CreateOne();

    Please give me some advice.

    Reply

    • Posted by jongampark on August 26, 2010 at 6:29 PM

      You can make a new document just like the usual MFC multitemplate Document-View-Frame mechanism. Just download the linked project and play with it. Then you will be able to figure it out.

      Reply

      • Posted by KM on August 26, 2010 at 10:46 PM

        THANK YOU VERY MUCH!!!

        I spent too much time to solve docakblepane with formview problem.

        I really appreciate your post and source code.

        thanks a lot.

        Reply

  2. Posted by Selvaraj on September 21, 2011 at 2:39 AM

    Yes it is good solution to solve the crash.

    I am facing some more problems, that is, main menu is not active, but the context menu in CFormview is working properly.

    Reply

    • Posted by jongampark on September 22, 2011 at 12:20 PM

      Hello. I don’t know why you want to put CFormView on a CDockableView, but if possible don’t do it.
      In MFC, any *Views are not expected to be in any other *Views.
      If you take a look at MFC carefully, you will find out that there are one pair of similar classes, e.g. one for control and the other for view.
      Controls are supposed to be put in a view.

      Why I posted this article was that my manager wanted me to do it. Although he has been a long time MFC programmer, but he didn’t understand a philosophy behind the MFC framework. ( Actually he didn’t understand what framework is. He will argue that he understand what it is, but when I talked with him, I found out that he just liked to use jargon to give ignorant higher managers that he was very technical. )
      I could convert existing codes with controls, but there was one thing not clear at that moment. I was not sure if new design would break existing behavior at that moment. The original codes which were written long time back was not written by someone who understood MFC correctly. So, although he wrote “working” codes, but it didn’t work properly. The existing structure looked awfully and unnecessarily complicated, and my manager didn’t give me time to think about it. Somehow he prevented me from saying even one word about MFC. So, I had to put the CFormView on a CDockableView, which was his idea. After a while, I personally tried to convert it in my own way, and it took about 1 day and a half with cleaner and shorter codes. His attitude was bold and thought like he knew more than others about MFC, which was not true. ( Anyway, two good programmers, technically and in their personality left because of him. )

      Remember this : writing working codes is not OK. Writing codes which follows philosophy pursued by the framework is important. ( It should work also, though. ) If possible, avoid putting a view on another view. Even the guy who implemented the notorious ribbon bar of current MS products ( actually he was the guy who showed how to put a view in a view. If you search with “ribbon bar” etc, then you will be able to trace back to his initial project. ) didn’t recommend to do so.

      Reply

  3. Great post: solved my mouse problem with a floating CView.
    Thanks.

    Reply

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: