Click here to Skip to main content
15,881,248 members
Articles / Desktop Programming / MFC

The SBJ MVC Framework - The Model, from Abstraction to Realization

Rate me:
Please Sign up or sign in to vote.
5.00/5 (19 votes)
20 Mar 2009CPOL19 min read 109K   1.3K   51  
A Model-View-Controller Framework that integrates with the MFC Doc/View architecture
//------------------------------------------------------------------------------
// $Workfile: TreeController.cpp $
// $Header: /SbjDev/SbjCore/TreeController.cpp 15    11/12/08 2:22p Steve $
//
//	Copyright � 2008 SbjCat
// All rights reserved.
//
//
// *** Authors ***
//	 Steve Johnson
//
// $Revision: 15 $
//
//-----------------------------------------------------------------------------

#include "StdAfx.h"

#include "resource.h"
#include "TreeController.h"
#include "TreeInserter.h"

#include "ControlledWndT.h"
#include "CmdMsgHandler.h"
#include "NotifyMsgHandler.h"
#include "WndMsgHandler.h"

#include "ItemDisplayModel.h"
#include "TreeItemController.h"
#include "EventT.h"

#include "TextSource.h"
#include "TextHandler.h"
#include "DropTarget.h"

#include "DocController.h"
#include "DocEvents.h"
#include "ModelEvents.h"

#include <map>

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

namespace localNS
{

	// Event Handlers /////////////////////////////////////////////////////
	
	class FileOpenEventHandler : public SbjCore::EventMgr::EventHandler
	{
		SbjCore::Mvc::TreeView::Controller* pTheCtrlr;
	public:
		FileOpenEventHandler() :
			SbjCore::EventMgr::EventHandler(SbjCore::Mvc::Doc::Events::EVID_FILE_OPEN)
		{
		}

		virtual ~FileOpenEventHandler()
		{
		}

		void SetCtrlr(SbjCore::Mvc::TreeView::Controller* p)
		{
			pTheCtrlr = p;
		}

	private:		
		virtual void OnHandle(SbjCore::EventMgr::Event* pEvent)
		{
			ASSERT(pTheCtrlr != NULL);
			if (pTheCtrlr != NULL)
			{
				SbjCore::Mvc::Doc::Events::FileOpen* pTheEvent = dynamic_cast<SbjCore::Mvc::Doc::Events::FileOpen*>(pEvent);
				const SbjCore::Mvc::Doc::Controller* pDocCtrlr = pTheEvent->pCtrlr;
				HANDLE hItemRoot = pTheEvent->hItemRoot;
				(void)pTheCtrlr->LoadTree(pDocCtrlr, hItemRoot);
			}
		}
	};

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

	class ItemInsertedEventHandler : public SbjCore::EventMgr::EventHandler
	{
		SbjCore::Mvc::TreeView::Controller* pTheCtrlr;
	public:
		ItemInsertedEventHandler() :
		  SbjCore::EventMgr::EventHandler(SbjCore::Mvc::Model::Events::EVID_ITEM_INSERTED)
		  {
		  }

		  void SetCtrlr(SbjCore::Mvc::TreeView::Controller* p)
		  {
			  pTheCtrlr = p;
		  }

	private:		
		virtual void OnHandle(SbjCore::EventMgr::Event* pEvent)
		{
			ASSERT(pTheCtrlr != NULL);
			if (pTheCtrlr != NULL)
			{
				SbjCore::Mvc::Model::Events::ItemInsert* pTheEvent = dynamic_cast<SbjCore::Mvc::Model::Events::ItemInsert*>(pEvent);
				const SbjCore::Mvc::Model::Controller* pModelCtrlr = pTheEvent->pCtrlr;
				HANDLE hChild = pTheEvent->hChild;
				HANDLE hParent = pTheEvent->hParent;
				HANDLE hAfter = pTheEvent->hAfter;
				HTREEITEM htiParent = TVI_ROOT;
				HTREEITEM htiAfter = TVI_LAST;
				
				SbjCore::Mvc::TreeView::ItemController* pItemCtrlr = pTheCtrlr->GetItemController(hParent);
				if (pItemCtrlr != NULL)
				{
					htiParent = pItemCtrlr->GetHandle();
				}
				if (hAfter != NULL)
				{
					CTreeCtrl* pTree = pTheCtrlr->GetTreeCtrl();
					pItemCtrlr = pTheCtrlr->GetItemController(hAfter);
					if (pItemCtrlr != NULL)
					{
						htiAfter = pItemCtrlr->GetHandle();
						htiAfter = pTree->GetPrevSiblingItem(htiAfter);
					}
				}
				(void)pTheCtrlr->InsertTree(pModelCtrlr, hChild, htiParent, htiAfter);
			}
		}
	};

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

	class ItemRemovedEventHandler : public SbjCore::EventMgr::EventHandler
	{
		SbjCore::Mvc::TreeView::Controller* pTheCtrlr;
	public:
		ItemRemovedEventHandler() :
		    SbjCore::EventMgr::EventHandler(SbjCore::Mvc::Model::Events::EVID_ITEM_REMOVED),
			pTheCtrlr(NULL)
		{
		}

