Contents
-
Summary
- What does a document/view framework do?
- Why another document/view framework for wxWidget?
- Use of Events instead of Direct calls
- An overview
- How a docview application looks like
- Typical steps to use this framework
- The parts of the docview framework
- The a2dDocumentCommandProcessor
- The a2dEventDistributer
- The a2dDocviewGlobal
- The a2dDocument
- The a2dView
- The a2dDocumentTemplate
- The a2dViewTemplate
- The a2dViewConnector
- a2dIOHandlerStrIn
- a2dIOHandlerStrOut
- Event overview
- Topics
Summary
This chapter introduces a new document/view framework for wxWidgets which uses another approach than the standard wxWidgets document/view framework. It explains why this doc/view framework was designed and why it may better fit your needs than the currently framework used by wxWidgets. The chapter also explains how to use this framework. The Docview module is at the heart of the wxArt2D libary. But it can also be used by other applications which require an advanced Document View system.
What does a document/view framework do?
A document/view framework follows the idea that a typical application consists of "documents" and "views on documents". Document in this term holds data and provides for interface-independent operations on its data. Views visualize and change the data of the documents. Other objects in a document/view framework are I/O handlers, which load and save the document's its data to a file or stream.
Example What is a document, what is a view?
Think of a HTML document which consists of several HTML tags (the data). A view on this HTML document might be a view which interprets the HTML tags and displays a formatted page (like the browser Mozilla), while another view might show the pure HTML tags. In this case you have two views on the same document. Yet another view may collect all tags used in the HTML documents, and display statistics on this. This last case uses a view on multiple documents.
A document/view framework assists the programmer to follow this program-paradigm and offers an API to work efficiently with this approach. The overall central command processor, maintains the open documents in a list, and is used to create new views on those documents. Its uses document templates for creating new documents, and view templates for creating new views for a document. How new views are hooked into the application its windows and frames, is under control of a a2dViewConnector. Each document can have an internal command processor, which will then be used to make modification on the document undoable.
Why another document/view framework for wxWidget?
If you are familiar with wxWidget you may have recognized that this portable GUI toolkit also delivers a document/view framework in its standard distribution. So, why another document/view framework? The reason to develop this framework was that the wxWidget doc/view architecture is heavily related to the interface you want to use for your application (i.e. MDI or SDI). Views are responsible for creating the windows and frames that they use for displaying document data. This makes views only useable for the one type of application. If you develop a MDI application your views rely on the MDI classes and you will have to do a lot of work if you want switch to another interface, i.e. SDI, because the views are related to a wxDocMDIChildFrame, for example. Therefore you can not write general view classes that can be used in any type of application. Imagine someone has written a nice viewing class to display the content of an HTML document. Of course one would like to make this viewing class part of a library that includes the HTML document class, plus its viewing classes. The trouble in that case is that the view class creates a window and frame for one type of application, this makes it impossible to use it in another type of application.
This framework has another approach: A view is independent from a frame, it is only interested in one window, which it will use for displaying data from a document. Where this window is situated in the application is not important for the view. A view knows nothing about the user-interface, it may be MDI, SDI etc. You can use a view with several user-interfaces; the view is connected to a specific interface (window or frame) at runtime by a view connector. After creation of a new view, a viewconnector class is used to generate or use an existing window for the view , and if needed, it will also create the (child)frame to be used as parent for the new window. Now a view only needs to know its display window, which it uses for displaying document data, and for sending events up to that window, including parent windows or frames. The viewconnector is responsible for connecting of a view into the application, and this makes a view application independent. Because of this a view can easily be used in all types of applications, and therefore be made part of a library. In the wxArt2D library, a view called a2dCanvasView is used to display the content of a a2dCanvasDocument, on a a2dCanvas window. a2dCanvasView is only aware of one window, which is a2dCanvas. It does not know, if the application is SDI or MDI, or any other. This way of connecting views works very well when reusing the same windows in one frame for displaying several document plus views. All that is needed is to disconnect the old views from the windows in a frame and connect the new ones.
The wxArt2D Docview framework is an improvement over the wxWidgets Docview classes for the following reasons:
- View connectors make a view independent from a window. The view connector associates a window with a view. This was the biggest stumbling block with the wxWidgets version, and which made it unsuitable for use as a base for wxArt2D, where view and rendering are combined in one class and capable of being plugged into a a2dCanvas. Views or not responsible for windows, and in fact Views can be plugged in an out of its display window.Moreover, it can have a document from which it can display information, however, this is not a must.
- Combining several views into one frame is easy. A view may or may not have a document attached, so a set of Views can remain attached to a window, and its parent, even if the documents are all closed. On opening a new document all of the existing views can be re-used for displaying a view of the new document. In the wxWidgets version this is close to impossible.
- documents do not own views, and do not have a list of views, thus making many things much easier. For example, a view can display information from several documents. The Event Distributor is the key to this by accepting events from a Document and dispatching them to Views and other registered classes. Thus it is possible for a view to claim an event and update its view of the associated Document.
- Almost everything works via events, and in many cases special events are sent to a global event distributor which dispatches the events to those handler classes registered to it. In this way your application is always in complete synchronization, something that is not possible in the wxWidgets version.
- the concept of a document manager, is replaced by the Command Processor. This class, in addition to doing what the old docmanager did, adds a central Command Processor to the Application. The Command Processor maintains documents and templates, and manages the view for the current document. Command Processing is implemented in such a way as to make macro recording much easier. The Command Processor commands are implemented as ascii strings. Internal there is a variable list, which can be used as part of commands. Commands can set and use variables, either set by the application or by external commands.
- Important is the way document and their view and windows are closed. A view can be closed for two reasons. One when its frame/window is closed, two when a/its document is closed. So two paths. The second path does come to the view via its document via a viewclose event, and it may/sometimes should also close the windows that contains the view. In a multiframe app this is the case. In a single frame having several views, the windows should stay intact, and maybe even the view, and only the document should be decoupled from its document. In that case the views can be used for a new document later on. In the first path ( closing the frame ), the close event comes form the window, and is sent to the view, and closing the view might lead to also needing to close the document it displays. This is above situation is arranged in the wxArt2D version in an orderly manner, without confusion.
- There are two templates on to link file extension to document types and an IOhandler class to read such a file into a document. A second template is used to couple document types to view types. This is different form the original docview classes where there was only one template, which coupled file extension plus document type plus viewtype in one. In many cases this does not make sense.
- Iohandlers are the classes to read files into document, a basic XML pull/push parser iohandler is part of the docview classes. This makes it easy for you to read and write XML based data to and from into an internal document.
Use of Events instead of Direct calls
This document/view architecture heavily relies on events which are the base of communication between the parts of this framework. The ideas behind the original wxWidgets Docview classes are still the same. But for instance events are used as the means of communicating between the document and its views. This has resulted in a very flexible framework with enhanced ease of use. The main advantage is in the way views of documents are organized. The association of views with the application's windows is via the a2dViewConnector class which results in a view having a single pointer to its parent window. In this way views are made application independent, and views can be stored in a software library and used by other applications and/or different flavors of the same application. For instance an editor view which manipulates an object of a document can be used in a multi or single window application without modification.
Events are used to notify, collect information, or start an action. These events are managed by an event distributer to which it is possible to register a class. In this way independent classes, can be easily hooked into the framework. For example, Views receive update events from associated documents, via the event distributer. A document has no idea how many views of itself there are. A view can even display information from one or more documents. By virtue of the event system the classes in the framework are loosely coupled. Additionally one can use a document class by itself, and without the rest of the framework classes. Furthermore a Document and a View combined with a display window are enough for the implementation of a complete application. The last situation is the simplest use of a a2dCanvas in the wxArt2D library.
The Event Distributor when combined with the Command Processor makes it possible to have a variety of multiple views from multiple documents. Each View additionally has a plugin capability for adding tools etc. Undo/Redo capability is provided by the Command Processor by virtue of its ability to stack commands. Thus the undoing of modifications to a document is possible.
An overview
The docview module is part of the distribution of wxArt2D (a 2D-graphic vector drawing library) but an independent module of the library.
The idea behind this module is, that the parts of the docview framework communicate through events with each other and each part knows only the things which are absolutely necessary for it. The classes are therefore very loosely coupled, and this again makes it possible to use some of the classes in a standalone situation too. In a docview controlled application, a2dDocumentCommandProcessor is essential, since it keeps track of opened documents, and the current view that has the focus or is active. Many operation or commands called via this class operate on the current document or view.
Another important class is a2dViewConnector which connects a view (a2dView) with a window. This class is the only class that has the knowledge what interface (i.e. MDI) your application uses. If you want to switch to another interface you have just to modify your a2dViewConnector derived class or create an additional a2dViewConnector.
You create your documents as subclasses of the a2dDocument and your views derive from the a2dView.
Smart pointers are used instead of pointers where possible, this means, that an object can be owned by several other objects (they have a "reference" to it) and will automatically be deleted if no other object has a reference to it. A typical use is for view on a document, when all views are released, and all other classes holding a smart pointer to a document are released, a document will be deleted. This approach does not need administrating a list of views inside a document, and makes the document unaware of how many views exist on it. Still it is aware of something that is used to display the contents of the document, and therefore its sends event around to makes those views and dialogs aware of changes to the document, which views can intercepts in order to update itself.
A new document, a a2dDocument, instance is created by a a2dDocumentTemplate instance, which informs the a2dViewConnector via an event that a new document was created. The a2dViewConnector derived class creates a a2dView by using a a2dViewTemplate and receives an event from the a2dViewTemplate if the view was created. The latter event is used by the a2dViewConnector to connect the new view with an application window (either this window already exists or the connector creates a new window, this depends on your needs).
If you are familiar with the standard doc/view framework of wxWidget you may have noticed, that it also use a class DocTemplate but no ViewTemplate. The ViewTemplate is used to model the relationship between documents and views and used to create new views depending on the created document type. The DocTemplate informs the framework about different document types, and which types of files belong to which type of document. Many files formats may use the same type of document type. Each file format does not need to have a specific view, since views do normally only depend on the type of document. Seperating templates into view and document templates has resolved this problem. For each file format (recognized by the extension), there is a2dDocumentTemplate, and the way this format needs to be parsed is indicated by the I/O handler a2dIOHandler in that template. The a2dDocument will automatically use this to read the contents of the file into document, or write the contents of the document to a file.
How a docview application looks like
The figure a docview application shows how a docview based application can look like. A a2dDocumentCommandProcessor has a list of a2dDocument's. The commandprocessor, takes care of closing and opening or creating new documents. Next to that it creates a2dView's for the documents. It is called a command processor because here is the entry point for command like operations on the current document or view. Views do display data stored in a a2dDocument onto a a2dDocumentViewWindow. The a2dDocumentViewWindow is a subwindow of a2dDocumentFrame. The application here has 3 documents, document 1 has 3 views. View1 is using Frame1 to display itself. View 2 and 3 use Frame2 to display themselfs. Document 2 has 2 views, and they use Frame3 for the display. At last Document 3 has one view, displayed on the Parent Frame of the application.
Communication in the docview classes is done by sending events. For example, a view sents a viewclose event to its display window and document, when it wants to close. When a a2dView sents events to a a2dDocumentViewWindow, the last will either handle the event or sent it up to the parent window, which is often a a2dDocumentFrame. This way views inform a a2dDocumentFrame of closing, and the frame can react by destroying itself, or by disconnecting the view from itself, in order to reconnect to another view later on.
New views and documents are/can be generated by the templates a2dViewTemplate and a2dDocumentTemplate. Those templates generate a view or document, and inform a a2dViewConnector of such an event. The a2dViewConnector will then be responsible for connecting the new views to the application its windows. The a2dViewConnector may first generate a new a2dDocumentFrame plus a a2dDocumentViewWindow, and use that window to connect the new view to. In another application, it may use an existing a2dDocumentViewWindow, disconnect the connected view, and connect the new view to the window.
A view always has only one wxWindow pointer internal, which it uses to display itself on to. So if a wxFrame displays two views, this means that the a2dDocumentFrame has two a2dDocumentViewWindow's as children, and those two windows are used by the a2dView's to display them selfs.
Typical steps to use this framework
There are some overall steps to use this framework.
A detailed description of each docview part follow after this overview.
Create your own document and view classes, overriding a minimal set of member functions of a2dDocument and a2dView.
- Create windows which should be used by the views, or better: where the views should be connected to. Either you use a wxDocviewWindow or a wxDocviewScrolledWindow or you may create your own windows.
Define your view connector(s) by overriding the member methods OnPostCreateDocument(a2dDocumentTemplateEvent& event) and OnPostCreateView(a2dViewTemplateEvent& event) or catch the events wxEVT_POST_CREATE_DOCUMENT and wxEVT_POST_CREATE_VIEW to direct them to your own class functions. The latter event is more important than the first because the base class (a2dViewConnector) handles the first event, but it does know nothing about your application and will show an error if you don't catch the wxEVT_POST_CREATE_VIEW event.
Create a a2dDocumentCommandProcessor at application start-up, i.e. in the OnInit() method of wxApp. Initialize a2dDocviewGlobals with it. You can also use the default a2dDocviewGlobals->GetDocviewCommandProcessor() directly if there is no need for you own one. a2dDocviewGlobals gives also access to a2dEventDistributer.
- Example: Creating the global used objects at application start-up
1 bool MyApp::OnInit()
2 {
3 [...]
4
5 // We hold a pointer to the docview command processor
6 // because we use this pointer later for the doc/view templates
7
8 // Use your own specific derived a2dDocumentCommandProcessor
9 // a2dMultiViewDocManager* docmanager = new a2dMultiViewDocManager();
10 // a2dDocviewGlobals->SetDocviewCommandProcessor( docmanager );
11 // Initialize the <<Dox(a2dDocviewGlobal)>> global object, which gives us access to the command processor and friends
12 // a2dDocviewGlobals->SetDocviewCommandProcessor( docMan );
13 // OR the default
14 <<Dox(a2dDocumentCommandProcessor)>>* docMan = a2dDocviewGlobals->GetDocviewCommandProcessor();
15
16 // Maybe we want to limit the no. of max. opened docs? (default value is 10000, which is enough in several cases ;) )
17 docMan->SetMaxDocsOpen(20);
18
19 [...]
20 }
Create one or more a2dDocumentTemplates and a2dViewTemplates and associate the templates with the a2dDocumentCommandProcessor.
- Example: Associating templates with the a2dDocumentCommandProcessor
1 bool MyApp::OnInit()
2 {
3 [...]
4 // We assume that you've created a <<Dox(a2dDocumentCommandProcessor)>> instance, called "docMan"
5 // Also we assume that you've defined a MyDrawingDoc document (derived from a2dDocument
6 // and a MyTextView and a MyDrawingView which were derived from <<Dox(a2dView)>>. You've also
7 // created a view connector instance called "viewConn" to view only one document at time
8
9 // Pointers to (maybe) several doc / view templates
10 <<Dox(a2dDocumentTemplate)>>* docTpl;
11 <<Dox(a2dViewTemplate)>>* viewTpl;
12
13 // Creation of one template for only one known document
14 docTpl = new <<Dox(a2dDocumentTemplate)>>(
15 wxT("Drawing Document"), // The description shown in the file selector dialog
16 wxT("*.drw"), // The file extension shown in the file selector dialog
17 wxT(""), // The default directory for this doc type
18 wxT("drw"), // The file extension
19 wxT("DrwDoc"), // The doc type description (must be unique to
20 // associate the view tpl with the doc
21 CLASSINFO(MyDrawingDoc), // Your derived <<Dox(a2dDocument)>> class
22 viewConn, // View connector instance to use
23 a2dREFTEMPLATE_VISIBLE, // Template should be visible (shown in file sel. dialogs)
24 new MyIOHandler() // The I/O handler for this document (optional value)
25 );
26
27 // Make the doc template known by our <<Dox(a2dDocumentCommandProcessor)>> instance
28 docMan->AssociateDocTemplate(docTpl);
29
30 // We've two views, so we'll create two view templates
31
32 // Creation of the drawing view template
33 viewTpl = new <<Dox(a2dViewTemplate)>>(
34 wxT("Drw View"), // Description of the view
35 wxT("DrwDoc"), // The unique document type name (see doc template)
36 wxT("Drawing View"), // View name which is shown by the view sel. dialog
37 CLASSINFO(MyDrawingView), // View class (our drawing view)
38 viewConn, // View connector instance
39 a2dREFTEMPLATE_VISIBLE, // Template should be visible (shown in view sel. dialogs)
40 wxSize(200, 300) // Size of the view window / frame
41 // (this is an optional value which you may use on your own,
42 // i.e. to create frames with a constant size)
43 );
44
45 // Associate the <<Dox(a2dDocumentCommandProcessor)>> with the view template
46 docMan->AssociateViewTemplate(viewTpl);
47
48
49 // Creation of the text view template
50 viewTpl = new <<Dox(a2dViewTemplate)>>(
51 wxT("Drw View"),
52 wxT("DrwDoc"),
53 wxT("Text View"), // View name which is shown by the view sel. dialog
54 CLASSINFO(MyTextView), // View class (our text view)
55 viewConn,
56 a2dREFTEMPLATE_VISIBLE,
57 wxSize(200, 450)
58 );
59
60 // Associate the <<Dox(a2dDocumentCommandProcessor)>> with the 2nd view template
61 docMan->AssociateViewTemplate(viewTpl);
62
63 [...]
64 }
The parts of the docview framework
In this section we will have a look at important parts of the framework and explain the task of each part.
First we will have a look at the global used classes: a2dDocumentCommandProcessor, a2dEventDistributer, and a2dDocviewGlobal. After that we introduce the a2dDocument, a2dView and their template classes and explain the scope of the a2dViewConnector.
It is possible to use this framework without some of the mentioned global objects, but this is not the scope of this document and only useful in special cases; so we won't discuss this matter here.
Please have a look at the "nonmanaged" sample which is part of the docview distribution if you are interested to use this framework without the global objects.
The a2dDocumentCommandProcessor
The a2dDocumentCommandProcessor is an object which acts like the wxDocManager of the standard wxWidget doc/view framework but has some properties which are unique.
This class maintains a global docview settings (i.e. how many documents should maximal be opened etc.) and controls in combination with the a2dEventDistributer how the different parts interact within the framework.
An instance of this class should be created at application start-up and added to the a2dDocviewGlobal instance, in order to get to it at any time.
The a2dDocumentCommandProcessor object receives events from child and parent frames, and from views. The class is registered to the a2dEventDistributer, and this way is informed about many events that are distributed in the application. Child and Parent frames in the application sent an event directly to this class if they don't handle the event themselfs. This is a good way to combine generic menu handlers for Opening and closing files into the central a2dDocumentCommandProcessor, instead of handling it in the parent or child frame directly. You should use the standard wxIDs for your menu / toolbar items; please have a look into the wxWidget manual for an overview. The instance of this class also takes care of enabling / disabling the menu / toolbar items (UI updates), this saves you a lot of routine work.
Events like drawing update events from a2dDocument or a2dView are distributed via the global instance of a2dEventDistributer to all registrated classes. This class here does recieve events via the a2dEvtHandler event processing mechanisms from which it is derived. Therefore you can intercept any event sent to it yourself, via a standard wxWidget static event table, and modify its behaviour. The events sent to a2dDocumentCommandProcessor are for common tasks as opening new files, closing files, printing etc. Next to that normal member functions that perform common tasks are gathered here. Think of maintaining the file history.
The most important task of this class is to maintain a record of all open documents, and which of them is the current document. The current document is the document that has a view that currently has the focus or is activated. When no view is active, the last active view its document will stay the active document. Most classes in the docview framework are reference counted. One can therefore not say that one specific class Owns an instance of another. Like here all open documents are known in the a2dDocumentCommandProcessor, and it holds a reference to ( owns ) each one of them, but the same document is also owned by a2dView. In general it is organized in such a manner that the a2dDocumentCommandProcessor is the last to release a document reference.
This class also holds references to all a2dDocumentTemplate and a2dViewTemplate objects used by the application.
In general you don't have to know what this class does in detail, because you will need only some methods of this class (i.e. attaching a file history to a menu). We will discuss some important methods in the section about the a2dDocumentTemplate and the a2dViewTemplate. Please have a look into the class documentation for an API overview.
If you want more specific features you may create your own command processor and attach an instance of the derived class to the a2dDocviewGlobals object at application start-up.
As said before the a2dDocumentCommandProcessor is the heart of the docview framework. But until now it was not clear why it is actually derived from a2dCommandProcessor. The reason is that new commands within an application are going through this class too. One derives from a2dDocumentCommandProcessor and adds functions to implement functionality in form of command like functions. Asume you want to perform a your unique special action on the current active document, clearly the way to do it is via a2dDocumentCommandProcessor, since it know what the current document is. But the action itself might be defined in a derived a2dDocument class, so the new command function calls on the current document the special action on your own type of document. It is often a good idea to add functionality which modifies a document, but which does not require any type of a2dView to do it, in the above described way. To make a well organized application, it is good to be able to execute commands via a commandline interface using command strings. Even if not used much, it makes sure that GUI is well seperated from the actual actions, and makes it easy to implement macro like features. A command function is then a sort of interface to deeper used classes, like documents and views etc. At last the a2dDocumentCommandProcessor, can be used from a2dCommandLanguageWrapper. This last class is the way to wrap scripting languages to the docview framework, and extensions you made via a derived a2dDocumentCommandProcessor.
The a2dEventDistributer
As the name of this class says, this object distributes events that are sent to it to registrated classes instances. The a2dEventDistributer is for example used from the a2dDocument and the a2dView, where it distributes events created by both classes. Where possible communication from one class to another is based on this event distribution, even if a direct call would be possible. This has the advantage that the same event can be intercepted by other classes too, and that the classes do not have to know about each other. An application which only contains one a2dDocument to store some file internal, and makes no use of views or commandprocessors etc., can be handled without a problem.
You may register a wxEventHandler (typically a wxFrame) to catch distributed events. The events which are distributed are discussed later in the events overview. The a2dDocumentCommandProcessor is registered automatically, and communication between the several classes is often via the a2dEventDistributer, which can be reached via a2dDocviewGlobals->GetEventDistributer(). Any class derived from wxEvtHandler or a2dEvtHandler can be registered to the a2dEventDistributer, also any event can be sent to it. So when ever you have the need to be informed about a certain action, find out if there is an event sent to the a2dEventDistributer, register the class, and add in the static event table of your class a handler for that event. Warning
Don't forget to unregister an event handler which you've registered.
Example: Register and unregister a frame to catch events of the docview framework
1
2 // Constructor
3 MyFrame::MyFrame(wxWindow* parent, wxWindowID id, const wxString& title)
4 {
5 [...]
6 // Access the <<Dox(a2dEventDistributer)>> instance through the <<Dox(a2dDocviewGlobal)>> instance
7 // and register itself
8 a2dDocviewGlobals->GetEventDistributer()->Register(this);
9 }
10
11
12 // Destructor
13 MyFrame::~MyFrame()
14 {
15 // Access the <<Dox(a2dEventDistributer)>> instance through the <<Dox(a2dDocviewGlobal)>> instance
16 // and unregister itself
17 a2dDocviewGlobals->GetEventDistributer()->Unregister(this);
18 }
The a2dDocviewGlobal
The a2dDocviewGlobal is a global object which gives access to the a2dDocumentCommandProcessor and the a2dEventDistributer. At application start-up the global available instance of the a2dDocviewGlobal may be initialized. It is already initialized by a default a2dDocumentCommandProcessor, but you can replace it with your own if needed. This object is called a2dDocviewGlobals
Example: Initialization of a2dDocviewGlobals
1 void MyApp::IntitDocView()
2 {
3 // Create the command processor
4 <<Dox(a2dDocumentCommandProcessor)>>* docMan = new <<Dox(a2dDocumentCommandProcessor)>>();
5
6 // Initialize the <<Dox(a2dDocviewGlobal)>> global object, which gives us access to the command processor and friends
7 a2dDocviewGlobals->SetDocviewCommandProcessor( docMan );
8
9 }
a2dDocviewGlobals is defined in doccom.h and doccom.cpp as a global object. a2dDocviewModule OnInit() is called before the application is starting in wxApp::OnInit(). This is where the a2dDocviewGlobal is initiated and assigned to a2dDocviewGlobals. The classes of the framework are looking for the a2dDocviewGlobals object.
After construction of the a2dDocviewGlobal object you have access to the a2dDocumentCommandProcessor-, and the a2dEventDistributer-instance, either the default or the one you have created at application start-up.
Example: Accessing the a2dDocumentCommandProcessor through the a2dDocviewGlobal instance
1 void MyFrame::MyFrame(wxWindow* parent, wxWindowID id, const wxString& title)
2 {
3 [...]
4 // We assume, that you have already created a wxMenu called fileMenu
5
6 // Access the <<Dox(a2dDocumentCommandProcessor)>> instance and attach the file history
7 // to the file menu
8 a2dDocviewGlobals->GetDocviewCommandProcessor()->FileHistoryUseMenu(fileMenu);
9 }
The a2dDocument
This class is the base for modeling the application's data. This data can be read form files and stored back to files, or you can fill the document directly from wiith in the program code. A plain a2dDocument is of no use to store data, it simply does not know how your data is organized. So you need to derive your own document class, and store in there the data using your won classes.
You may want to override the member functions wxInputStream& a2dDocument::LoadObject(wxInputStream& stream) and wxOutputStream& a2dDocument::SaveObject(wxOutputStream& stream) to load and save your document data. This is necessary if you don't define your own a2dIOHandler and attach an instance of the IOhandler to the a2dDocumentTemplate at application start-up.
The rest is up to you: If you want to, you may override several "OnXXX" methods to implement your own behaviour for saving, opening etc. your documents.
The a2dView
The a2dView class can be used to model the viewing and editing component of an application's file-based data (the documents).
It is used to display the data of a a2dDocument object. It is your choice how it displays the document: It may show the whole document or just parts of it. An important advantage of the framework's views is that they are completely independent of the user-interface. A view can even be used to display document data into a bitmap, or to print document data to a printer. So the device that the view uses to display/present the data is not fixed. Still, the most common use is to have a view display its data on a wxWindow. Which can be a window within a frame, but as well a wxDialog. The task of the view is to read the document, and assemble the data from the document that it wants to present. Next it will present that data on the device in some form. So in fact the view translates the data stored in the document, to some form which is displayed to the user via a window. Assuming we have a document with graphical primitives, one view will simply draw the document onto a window, while another view counts the number of colors used in the primitives, and display a table of colors and number of times the colors is used.
Updating a view, is what happens when a document is changed and an update event is sent to all views, in order to have them update their data too. Some view may indeed decide to store internal the data that they do display on a window. Like in the above case, where the color statistics are displayed onto a window. The advantage is that after a paint event ( e.g after removing an overlaping window), the view does not have to read the whole document again to find the color statistics data, instead it simply displays its internal data once more. The same is true for drawing the graphic primitives, when the view draws this into a bitmap first, it can reuse this bitmap to quickly reblit damaged parts caused by on paint events. Here the bitmap is the data that the view stores internal. The bitmap will only be changed/redrawn when the document its data has changed, and when because of that an update event is sent to the view.
One other situation does exist, where the view does not need to store its data. When the view uses a control, which itself internal already stores the data it shows. For instance a wxTextCtrl stores the text that it show internal. On paint events are directly handled by the control itself. In such a case the view only needs to make sure to update that data after the document changes.
The a2dViewConnector takes care of connecting new views to their windows. Several (different or same kind of) views can be connected to one document and display it. Default a view has a pointer to only one document, but it is also possible to have a view display data of several documents.
A view is informed of changes to a document through an update event, which is sent from the document to tell the view that they need to update themselves from the document. The Update event is a a2dDocumentEvent with id wxEVT_UPDATE_VIEWS, and you need to sent it to the a2dEventDistributer class from a a2dDocument when views need to redisplay the modified data. Only the views that use the document that is mentioned in the update event, should update themselves. The update event is always sent to the a2dEventDistributer class, and therefore to all views, since all views are registered to it by default. If a view wants to display data from two documents, it should update itself after an update event from the first one as well as from the other document.
A view is not limited to windows, you may create a view which displays only parts of a document. This might be a properties dialog which show the author, title and some other statistical data in a dialog (properties dialog). But since an update event is sent to all registered class instances of the eventdistributer, you can also directly use a wxDialog derived class. A view is best used to seperate the view its data from the application its interface in form of frames and windows.
The a2dDocumentTemplate
One or more instances of this class should be created at application start-up. When you open a file, the extension of the file or its io handler is used to choose a a2dDocumentTemplate , which on its turn is used for creating a document that is needed to store this file in memory. The document template defines the relation between the file formats and the document types available. Often this is based on the extension of the file. In the file open dialog, you get a list of filters taken from the templates, and this way a certain template is coupled to the file which will be opened. Another way is to use the template its iohandler, which defines how the data needs to be loaded into a specific document. When no extension is available, the io handlers does test the file first, and if it can load the file, that template will be used.
A special a2dDocumentTemplateAuto can be used to automatically search for a suitable iohandler. It searches in the list of document templates in the central a2dDocumentCommandProcessor for a template which can save the document or load the file.
The a2dViewTemplate
The a2dViewTemplate models the relationship of a document and a view. One or more instances of this class should be created at application start-up and made known to the a2dDocumentCommandProcessor . You may create several instances of this class to model the relationship of more than one view to a document type.
A a2dViewTemplate is used from a2dViewConnector . The connector uses it to decide which view templates fit to a document just created, and gives the user the choice for one of them. That is only the default implementation, in a derived connector class, you can create new views, or connect to existing views etc., as long as the view is okay for the document, that is not a problem.
The a2dViewConnector
The key factor in having a2dView 's independent of wxFrame's and wxWindow's is in the a2dViewConnector . Instead of having a wxView generating the windows/frames, this responsibility is shifted to a specific a2dViewConnector . This class handles the connection of a view to the view-device (typically a window).
An application will use a derived class from the a2dViewConnector class, to define how new a2dView instances are connected/coupled to the windows and frames of the application. A few predefined frames and windows, which know how to deal with views, are normally used to connect new views into the application. You use a2dDocumentFrame when the view directly uses the frame to display data from the document. For a scrolled window within a frame, use wxDocFrameScrolledWindow and a2dScrolledWindow. For a normal window within a frame, use a2dDocumentFrame and a2dDocumentViewWindow .
New Views and documents generated by the a2dDocumentTemplate and a2dViewTemplate result in events being sent to a a2dViewConnector ( to which the templates have a pointer ). When a new a2dDocument is generated from a a2dDocumentTemplate , the a2dViewConnector gets an event, and then decides to generate a view via a a2dViewTemplate for the new document. a2dViewTemplate sends another event to the a2dViewConnector , and this allows the connector to plug the new view into the application its windows (either existing frames/window or new created frames and window.)
The a2dView 's are not aware and responsible of frames in the application. The only thing which connect them to the outside world is a pointer to a wxWindow ( a2dDocumentViewWindow or a2dScrolledWindow) . Because of this the views can be used in any type of application without modification.
a2dIOHandlerStrIn
This class serves several pure virtual methods which are used by the framework to load document's data. The goal of this class is, that you create your a2dDocument derived classes independent from loading/saving data. A a2dDocumentTemplate can have two io handlers, one for loading, and one for saving. By connecting a document to I/O handlers via a document template you don't have to model several documents and override the OnLoadObject? and OnSaveObject? methods, but create just another I/O handler.
If you've created a a2dIOHandlerStrIn derived class and create an new instance of it which you attach to a a2dDocumentTemplate , the framework will take care to inform the I/O handler to load/save document's data. Please have a look at its class documentation for further information which methods you'll have to override.
a2dIOHandlerStrOut
This class serves several pure virtual methods which are used by the framework to save document's data. The a2dIOHandlerStrOut is set to a a2dDocumentTemplate , and that is where a a2dDocument uses it from to save data to a file.
Event overview
The figure events in the docview classes.
a2dDocumentViewWindow and a2dDocumentViewScrolledWindow, have event handling that takes care of redirecting events coming from the a2dView up through the hierarchy of window/frames. This is how a parent/top frame in an application can close all documents and its views and eventually destroy all windows that are used by the view.
So the parent close event goes via: docmanager->documents->a2dView ->(a2dCloseViewEvent )->wxDocView(Scrolled)Window->a2dDocumentFrame .
But when closing a child window the close event goes via: a2dDocumentFrame ->a2dView ->(a2dCloseViewEvent )->wxDocView(Scrolled)Window->a2dDocumentFrame
The a2dDocumentFrame will redirect activate events to the view, and in case of a window close event, it will redirecting the event to the view also. a2dDocumentViewWindow and a2dDocumentViewScrolledWindow, will automatically sent the events that or generated by wxWidget to the a2dView (s). The a2dView may generate new events for its DisplayWindow? and a2dDocument or Itself. a2dView can indeed redirect events to its display window, being a wxDocView(Scrolled)Window. Since those events are of type command, they travel up to the parent of the a2dViewWindow<wxWindow>, which is the a2dDocumentFrame in most cases. When a a2dDocumentFrame does not handle the a2dView event, it would sent it back to the a2dView , this would result in looping. Therefore the a2dView disables itself for events until the event it did sent up is processed.
The above has the following advantages:
A derived a2dView which is able to display parts of a derived a2dDocument , can be used in a multiframe as well as a singleframe application.
A derived a2dView which is able to display parts of a derived a2dDocument , can be used in a multiframe as well as a singleframe application.
A view depends on a a2dViewConnector derive class to arrange how and where it is placed. And on which window it needs to display the part of the document it is asked to display.
specialized frames ( e.g. a complete editor) may function as a parent frame or child frame in a single or multiframe application. The a2dViewConnector will take care of connecting the new a2dView 's to it. So these very specialized frame's can go into a library also.
An application in general does not need to derive from specialized a2dView 's or frame's etc. in order to use them. The a2dViewConnector is the one that will connect the view and frames in the way he wants it.
Derived a2dViewConnector classes for several types of applications ( single frame, multi frame, single frame split windows etc. ) can be written, and strored in a library.
Often the only class to derived from ( if necessary), is a a2dViewConnector . This as enough to use the specialized views and documents.
Topics
Closing of Views
How are views closed. There are two routes involved in a multiple frame application. If you want to exit the application all top window/frames need to be destroyed. But when you only want to close one frame, the view displayed in there will be detached from its document and deleted. Several situations are possible. In Closing Frames and Applications it will go into detail.
Use existing views
How can a viewconnector use an existing view instead of creating new views.
If the application has only one wxFrame containing several wxWindow's used for displaying a2dView 's, the connector its task will be again to receive events from the template. But now it will first disconnect an existing view from a window inside the application its only frame. Next it will connect the new view into that window. It may decide to remove the old view and its document, or keep them intact and only disconnect those old views.
Different types of application, MultiFrame? / Single Frame
In an application with one parent wxFrame and for each view a child wxFrame, every new view will get its one a2dDocumentFrame . The a2dDocumentTemplate creates new documents, and a2dViewTemplate the new views. After creations by the templates of a a2dDocument and a a2dView , events are sent to their a2dViewConnector . In a multiframe application the specialized connector its reaction to a newly created document, will be to tell the a view template to also generate the first view for the document. After creation of the view, the connector is informed by an event, and will react by creating a new a2dDocumentFrame and/or a child window wxDoc(Scrolled)ViewWindow. The last window is set as the DisplayWindow? for the new a2dView , this window will be used by the view to display itself. (it is also possible to directly use the a2dDocumentFrame as the display window for the view )
In an MDI like application the types of frame is a a2dDocumentMDIParentFrame containing a2dDocumentMDIChildFrame instances. But for the rest connecting views is equal to the SDI situation.
In a single frame application with only a fixed number of views, which are always shown, a derived viewconnector will not use view templates. Instead it uses the views that where created only once. After creating or openeing a new document, an existing view is connected to the document. If the view is already connected to a document, this document will first be detached/released. One may decide to still create new views, but only connect some views to the windows in the application. The view which are not displayed, will have there display window set to NULL.
In a tabbed/notebook like situation, one may decide to use exsiting views from an existing tab, or create new views, and for each new view create an extra tab.
Reference counting
In most classes pointer reference counting is used, with the a2dSmrtPtr that uses an Own and Release principle. The last owning objectX which owns an objectY will really delete the objectY when it releases that object. All the other Objects owning objectY will just lower the reference count. This way a a2dView Owns a document, but the same document is also owned by the a2dDocumentCommandProcessor . When the view is closed and Released from its a2dDocumentViewWindow , it will also Release its a2dDocument . But the document itself will only really be deleted, when all views on it are released, and the a2dDocumentCommandProcessor has released the document too. In the same manner template classes can be owned by the a2dDocumentCommandProcessor , but also by a a2dViewConnector at the same time. Another useful trick is to Own a a2dView one time extra at the beginning of an event processing, and release it again at the end of the event processing. When the event leads to Releasing the view from its window ( for instance the view its window was closed ), this will only lead to real deletion of the view itself when the event processing is completely finished. This because the last Release that will lead to the reference count reaching zero, is at the end of the event processing routine.
Two Templates
There are two types of templates, the first a2dDocumentTemplate tells what files/file extensions belong to which type of a2dDocument . It can also define what a2dIOHandlerStrIn needs to be used for reading this document from a file, and what a2dIOHandlerStrOut needs to be used for writing the document to a file. So several templates can exist for the same type of document, but each for different in and output formats. The second a2dViewTemplate defines which types of views can be generated for a certain a2dDocument . In general all those templates or associated with the a2dDocumentCommandProcessor . It is possible to attach several a2dViewTemplate 's directly to a a2dViewConnector , in case one wants to limit the types of views for a certain connector type. Since templates are reference counted, one view template can be owned by several view connectors and the a2dDocumentCommandProcessor . Both types of templates do have a pointer to a specific a2dViewConnector , and this connector is used to connect new documents and views into the application.
- New documents are generated via a2dDocTemplate's.
New views are generated via the a2dViewTemplate 's.
How the new views are connected into the application its windows is defined in a a2dViewConnector derived class.
Design considerations of the general and docview module
* Why is there a a2dObject
The a2dObject class is derived from wxObject.
Smart pointers are used all over the wxArt2D library. They have many advantages, but the main one is that deletion is done when the last smart pointer holding on to an object is getting out of scope. In other words, deletion is automatic. And this helps a lot in designing a robust docview framework. To name a simple one: a document is deleted only when all smart pointer to it are deleted.
For using smart pointers one needs in the object that the smart pointer is pointing to a reference count. When it reaches zero the object will be deleted. That is why there is a2dObject. Next to this a2dObject is the base class for almost all objects in the library, and the serialization using XML needs it. Especially since wxArt2D its graphical documents, contain multiple references to graphical canvas objects, and this requires a special approach when saving to save such objects.
No the bad thing. If a derived a2dObject class needs event handling, wxEvtHandler is not possible anymore. And so all event handling in wxWidgets itself, needs to be copied but this time with a2dObject in between.
This holds all need to store dynamic properties, in is in between a2dObject and a2dEvtHandler, because i wanted to prevent that all object needing properties would automatically get event handling.
A light wait event handler for canvas object was needed. It is normal to store thousands of objects in a canvas document, doing this using wxEvtHandler is no good. But even if anted, it would not be possible, because a2dObject is needed for its reference counting for smart pointers, and the serialization. As said a2dObject is used as base class for almost all classes. Therefore if such a class needs event handling it nees to derive from a2dEvtHandler. This is why most classes in the docview framework use a2dEvtHandler as base, and its event tables are defined accordingly. Still the whole wxEvtHandler code is very simular to the a2dEvtHandler code.