Introduction
This article presents a method for multiple dynamic views creation whithout
the MFC's document/view architecture. That views could be attached to other
frames, controls or dock bars.
The document/view architecture is a very useful mechanism to create robust
applications that requires multiple views, but sometimes there is not necessary
such funcionality. Even though creating views whithout the help of the framework
is neither evident or easy. The problem lies basically in the following aspects:
- Derivating a class from
CView
does not allow creation of the
views using the operator
new
because the constructor and destructor of the derivated class are protected.
- When the window or frame that contains the views created "by
hand" is destroyed, then automatically the objects
CView
are
destroyed and is invokated the DestroyWindow()
for each one. That's
why the created views can not be referenced using objects like member variables
of a class but using pointers to objects, so it is necessary to use the operator
new
for its creation but not invoking delete
because
the framework will destroy that objects automatically.
- When creating multiple views dinamically there is necessary to assign
valid identifiers that can not be defined using the resource editor of Visual
C++. For that there must be defined a generic identifier that will be used as
base for the identification of each view that will be created by the time (using
something like
WM_USER + idView0
, WM_USER + idView1
,
...).
Step by Step
- Derivate the customized view/views using the wizard of Visual C++ from
CView
and make public the constructor and destructor of the class. This will allow the
using of the operator
new
in the objects creation.
class CMyView : public CView
{
class CMyPoint : public CObject
{
public:
CMyPoint(CPoint location) { m_location = location; }
CPoint m_location;
};
public:
CMyView();
virtual ~CMyView();
public:
int m_idView;
protected:
DECLARE_DYNCREATE(CMyView)
protected:
CObList m_points;
public:
protected:
virtual void OnDraw(CDC* pDC);
protected:
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
DECLARE_MESSAGE_MAP()
};
- In the frame/window/dialog class where it is desired to create and
reference the views, it is necessary to add an object list/array (
CObList
)
or a set of attributes pointer to the views so in the future could be referenced the
views using those variables. In the example it is created an object CObList
that
stores a pointer to each view created and that object CObList
is
added to the dialog which is the main window.
enum VIEW_TYPE { VIEW_2D, VIEW_OPENGL };
class CDynViewsDlg : public CDialog
{
public:
CDynViewsDlg(CWnd* pParent = NULL);
enum { IDD = IDD_DYNVIEWS_DIALOG };
CTabCtrl m_tabCtrl;
protected:
virtual void DoDataExchange(CDataExchange* pDX);
protected:
void AddView(VIEW_TYPE viewType);
void SelectTab(int nTab);
HICON m_hIcon;
CObList m_listViews;
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnSelchangeTabctrl(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnAddview();
afx_msg void OnAddopenglview();
DECLARE_MESSAGE_MAP()
};
- When is desired to create a new view, simply must be created an object of
that class
CView
using the operator new
, store the
pointer to that view for lately use, search a new identifier not used for the
new view and invoke create of the view using that identifier.
void CDynViewsDlg::OnAddview()
{
AddView(VIEW_2D);
}
void CDynViewsDlg::OnAddopenglview()
{
AddView(VIEW_OPENGL);
}
void CDynViewsDlg::AddView(VIEW_TYPE viewType)
{
int idNewView = m_listViews.GetCount();
CRect clientRect;
m_tabCtrl.GetClientRect(&clientRect);
clientRect.DeflateRect(10, 30);
CMyView* m_pNewView;
CString tabCaption;
switch(viewType) {
case VIEW_2D:
m_pNewView = new CMyView();
tabCaption.Format("View %d (2D)", idNewView);
break;
case VIEW_OPENGL:
m_pNewView = new COpenGLView();
tabCaption.Format("View %d (OpenGL)", idNewView);
break;
};
m_tabCtrl.InsertItem(idNewView, tabCaption);
m_pNewView->m_idView = idNewView;
if(! m_pNewView->Create(NULL, NULL, WS_VISIBLE | WS_CHILD, clientRect,
&m_tabCtrl, ID_VIEW_BASE + idNewView))
{
TRACE( "Failed view creation\n" );
}
m_listViews.AddTail(m_pNewView);
m_tabCtrl.SetCurSel(idNewView);
SelectTab(idNewView);
}
- Once this has been made it is possible to use the pointer to the view to
achieve basic window functions like show/hide, change size, etc.
void CDynViewsDlg::SelectTab(int nTab)
{
CMyView* pViewSelected = NULL;
POSITION pos = m_listViews.GetHeadPosition();
while(pos != NULL) {
CMyView* pView = (CMyView*) m_listViews.GetNext(pos);
pView->ShowWindow(SW_HIDE);
if(pView->m_idView == nTab) pViewSelected = pView;
};
if(pViewSelected != NULL)
pViewSelected->ShowWindow(SW_SHOW);
}
- It is really important to remember that the objects
CView
created dynamically must not be deleted explicitally using the operator delete
. In fact they will be automatically deleted by the framework in the WM_DESTROY
message of the window that is parent of the views. In the example the parent of
all the views is a tab control and when the application exits all the views are
deleted and there are not any
memory leaks.