		void SetCtrlr(SbjCore::Mvc::TreeView::Controller* pCtrlr)
		{
			pTheCtrlr = pCtrlr;
		}


	private:		
		virtual void OnHandle(SbjCore::EventMgr::Event* pEvent)
		{
			ASSERT(pTheCtrlr != NULL);
			SbjCore::Mvc::Model::Events::ItemRemove* pTheEvent = dynamic_cast<SbjCore::Mvc::Model::Events::ItemRemove*>(pEvent);
			ASSERT(pTheEvent != NULL);
			if (pTheEvent != NULL)
			{
				SbjCore::Mvc::TreeView::ItemController* pItem = pTheCtrlr->GetItemController(pTheEvent->hItem);
				if (pItem != NULL)
				{
					pTheCtrlr->DeleteItem(pItem);
				}

			}
		}
	};

	// Message Handlers /////////////////////////////////////////////////////

	class NMClickHandler : public SbjCore::Mvc::NotifyMsgHandler
	{
		virtual bool OnHandleNotify(NMHDR* pNMHDR, LRESULT* pResult)
		{
			pNMHDR;
			*pResult = 0;
			bool bRslt = true;

			SbjCore::Mvc::TreeView::Controller* pCtrlr = static_cast<SbjCore::Mvc::TreeView::Controller*>(GetController());

			if (pCtrlr != NULL)
			{
				CTreeCtrl* pTree = pCtrlr->GetTreeCtrl();

				DWORD pos = GetMessagePos();
				CPoint pt(LOWORD(pos), HIWORD(pos));
				pTree->ScreenToClient(&pt);

				UINT flags = 0;
				HTREEITEM hti = pTree->HitTest(pt, &flags);

				if (hti != NULL)
				{
					SbjCore::Mvc::TreeView::ItemController* pItemCtrlr = pCtrlr->GetItem(hti);
					ASSERT(pItemCtrlr != NULL);
					if (pItemCtrlr != NULL)
					{
						// all we do at this level is add selection when the state is changed
						if ((flags & TVHT_ONITEMSTATEICON) == TVHT_ONITEMSTATEICON)
						{
							pCtrlr->SelectItem(pItemCtrlr);
							UINT nStateItemIndex = pItemCtrlr->GetStateImage();
							nStateItemIndex = 0;   
						}
						bRslt = pItemCtrlr->HandleNotifyMsg(pNMHDR, pResult);
					}
				}

			}
			*pResult = 0; // continue with default

			return bRslt;	
		}
	};


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

	class NMRClickHandler : public SbjCore::Mvc::NotifyMsgHandler
	{
		virtual bool OnHandleNotify(NMHDR* pNMHDR, LRESULT* pResult)
		{
			pNMHDR;
			*pResult = 0;
			bool bRslt = false;

			SbjCore::Mvc::TreeView::Controller* pTheCtrlr = (SbjCore::Mvc::TreeView::Controller*)GetController();

			CTreeCtrl* pTree = pTheCtrlr->GetTreeCtrl();

			if (pTree != NULL)
			{
				DWORD dwPos = GetMessagePos();

				CPoint pt(LOWORD(dwPos), HIWORD(dwPos));
				CPoint ptClient(pt);

				pTree->ScreenToClient(&ptClient);

				UINT nFlags = 0;

				HTREEITEM hti = pTree->HitTest(ptClient, &nFlags);

				if (hti != NULL)
				{
					pTree->Select(hti, TVGN_CARET);
				}

				*pResult = pTree->SendMessage(WM_CONTEXTMENU, (WPARAM)pTree->GetSafeHwnd(), MAKELPARAM(pt.x, pt.y));

				bRslt = true;

			}

			return bRslt;	
		}
	};

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

	class RenameItemHandler : public SbjCore::Mvc::CmdMsgHandler
	{
		virtual bool OnHandleCmd(UINT nID)
		{
			nID;
			SbjCore::Mvc::TreeView::Controller* pTreeCtrlr = (SbjCore::Mvc::TreeView::Controller*)(GetController());

			CTreeCtrl* pTree = pTreeCtrlr->GetTreeCtrl();

			SbjCore::Mvc::TreeView::ItemController* pItemCtrlr = pTreeCtrlr->GetSelectedItem();

			if ((pItemCtrlr != NULL) && (pItemCtrlr->CanEdit()))
			{
				pTree->EditLabel(pItemCtrlr->GetHandle());
			}

			return true;
		}

		virtual bool OnCmdUI(CCmdUI* pCmdUI)
		{
			SbjCore::Mvc::TreeView::Controller* pTreeCtrlr = (SbjCore::Mvc::TreeView::Controller*)(GetController());

			CTreeCtrl* pTree = pTreeCtrlr->GetTreeCtrl();

			DWORD dwStyle = pTree->GetStyle();

			bool bCanEdit = ((dwStyle & TVS_EDITLABELS) == TVS_EDITLABELS);

			if (bCanEdit)
			{
				SbjCore::Mvc::TreeView::ItemController* pItemCtrlr = pTreeCtrlr->GetSelectedItem();

				bCanEdit = ((pItemCtrlr != NULL) && (pItemCtrlr->CanEdit()));
			}
			pCmdUI->Enable(bCanEdit);

			return true;
		}
	};


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

