A few weeks ago, working on TIFF viewer software, I realized that many developers, who use wxWidgets in their work, spend their time on implementing the functionality which already exists in wxWidgets library. Such tasks as loading/saving documents, edit/copy/paste functionality, separating the GUI from application’s logic, all of them can be performed in a far more simple way than people usually do. Why should I write the code which creates wxFileDialog, checks the current state of application, asks user if he/she wants to save the changes, shows wxFileDialog for saving document into a file and so on? Why should I do everything by hands? Life is too short for spending it to all these things ;)
But there no need to give up because there is a thing (in fact, a set of things ;)) which allow to avoid writing tons of unnecessary code. It is wxWidgets’s Document/View framework.
It is the first article from a set of articles about such an interesting part of wxWidgets as Document/View framework.
As always, I’ll try to explain everything by an example. So, the first step will be creating a skeleton application which utilizes Document/View architecture:
|
DocViewTestApp.h
|
#ifndef _DOC_VIEW_TEST_APP_H
#define _DOC_VIEW_TEST_APP_H
#include <wx/wx.h>
#include <wx/docview.h>
class DocViewTestApp : public wxApp
{
wxDocManager * m_DocManager;
public:
virtual bool OnInit();
virtual int OnExit();
};
DECLARE_APP(DocViewTestApp)
#endif
|
|
DocViewTestApp.cpp
|
#include "DocViewTestApp.h"
#include "DocViewTestMainFrame.h"
IMPLEMENT_APP(DocViewTestApp);
bool DocViewTestApp::OnInit()
{
m_DocManager = new wxDocManager;
m_DocManager->SetMaxDocsOpen(1);
DocViewTestMainFrame * frame = new DocViewTestMainFrame(m_DocManager, NULL);
SetTopWindow(frame);
frame->Centre();
frame->Show();
return true;
}
int DocViewTestApp::OnExit()
{
wxDELETE(m_DocManager);
return wxApp::OnExit();
}
|
As you can see, the application class contains a member m_DocManager which is an object of wxDocManager class.
The wxDocManager class is part of the document/view framework supported by wxWidgets, and cooperates with the wxView, wxDocument and wxDocTemplate classes.
|
DocViewTestMainFrame.h
|
#ifndef _DOC_VIEW_TEST_MAINFRAME_H
#define _DOC_VIEW_TEST_MAINFRAME_H
#include <wx/wx.h>
#include <wx/docview.h>
#include <wx/aui/aui.h>
#define DocViewTestMainFrameTitle _("DocView Test")
class DocViewTestMainFrame : public wxDocParentFrame
{
DECLARE_DYNAMIC_CLASS(DocViewTestMainFrame)
wxAuiManager m_AuiManager;
void CreateControls();
wxMenuBar * CreateMenuBar();
public:
DocViewTestMainFrame();
DocViewTestMainFrame(wxDocManager * docManager, wxFrame * parent,
wxWindowID id = wxID_ANY,
const wxString & title = DocViewTestMainFrameTitle);
~DocViewTestMainFrame();
bool Create(wxDocManager * docManager, wxFrame * parent,
wxWindowID id = wxID_ANY,
const wxString & title = DocViewTestMainFrameTitle);
DECLARE_EVENT_TABLE()
void OnExit(wxCommandEvent & event);
};
#endif
|
|
DocViewTestMainFrame.cpp
|
#include "DocViewTestMainFrame.h"
IMPLEMENT_DYNAMIC_CLASS(DocViewTestMainFrame, wxDocParentFrame)
BEGIN_EVENT_TABLE(DocViewTestMainFrame, wxDocParentFrame)
EVT_MENU(wxID_EXIT, DocViewTestMainFrame::OnExit)
END_EVENT_TABLE()
DocViewTestMainFrame::DocViewTestMainFrame()
{
}
DocViewTestMainFrame::DocViewTestMainFrame(wxDocManager * docManager, wxFrame * parent,
wxWindowID id, const wxString & title)
{
Create(docManager, parent, id, title);
}
DocViewTestMainFrame::~DocViewTestMainFrame()
{
m_AuiManager.UnInit();
}
bool DocViewTestMainFrame::Create(wxDocManager * docManager, wxFrame * parent,
wxWindowID id, const wxString & title)
{
bool res = wxDocParentFrame::Create(docManager, parent, id, title,
wxDefaultPosition, wxSize(650, 450));
if(res)
{
CreateControls();
}
return res;
}
void DocViewTestMainFrame::CreateControls()
{
SetMenuBar(CreateMenuBar());
m_AuiManager.SetManagedWindow(this);
m_AuiManager.AddPane(new wxPanel(this, wxID_ANY),
wxAuiPaneInfo().CenterPane().Name(_("Canvas")));
m_AuiManager.Update();
}
wxMenuBar * DocViewTestMainFrame::CreateMenuBar()
{
wxMenuBar * result = new wxMenuBar;
wxMenu * fileMenu = new wxMenu;
fileMenu->Append(wxID_NEW, _("New\tCtrl+N"));
fileMenu->Append(wxID_OPEN, _("Open\tCtrl+O"));
fileMenu->AppendSeparator();
fileMenu->Append(wxID_SAVE, _("Save\tCtrl+S"));
fileMenu->Append(wxID_SAVEAS, _("Save as..."));
fileMenu->AppendSeparator();
fileMenu->Append(wxID_EXIT, _("Exit\tAlt+F4"));
wxMenu * helpMenu = new wxMenu;
helpMenu->Append(wxID_ABOUT, _("About..."));
result->Append(fileMenu, _("File"));
result->Append(helpMenu, _("Help"));
return result;
}
void DocViewTestMainFrame::OnExit(wxCommandEvent &event)
{
Close();
}
|
Our main frame class is derived from wxDocParentFrame class. wxDocParentFrame provides a default top-level frame for applications which utilize Document/View architecture. It can be used ONLY FOR SDI (Single Document Interface), not MDI (Multiple Document Interface) applications. As you can see, we pass a pointer to wxDocManager object to the constructor of our main frame. Passing of wxDocManager object to the constructor is mandatory because in other way our frame will not be able to load the documents.
Now, after we finished a GUI-related part, we have to create our Document and View classes:
|
DocViewTestDocument.h
|
#ifndef _DOC_VIEW_TEST_DOCUMENT_H
#define _DOC_VIEW_TEST_DOCUMENT_H
#include <wx/wx.h>
#include <wx/docview.h>
class DocViewTestDocument : public wxDocument
{
DECLARE_DYNAMIC_CLASS(DocViewTestDocument)
public:
DocViewTestDocument();
};
#endif
|
|
DocViewTestDocument.cpp
|
#include "DocViewTestDocument.h"
IMPLEMENT_DYNAMIC_CLASS(DocViewTestDocument, wxDocument)
DocViewTestDocument::DocViewTestDocument()
{
wxLogTrace(wxTraceMask(), wxT("DocViewTestDocument::DocViewTestDocument"));
}
|
|
DocViewTestView.h
|
#ifndef _DOC_VIEW_TEST_VIEW_H
#define _DOC_VIEW_TEST_VIEW_H
#include <wx/wx.h>
#include <wx/docview.h>
class DocViewTestView : public wxView
{
DECLARE_DYNAMIC_CLASS(DocViewTestView)
public:
DocViewTestView();
virtual void OnDraw(wxDC* dc);
virtual void OnUpdate(wxView *sender, wxObject *hint = (wxObject *) NULL);
virtual bool OnClose(bool deleteWindow = true);
};
#endif
|
Our DocViewTestView class overrides wxView::OnDraw, wxView::OnUpdate and wxView::OnClose methods.
The default implementation calls wxDocument::Close to close the associated document. Does not delete the view. The application may wish to do some cleaning up operations in this function, if a call to wxDocument::Close succeeded.
|
DocViewTestView.cpp
|
#include "DocViewTestView.h"
IMPLEMENT_DYNAMIC_CLASS(DocViewTestView, wxView)
DocViewTestView::DocViewTestView()
{
wxLogTrace(wxTraceMask(), wxT("DocViewTestView::DocViewTestView"));
SetFrame(wxTheApp->GetTopWindow());
}
void DocViewTestView::OnDraw(wxDC* dc)
{
wxLogTrace(wxTraceMask(), wxT("DocViewTestView::OnDraw"));
}
void DocViewTestView::OnUpdate(wxView *sender, wxObject *hint)
{
wxLogTrace(wxTraceMask(), wxT("DocViewTestView::OnUpdate"));
}
bool DocViewTestView::OnClose(bool deleteWindow)
{
wxLogTrace(wxTraceMask(), wxT("DocViewTestView::OnClose"));
if (!GetDocument()->Close())
{
return false;
}
SetFrame(NULL);
Activate(false);
return true;
}
|
As you can see, we associate a top level window of our application with each object of DocViewTestView class. After that DocViewTestView object will receive events from this frame. In fact, we can associate not only wxFrame-derived objects but any wxWindow-derived object, e.g. wxScrolledWindow or wxTextCtrl.
Note that this "frame'' is not a wxFrame at all in the generic MDI implementation which uses the notebook pages instead of the frames and this is why this method returns a wxWindow and not a wxFrame.
Now, after we created Document and View classes, we have to create a document template. The
wxDocTemplate class is used to model the relationship between a document class and a view class.
|
DocViewTestApp.cpp
|
#include "DocViewTestApp.h"
#include "DocViewTestMainFrame.h"
#include "DocViewTestDocument.h"
#include "DocViewTestView.h"
...
bool DocViewTestApp::OnInit()
{
m_DocManager = new wxDocManager;
m_DocManager->SetMaxDocsOpen(1);
wxDocTemplate * docTemplate = new
wxDocTemplate(m_DocManager, _("DocViewTest Document"),
wxT("*.png;*.bmp;*.tiff;*.tif;*.jpg;*.jpeg"), wxEmptyString,
wxT("png"), wxT("DocViewTest Doc"), wxT("DocViewTest View"),
CLASSINFO(DocViewTestDocument), CLASSINFO(DocViewTestView));
...
}
...
|
As you can see here, for each wxDocTemplate object we have to specify a wxDocManager, document name (will be displayed in the file filter list of Windows file selectors), wildcard, default directory (we left this field empry by using wxEmptyString), default file extension, document type name, view type name, document and view classes.
Now we can start the application and see how it works:

