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.
Collapse
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.
Collapse
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.
Collapse
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.