	/// CEdit subclass for the CTreeCtrl label edit so it will handle return and escape
	class LabelEdit : public SbjCore::Mvc::ControlledWndT<CEdit>
	{	  
		class EditController : public SbjCore::Mvc::WndController
		{
			CTreeCtrl* pTheTree;

			friend class KeyDownHandler;
			class KeyDownHandler : public SbjCore::Mvc::WndMsgHandler
			{
			public:
				KeyDownHandler()
				{
				}
			private:				

				virtual LRESULT OnHandleWndMsg(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
				{
					bool bRslt = false;
					lParam;
					*pResult = DLGC_WANTALLKEYS;
					EditController* pEditCtrlr = (EditController*)GetController();

					if ((wParam == VK_RETURN) || (wParam == VK_ESCAPE))
					{
						bool bCancel = (wParam == VK_ESCAPE);
						pEditCtrlr->pTheTree->SendMessage(TVM_ENDEDITLABELNOW, 0, (LPARAM)bCancel);
					}
					return bRslt;
				}

			} theKeyDownHandler;

		public:
			EditController()
			{ 
				AddHandler(WM_GETDLGCODE, &theKeyDownHandler);
			}
			void SetTreeCtrl(CTreeCtrl* p)
			{
				pTheTree = p;
			}

		} theCtrlr;
	public:
		LabelEdit()
		{
			SetController(&theCtrlr);
		}
		void SetTreeCtrl(CTreeCtrl* pTree)
		{
			theCtrlr.SetTreeCtrl(pTree);
		}
	};

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

	class BeginLabelEditHandler : public SbjCore::Mvc::NotifyMsgHandler
	{
		virtual bool OnHandleNotify(NMHDR* pNMHDR, LRESULT* pResult)
		{
			pNMHDR;
			*pResult = 1; // flag as non edit
			static LabelEdit theLabelEdit;
			bool bRslt = true;
			SbjCore::Mvc::TreeView::Controller* pTreeCtrlr = (SbjCore::Mvc::TreeView::Controller*)GetController();
			SbjCore::Mvc::TreeView::ItemController* pItemCtrlr = pTreeCtrlr->GetSelectedItem();

			if (pItemCtrlr != NULL)
			{
				(void)pItemCtrlr->HandleNotifyMsg(pNMHDR, pResult); 
				// item set the flag
				if (*pResult == 0)
				{
					CTreeCtrl* pTree = pTreeCtrlr->GetTreeCtrl();

					// subclass the edit so it handles return and escape 
					CEdit* pEdit = pTree->GetEditControl();
					theLabelEdit.SetTreeCtrl(pTree);
					if (theLabelEdit.GetSafeHwnd() != NULL)
					{
						theLabelEdit.UnsubclassWindow();
					}
					theLabelEdit.SubclassWindow(pEdit->GetSafeHwnd());
				}
			}
			return bRslt;
		}
	};

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

	class EndLabelEditHandler : public SbjCore::Mvc::NotifyMsgHandler
	{
		virtual bool OnHandleNotify(NMHDR* pNMHDR, LRESULT* pResult)
		{
			bool bRslt = true;
			*pResult = 1;

			SbjCore::Mvc::TreeView::Controller* pTreeCtrlr = dynamic_cast<SbjCore::Mvc::TreeView::Controller*>(GetController());
			if (pTreeCtrlr != NULL)
			{
				SbjCore::Mvc::TreeView::ItemController* pItemCtrlr = pTreeCtrlr->GetSelectedItem();
				bRslt = pItemCtrlr->HandleNotifyMsg(pNMHDR, pResult);
			}
			return bRslt;
		}
	};

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

	// Internal DragNDrop ///////////////////////////////////////////////////


	class BeginDragHandler : public SbjCore::Mvc::NotifyMsgHandler
	{
		virtual bool OnHandleNotify(NMHDR* pNMHDR, LRESULT* pResult)
		{
			pNMHDR;
			pResult;
			bool bRslt = true;
			SbjCore::Mvc::TreeView::Controller* pTreeCtrlr = (SbjCore::Mvc::TreeView::Controller*)GetController();
			CTreeCtrl* pTree = pTreeCtrlr->GetTreeCtrl();
			NMTREEVIEW* pNMTree = (NMTREEVIEW*)pNMHDR;
			HTREEITEM hti = pNMTree->itemNew.hItem;

			pTree->Select(hti, TVGN_CARET);

			CString s;
			s.Format(_T("%d"), hti);
			SbjCore::DragNDrop::TextSource* pSrc = new SbjCore::DragNDrop::TextSource(CFHTREEITEM, s);
			if (pSrc != NULL)
			{
				pSrc->DoDragDrop();
				bRslt = true;
				delete pSrc; 
			}
			return bRslt;
		}
	} ;

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

	class TreeDropTarget : public SbjCore::DragNDrop::DropTarget
	{
	public:
		TreeDropTarget()
		{
		}
		virtual ~TreeDropTarget()
		{
		}
		/*			
		virtual DROPEFFECT OnDragEnter(CWnd* pWnd, COleDataObject* pDataObject,
		DWORD dwKeyState, CPoint point)
		{
		}

		virtual DROPEFFECT OnDragOver(CWnd* pWnd, COleDataObject* pDataObject,
		DWORD dwKeyState, CPoint point);
		*/