For the first look our application does nothing. But it is only "for the first look". If you select
File -> Open menu item, file open dialog will appear and if you select a file, then the title of our main frame will be changed, if you select
File -> New menu item, the title of our main frame will be changed to the name of newly created document. Also if you select
File -> Save menu item, save file dialog box will appear (but in fact, you will not be able to save the document because this behavior is not specified in our skeleton application).
Also if you perform these tasks:
- Create new document
- Open a file
- Save a file
- Create new document
- Close the application
And then will look to "Output" window of your IDE, you will see such messages:
01:54:47: DocViewTestDocument::DocViewTestDocument
01:54:47: DocViewTestView::DocViewTestView
01:54:51: DocViewTestView::OnClose
01:54:51: DocViewTestDocument::DocViewTestDocument
01:54:51: DocViewTestView::DocViewTestView
01:54:51: DocViewTestView::OnUpdate
01:55:02: DocViewTestView::OnClose
01:55:02: DocViewTestDocument::DocViewTestDocument
01:55:02: DocViewTestView::DocViewTestView
01:55:24: DocViewTestView::OnClose
Now you can get an idea about how everything works:
- When you execute
wxID_NEW command, new document is created, then a view for this document is created
- Then you executed
wxID_OPEN command, open file dialog is displayed, after you select a file wxView::OnClose method is called, then view and document are destroyed, new document is created, then a view for this document is created, wxView::OnUpdate method is called.
- When you exit the application
wxView::OnClose method is called, then view and document are destroyed.
All wxDocument-, wxView- and wxDocTemplate-derived objects are deleted automatically when wxDocManager object is destroyed (in our application we destroy it in wxApp::OnExit method).
That is all for today. In the next article I will show how to implement simple loading/saving functionality.
You can download a source code for this article here
T-Rex:
I write sample applications specially for articles.
I don't have a framework because usually each software with Document/View architecture has many peculiar properties which are not needed in other software and usually it is not possible to port the source code from one application to another without modifications. So i don't see any reason for adding one more layer above Document/View framework available in wxWidgets. It is better to use its ready to use parts.
I'm not sure that I understand you correctly, could you describe this part in details? :)
You could try to create several wxDocTemplate objects and pass CLASSINFO of your wxDocument class to each of them and CLASSINFO of each view e.g. Doctemplate1(Doc1, View1) Doctemplate2(Doc1, View2).
And in file selector dialog you will have several items in file filter combo box.
Another way is digging the source code of Document/View framework and creating your own modification which will select the specific view when opening documents.
Also you should remember that wxDocTemplate assigns only one view per document and all other views should be added manually.
I'll think about it :)
It is the first article and it describes only how to use SDI parent frame, I'm going to describe how to use MDI in next articles of this series.
Yes.
mnealon:
Thanks for taking the time to investigate and educate in this often confusing area of wxWidgets. (and for writing it in English)
I look forward to seeing more of these, especially since I will need to implement something like this in my own projects.
Could I ask if you have a framework for this series, with predefined "goals" in each part? Or are you just presenting your findings "as you go along"?
Would you offer some advice along the way, wrt e.g. not needing to present the "file filter list" dialog (single document/multiple view or perhaps multiple document/default view). I know, for example, in my current project that I will have a single document type, with several possible views of that document, but when opening I will always want a specific view used.
Once you have completed this series, how about an addendum with using wxArt2D's implementation of the doc/view framework, with its common view philosophy? (by this I mean that wxArt2D's version doesn't care about what the type of parent frame is, it is more concerned with the document/view and the view can be in any window/dialog - if memory serves it has it's own canvas object that it used for the view so statements such as "It can be used ONLY FOR SDI (Single Document Interface), not MDI (Multiple Document Interface)" shouldn't be necessary). Or will you be limiting your discourse to the pure wxWidgets codebase?
Will you be presenting any information wrt using doc/view in wxAUI?
In any case, thanks again for taking the time to do this, I always find your tutorials interesting and well thought out.