		virtual DROPEFFECT OnDragScroll(CWnd* pWnd, DWORD dwKeyState,
			CPoint point)
		{
			dwKeyState;
			CTreeCtrl* pTree = dynamic_cast<CTreeCtrl*>(pWnd);
			if (NULL == pTree) // assume CTreeView
			{
				CTreeView* pView = dynamic_cast<CTreeView*>(pWnd);
				if (pView != NULL)
				{
					pTree = &pView->GetTreeCtrl();
				}
			}

			if (pTree != NULL) // either ctrl or view
			{
				// get client RectTracker of destination window
				CRect rectClient;
				pTree->GetClientRect(&rectClient);
				CRect rect = rectClient;

				rect.InflateRect(-nScrollInset, -nScrollInset); 

				if (rectClient.PtInRect(point) && !rect.PtInRect(point))
				{
					static int nPause = 0; // kludge to slow down the drag
					nPause++;
					if ((nPause % 5) == 0)
					{
						nPause = 0;
						HTREEITEM hti =	pTree->HitTest(point);
						if (hti != NULL)
						{
							HTREEITEM htiNext = pTree->GetNextVisibleItem(hti);
							HTREEITEM htiPrev = pTree->GetPrevVisibleItem(hti);
							pTree->EnsureVisible(htiNext);
							pTree->EnsureVisible(htiPrev);
						}
					}
				}
			}

			return DROPEFFECT_NONE; // decided to let DragEnter and DragOver handle the effect
		}

	};

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

	class SelChangedHandler : public SbjCore::Mvc::NotifyMsgHandler
	{
		virtual bool OnHandleNotify(NMHDR* pNMHDR, LRESULT* pResult)
		{
			bool bRslt = true;

			LPNMTREEVIEW pNMTV = (LPNMTREEVIEW)pNMHDR;
			SbjCore::Mvc::TreeView::Controller* pTreeCtrlr = dynamic_cast<SbjCore::Mvc::TreeView::Controller*>(GetController());

			if (pTreeCtrlr != NULL)
			{
				pTreeCtrlr->BlockNotify();

				HTREEITEM hti = pNMTV->itemNew.hItem;

				SbjCore::Mvc::TreeView::ItemController* pItemCtrlr = dynamic_cast<SbjCore::Mvc::TreeView::ItemController*>(pTreeCtrlr->GetItem(hti));
				bRslt = pItemCtrlr->HandleNotifyMsg(pNMHDR, pResult);
				SbjCore::Mvc::Model::ItemHandles selItems;
				selItems.push_back(pItemCtrlr->GetModelItemHandle());
				SbjCore::Mvc::Model::Controller* pModelCtrlr = SbjCore::Mvc::Model::GetCurController();
				pModelCtrlr->SetSelectedItems(selItems);
				pTreeCtrlr->BlockNotify(false);
			}
			return bRslt;
		}
	};

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

	class SetFocusHandler : public SbjCore::Mvc::WndMsgHandler
	{
		CALL_DEFAULT_FIRST() // comment out to handle message before default

			virtual LRESULT OnHandleWndMsg(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
		{
			wParam;
			lParam;
			*pResult = 0;
			LRESULT lRslt = 1;

			SbjCore::Mvc::TreeView::Controller* pCtrlr = dynamic_cast<SbjCore::Mvc::TreeView::Controller*>(GetController());
			SbjCore::Mvc::TreeView::ItemController* pItemCtrlr = dynamic_cast<SbjCore::Mvc::TreeView::ItemController*>(pCtrlr->GetSelectedItem());
			if (pItemCtrlr != NULL)
			{
				SbjCore::Mvc::Model::ItemHandles selItems;
				selItems.push_back(pItemCtrlr->GetModelItemHandle());
				SbjCore::Mvc::Model::Controller* pModelCtrlr = SbjCore::Mvc::Model::GetCurController();
				pModelCtrlr->SetSelectedItems(selItems);
			}

			return lRslt;
		}

	};

}

namespace SbjCore
{
	namespace Mvc
	{
		namespace TreeView
		{
			static int CALLBACK CompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
			{
				int nRslt = 0; // equal

				CTreeCtrl* pTree = (CTreeCtrl*)lParamSort;
				ItemController* pItemCtrlr1 = (ItemController*)lParam1;
				ItemController* pItemCtrlr2 = (ItemController*)lParam2;

				CString sItem1 = pTree->GetItemText(pItemCtrlr1->GetHandle());
				CString sItem2 = pTree->GetItemText(pItemCtrlr2->GetHandle());

				nRslt = strcmp(sItem1, sItem2); // default 


				if ((pItemCtrlr1 != NULL) && (pItemCtrlr2 != NULL))
				{
					bool b1 = pItemCtrlr1->CanAcceptChildren();
					bool b2 = pItemCtrlr2->CanAcceptChildren();

					if (b1) // is a folder
					{
						if (!b2) // is not folder
						{
							nRslt = -1; // folders < items
						}
					}
					else // b1 is an item
					{
						if (b2)	// is a folder
						{
							nRslt = 1; // items > folders
						}
					}
				}

				return nRslt;
			}


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

			///////////////////////////////////////////////////////////////////////////////////
			// ControllerImpl 

			/// private implementation struct for Controller class
			struct ControllerImpl
			{
				TVSORTCB tvs;
				CImageList theImageList;
				DWORD dwStyle;
				typedef std::map<HTREEITEM, ItemController* > t_HTREEITEMToItemControllerMap;
				t_HTREEITEMToItemControllerMap theItemCtrlrsByHTREEITEM;
				typedef std::map<HANDLE, ItemController* > t_ModelSourceHandleToItemControllerMap;
				t_ModelSourceHandleToItemControllerMap theItemCtrlrsByModelSourceHandle;

				typedef std::map<CString, CRuntimeClass*> ItemRTCMap;
				typedef ItemRTCMap::iterator ItemRTCMapIter;
				ItemRTCMap theItemRTCMap;


				int nBlockNotify;
				UINT nImageListID;
				bool bUsingResIDs;
				int nExpandLevels;

				localNS::FileOpenEventHandler theFileOpenHandler;
				localNS::ItemInsertedEventHandler theItemInsertedHandler;
				localNS::ItemRemovedEventHandler theItemRemovedHandler;
				localNS::SelChangedHandler theSelChangedHandler;		
				localNS::SetFocusHandler theSetFocusHandler;

				localNS::NMClickHandler theNMClickHandler;		
				localNS::NMRClickHandler theNMRClickHandler;		
				localNS::RenameItemHandler theRenameItemHandler;

				localNS::LabelEdit theLabelEdit;
				localNS::BeginLabelEditHandler theBeginLabelHandler;
				localNS::EndLabelEditHandler theEndLabelHandler;

				localNS::BeginDragHandler theBeginDragHandler;
				localNS::TreeDropTarget theDropTarget;

				ControllerImpl(const UINT n, const int n1, DWORD d) :
				dwStyle(d),
					nImageListID(n),
					nBlockNotify(0),
					bUsingResIDs(false),
					nExpandLevels(n1)
				{
					if (nImageListID != -1)
					{
						CBitmap bmp;
						VERIFY(bmp.LoadBitmap(nImageListID));

						BITMAP bm;
						bmp.GetBitmap (&bm);

						UINT nFlags = ILC_MASK | ILC_COLOR24;

						theImageList.Create(16, bm.bmHeight, nFlags, 0, 0);
						theImageList.Add(&bmp, RGB (255, 0, 255));
					}
				}

				bool Sort(HTREEITEM htiParent, CTreeCtrl* pTree, PFNTVCOMPARE lpfnCompare)
				{
					tvs.hParent = htiParent;
					tvs.lpfnCompare = CompareProc;
					tvs.lParam = (LPARAM)pTree;
					if (lpfnCompare != NULL)
					{
						tvs.lpfnCompare = lpfnCompare;
					}
					return pTree->SortChildrenCB(&tvs);
				}

				virtual ~ControllerImpl()
				{
					try 
					{
						for (t_HTREEITEMToItemControllerMap::iterator iter = theItemCtrlrsByHTREEITEM.begin(); iter != theItemCtrlrsByHTREEITEM.end(); iter++)
						{
							ItemController* pItemCtrlr = iter->second;
							if (pItemCtrlr != NULL)
							{
								delete pItemCtrlr;
							}
						}
						theItemCtrlrsByHTREEITEM.clear();
						theItemCtrlrsByModelSourceHandle.clear();

					}
					catch (...)
					{
						ASSERT(FALSE);
					}

				}

			};



			///////////////////////////////////////////////////////////////////////////////////////////////
			// Controller 

			IMPLEMENT_DYNCREATE(Controller, WndController)

			Controller::Controller() :
				m_pImpl(new ControllerImpl((UINT)-1, 0, 0))
			{

			}



			Controller::Controller(const UINT nImageListID, const int nExpandLevels, const DWORD dwStyle /*= DEFAULT_TREE_STYLES*/) :
				m_pImpl(new ControllerImpl(nImageListID, nExpandLevels, dwStyle))
			{
				AddHandler(TVN_SELCHANGED, &m_pImpl->theSelChangedHandler);
				AddHandler(WM_SETFOCUS, &m_pImpl->theSetFocusHandler);
				AddHandler(NM_CLICK, &m_pImpl->theNMClickHandler);
				AddHandler(NM_RCLICK, &m_pImpl->theNMRClickHandler);
				AddHandler(TVN_BEGINDRAG, &m_pImpl->theBeginDragHandler);
				AddHandler(TVN_BEGINLABELEDIT, &m_pImpl->theBeginLabelHandler);
				AddHandler(TVN_ENDLABELEDIT, &m_pImpl->theEndLabelHandler);
				AddHandler(ID_SBJCORE_CTX_RENAME, &m_pImpl->theRenameItemHandler);				
			}

			Controller::~Controller(void)
			{
				try 
				{
					delete m_pImpl;			
				}
				catch (...)
				{
					ASSERT(FALSE);
				}
			}

			void Controller::MapItemRTCToModelTypeName( LPCTSTR lpszName, CRuntimeClass* pRTC )
			{
				m_pImpl->theItemRTCMap[lpszName] = pRTC;
			}

			
			ItemController* Controller::CreateItem(const SbjCore::Mvc::Model::Controller* pModelCtrlr, HANDLE hModelItem)
			{
				ItemController* pItem = NULL;
				CString sItemType = pModelCtrlr->GetItemTypeName(hModelItem);
				if (!sItemType.IsEmpty())
				{
					CRuntimeClass* pRTC = m_pImpl->theItemRTCMap[sItemType];
					if (pRTC != NULL)
					{
						pItem = dynamic_cast<ItemController*>(pRTC->CreateObject());
						pItem->Create(pModelCtrlr, this, hModelItem);
						m_pImpl->theItemCtrlrsByModelSourceHandle[hModelItem] = pItem;
					}
				}
				return pItem;
				
			}

			void Controller::LoadTree(const SbjCore::Mvc::Model::Controller* pModelCtrlr, HANDLE hItem, HTREEITEM htiAfter /*= TVI_LAST*/ )
			{
				CTreeCtrl* pTree = GetTreeCtrl();
				DeleteAllItems();

				InsertTree(pModelCtrlr, hItem, TVI_ROOT, htiAfter);
				
				ItemController* pItemRoot = GetItemController(hItem);

				if (pItemRoot != NULL)
				{
					pTree->Expand(pItemRoot->GetHandle(), TVE_EXPAND);
					SelectItem(pItemRoot);
				}
				TreeLoaded();
			}

			void Controller::InsertTree(const SbjCore::Mvc::Model::Controller* pModelCtrlr, HANDLE hItem, HTREEITEM htiParent, HTREEITEM htiAfter /*= TVI_LAST*/ )
			{
				Inserter theInserter(pModelCtrlr, this, htiParent, htiAfter);

				theInserter.Apply(hItem);
			}

			ItemController* Controller::GetItemController( HANDLE hItem )
			{
				return m_pImpl->theItemCtrlrsByModelSourceHandle[hItem];
			}

			CTreeCtrl* Controller::GetTreeCtrl() const
			{
				CTreeCtrl* pTree = dynamic_cast<CTreeCtrl*>(GetWnd());
				if (NULL == pTree) // assume CTreeView
				{
					CTreeView* pView = dynamic_cast<CTreeView*>(GetCmdTarget());
					if (pView != NULL)
					{
						pTree = &pView->GetTreeCtrl();
					}
				}
				return pTree;
			}
			

			void Controller::SetExpandLevels( const int n )
			{
				m_pImpl->nExpandLevels = n;
			}

			int Controller::GetExpandLevels() const
			{
				return m_pImpl->nExpandLevels;
			}

			void Controller::SetStyle( const DWORD dwStyle )
			{
				m_pImpl->dwStyle = dwStyle;
			}

			DWORD Controller::GetStyle() const
			{
				return m_pImpl->dwStyle;
			}

			void Controller::SetImageListID( const UINT n )
			{
				m_pImpl->nImageListID = n;
			}

			UINT Controller::GetImageListID() const
			{
				return m_pImpl->nImageListID;
			}

			bool Controller::UsingResIDs() const
			{
				return m_pImpl->bUsingResIDs;
			}

			void Controller::SetUsingResIDs( const bool b )
			{
				m_pImpl->bUsingResIDs = b;
			}

			// Sets image list and initializes the CTreeCtrl as a OLE Drop Target
			void Controller::OnInitialize()
			{
				SbjCore::Mvc::Controller::OnInitialize();

				CTreeCtrl* pTree = GetTreeCtrl();
				pTree->SetImageList(&m_pImpl->theImageList, TVSIL_NORMAL);
				CImageList* pIL = pTree->GetImageList(TVSIL_NORMAL);
				ASSERT(pIL == &m_pImpl->theImageList);

				m_pImpl->theDropTarget.Register(static_cast<CWnd*>(GetCmdTarget()));
				m_pImpl->theFileOpenHandler.SetCtrlr(this);
				m_pImpl->theItemInsertedHandler.SetCtrlr(this);
				m_pImpl->theItemRemovedHandler.SetCtrlr(this);
			}


			// called by the context menu handler so appropriate commands can be added to the menu
			SbjCore::Utils::Menu::ItemRange Controller::OnPrepareCtxMenu(CMenu& ctxMenu)
			{
				Utils::Menu::ItemRange menuItems;
				ItemController* pItemCtrlr = GetSelectedItem();
				if (pItemCtrlr != NULL)
				{
					menuItems = pItemCtrlr->OnPrepareCtxMenu(ctxMenu);
				}			
				return menuItems;
			}


			// inserts an item based on the ItemController class
			HTREEITEM Controller::InsertItem(ItemController* pItemCtrlr, 
				HTREEITEM htiParent /*= TVI_ROOT*/, 
				HTREEITEM htiAfter /*= TVI_LAST*/, 
				bool bExpandParent /*= true*/)
			{
				CTreeCtrl* pTree = GetTreeCtrl();

				TVINSERTSTRUCT tvis;

				tvis.hParent = htiParent;
				tvis.hInsertAfter = htiAfter;
				tvis.itemex = (TVITEMEX&)pItemCtrlr->GetTVItem();

				HTREEITEM hti = pTree->InsertItem(&tvis);

				pItemCtrlr->SetHandle(hti);
				pItemCtrlr->SetCmdTarget(GetCmdTarget());

				m_pImpl->theItemCtrlrsByHTREEITEM[hti] = pItemCtrlr;

				// call virtual methods

				pItemCtrlr->OnInserted();

				if (bExpandParent)
				{
					pTree->Expand(pTree->GetParentItem(hti), TVE_EXPAND);
				}

				return hti;

			}



			/** override in derived classes. Called in ItemController to provide derivative the 
			ability to map a resource ID to CImageList index
			*/
			int Controller::ImageIndexFromResID(UINT nResID) const
			{
				return nResID;
			}

			void Controller::AddDropTargetHandler(CLIPFORMAT cf, DragNDrop::DataHandler* p)
			{
				m_pImpl->theDropTarget.AddHandler(cf, p);
			}

			void Controller::BlockNotify(const bool bBlock /*= true*/)
			{
				if (bBlock)
				{
					m_pImpl->nBlockNotify++;
				}
				else
				{
					if (m_pImpl->nBlockNotify > 0)
					{
						m_pImpl->nBlockNotify--;
					}
				}
			}

			bool Controller::IsBlocked() const
			{
				return (m_pImpl->nBlockNotify > 0);	
			}

			ItemController* Controller::GetItem(HTREEITEM hti) const
			{
				return m_pImpl->theItemCtrlrsByHTREEITEM[hti];
			}


			ItemController* Controller::GetItemFromPt(CPoint pt, UINT *pFlags)
			{
				ItemController* pItemCtrlr = NULL;

				if (!IsBlocked())
				{
					BlockNotify(true);

					CTreeCtrl* pTree = GetTreeCtrl();

					HTREEITEM hti = pTree->HitTest(pt, pFlags);

					if ((*pFlags & TVHT_ONITEM) && (hti != NULL))
					{
						pItemCtrlr = GetItem(hti);
					}
					BlockNotify(false);
				}

				return pItemCtrlr;
			}

			ItemController* Controller::GetParentItem(const ItemController* pItemCtrlr) const
			{
				CTreeCtrl* pTree = GetTreeCtrl();
				ItemController* pParentItem = NULL;
				if (pItemCtrlr != NULL)
				{
					HTREEITEM hti = pItemCtrlr->GetHandle();
					if (hti != TVI_ROOT)
					{
						HTREEITEM htiParent = pTree->GetParentItem(hti);
						pParentItem = GetItem(htiParent);
					}
				}
				return pParentItem;

			}


			ItemController* Controller::GetSelectedItem()
			{
				ItemController* pItemCtrlr = NULL;

				if (!IsBlocked())
				{
					BlockNotify(true);

					CTreeCtrl* pTree = GetTreeCtrl();

					HTREEITEM hti = pTree->GetSelectedItem();

					if (hti != NULL)
					{
						pItemCtrlr = GetItem(hti);
					}
					BlockNotify(false);
				}
				return pItemCtrlr;
			}

			void Controller::SelectItem(ItemController* pItemCtrlr)
			{
				if (pItemCtrlr != NULL)
				{
					CTreeCtrl* pTree = GetTreeCtrl();

					BlockNotify(true);

					HTREEITEM hti = pItemCtrlr->GetHandle();

					pTree->SelectItem(hti);

					BlockNotify(false);
				}
			}

			ItemController* Controller::FindItem(SearchCriteria* pCriteria)
			{
				ItemController* pItemCtrlrFound = NULL;
				for (ControllerImpl::t_HTREEITEMToItemControllerMap::iterator iter = m_pImpl->theItemCtrlrsByHTREEITEM.begin(); iter != m_pImpl->theItemCtrlrsByHTREEITEM.end(); iter++)
				{
					ItemController* pItemCtrlr = iter->second;
					if (pCriteria->ItemMatches(pItemCtrlr))
					{
						pItemCtrlrFound = pItemCtrlr;
						break;
					}
				}
				return pItemCtrlrFound;
			}


			void Controller::DeleteAllItems()
			{
				CTreeCtrl* pTree = GetTreeCtrl();
				pTree->DeleteAllItems();

				for (ControllerImpl::t_HTREEITEMToItemControllerMap::iterator iter = m_pImpl->theItemCtrlrsByHTREEITEM.begin(); iter != m_pImpl->theItemCtrlrsByHTREEITEM.end(); iter++)
				{
					ItemController* pItemCtrlr = iter->second;
					delete pItemCtrlr;
				}
				m_pImpl->theItemCtrlrsByHTREEITEM.clear();
				m_pImpl->theItemCtrlrsByModelSourceHandle.clear();
			}

			void Controller::DeleteItem(ItemController* pItemCtrlr)
			{
				CTreeCtrl* pTree = GetTreeCtrl();

				if (pItemCtrlr != NULL)
				{
					HTREEITEM hti = pItemCtrlr->GetHandle();

					if (pTree->ItemHasChildren(hti))
					{
						DeleteChildren(pItemCtrlr);
					}
					ControllerImpl::t_HTREEITEMToItemControllerMap::iterator iter = m_pImpl->theItemCtrlrsByHTREEITEM.find(hti);
					if (iter != m_pImpl->theItemCtrlrsByHTREEITEM.end())
					{
						ItemController* pItemCtrlr = iter->second;
						HANDLE hItem = pItemCtrlr->GetModelItemHandle();
						ASSERT(hItem != NULL);
						m_pImpl->theItemCtrlrsByHTREEITEM.erase(iter->first);
						m_pImpl->theItemCtrlrsByModelSourceHandle.erase(hItem);
						delete pItemCtrlr;
					}
					pTree->DeleteItem(hti);
				}
			}

			void Controller::DeleteChildren(ItemController* pItemCtrlr)
			{
				CTreeCtrl* pTree = GetTreeCtrl();

				HTREEITEM hti = pItemCtrlr->GetHandle();

				if (pTree->ItemHasChildren(hti))
				{
					HTREEITEM htiChild = pTree->GetChildItem(hti);

					while (htiChild != NULL)
					{
						HTREEITEM htiNext = pTree->GetNextItem(htiChild, TVGN_NEXT);
						ItemController* pChildItem = (ItemController*)pTree->GetItemData(htiChild);
						DeleteChildren(pChildItem);
						htiChild = htiNext;
					}
				}
				ControllerImpl::t_HTREEITEMToItemControllerMap::iterator iter = m_pImpl->theItemCtrlrsByHTREEITEM.find(hti);
				if (iter != m_pImpl->theItemCtrlrsByHTREEITEM.end())
				{
					ItemController* pItemCtrlr = iter->second;
					delete pItemCtrlr;
					m_pImpl->theItemCtrlrsByHTREEITEM.erase(iter->first);
				}
				pTree->DeleteItem(hti);
			}


			bool Controller::SortChildren(HTREEITEM htiParent, PFNTVCOMPARE lpfnCompare /*= NULL*/)
			{
				return m_pImpl->Sort(htiParent, GetTreeCtrl(), lpfnCompare);
			}


			bool Controller::NameExists(LPCTSTR lpszName, HTREEITEM htiParent /*= TVI_ROOT*/) const
			{
				bool bRslt = false;
				CTreeCtrl* pTree = GetTreeCtrl();

				CString s(lpszName);

				HTREEITEM htiChild = pTree->GetChildItem(htiParent);

				while (htiChild != NULL)
				{
					HTREEITEM htiNext = pTree->GetNextItem(htiChild, TVGN_NEXT);
					CString sChild(pTree->GetItemText(htiChild));
					if (0 == sChild.Compare(s))
					{
						bRslt = true;
						break;
					}
					htiChild = htiNext;
				}

				return bRslt;
			}

			CString Controller::AssureNewName(LPCTSTR lpszName, HTREEITEM htiParent /*= TVI_ROOT*/)
			{
				int nCount = 0;
				CString sItemText(lpszName);

				while (NameExists(sItemText, htiParent))
				{
					nCount++;
					sItemText.Format(_T("%s(%d)"), lpszName, nCount);
				}
				return sItemText;
			}



			ItemController* Controller::AssureItemCanAcceptChildren(ItemController* pItemCtrlr)
			{
				if (!pItemCtrlr->CanAcceptChildren())
				{
					pItemCtrlr = pItemCtrlr->GetParentItem();				
				}
				return pItemCtrlr;
			}

			void Controller::TreeLoaded()
			{
				OnTreeLoaded();
			}
			void Controller::OnTreeLoaded()
			{
				// nada
			}


		}
	}
}


//*** Modification History ***
// $Log: /SbjDev/SbjCore/TreeController.cpp $
// 
// 15    11/12/08 2:22p Steve
// Finished Generalization of Model  v2.0.1
// 
// 14    10/24/08 9:03a Steve
// 
// 13    10/20/08 11:06a Steve
// Removed source parameter from Event and replaced with EventT data
// 
// 12    10/14/08 1:12p Steve
// Implemented Deletes
// 
// 11    9/23/08 3:05p Steve
// Fixed _com_error in XmlModelAccess::GetAttribute (guard against
// VT_NULL)
// 
// 10    9/23/08 11:30a Steve

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
SBJ
United States United States
Real name is Steve Johnson. Programming since 1979. Started on a Heathkit Micro with a DEC LSI-11 and UCSD Pascal. Moved to PCs & DOS as soon as Turbo Pascal became available. Did some Assembly, ISR, TSR etc. All this while working for a Manufacturing Co. for 8 years. Had my own solo Co. doing barcode labeling software for 4 years (terrible business man, all I wanted to do was code). Since then working for various software companies. Moved to Windows around the time of 3.1 with Borland C then C++. Then on to VC++ and MFC, and just about anything I could get my hands on or had to learn for my job, and been at it ever since. Of course recently I've been playing with .NET, ASP, C#, WPF etc.

Comments and Discussions