DocumentView Basics

team bbl


Document/View Basics

The document/view system is found in most application frameworks because it can greatly simplify the code required to build many kinds of applications.

The idea is that you can model your application primarily in terms of documents, which store data and provide GUI-independent operations upon it, and views, which display and manipulate the data. It's similar to the Model-View-Controller model (MVC), but here the concept of controller is not separate from the view.

wxWidgets can provide many user interface elements and behaviors based on this architecture. After you have defined your own classes and the relationships between them, the framework takes care of showing file selectors, opening and closing files, asking the user to save modifications, routing menu commands to appropriate code, and even some default print and print preview functionality and support for command undo and redo. The framework is highly modular, enabling the application to override and replace functionality and objects to achieve more than the default behavior.

These are the main steps involved in creating an application based on the document/view framework, once you've decided that this model is appropriate for your application. The ordering is to some extent arbitrary: for example, you may choose to write your document class before thinking about how a document will be presented in the application.

  1. Decide what style of interface you will use: Microsoft's MDI (multiple document child frames surrounded by an overall frame), SDI (a separate, unconstrained frame for each document), or single-window (one document open at a time, as in Windows Write).

  2. Use the appropriate parent and child frame classes, based on the previous decision, such as wxDocParentFrame and wxDocChildFrame. Construct an instance of the parent frame class in your wxApp::OnInit, and a child frame class (if not single-window) when you initialize a view. Create menus using standard menu identifiers such as wxID_OPEN and wxID_PRINT.

  3. Define your own document and view classes, overriding a minimal set of member functions for input/output, drawing, and initialization. If you need undo/redo, implement it as early as possible, instead of trying to retrofit your application with it later.

  4. Define any subwindows (such as a scrolled window) that are needed for the view(s). You may need to route some events to views or documents; for example, your paint handler needs to be routed to wxView::OnDraw.

  5. Construct a single wxDocManager instance at the beginning of your wxApp::OnInit and then as many wxDocTemplate instances as necessary to define relationships between documents and views. For a simple application, there will be just one wxDocTemplate.

We will illustrate these steps using a little application called Doodle (see Figure 19-1). As its name suggests, it supports freehand drawing in a window, with the ability to save and load your doodles. It also supports simple undo and redo.

Figure 19-1. Doodle in action


Step 1: Choose an Interface Style

Traditionally, multi-document applications on Windows have used MDI, as described in the section "wxMDIParentFrame" in Chapter 4, "Window Basics." A parent frame manages and encloses a series of document child frames, with the menu bar reflecting the commands available in either the active child frame or the parent frame if there are no child frames.

An alternative to MDI is to dispense with the concept of a "main" window and show each document in a new frame that can be moved around the desktop without constraint. This is the usual style on Mac OS, although on Mac OS, only one menu bar is shown at a time (that of the active window). Another twist on Mac OS is that unlike on other platforms, the user does not expect the application to exit when the last window is closed: there is normally an application menu bar with a reduced set of commands that shows without a window being visible. Currently, to achieve this behavior in wxWidgets, you must create an off-screen frame, whose menu bar will automatically be shown if all visible windows have been destroyed.

A variation of this technique is to show a main window in addition to separate document frames. However, it is quite rare for this to be a useful model. Yet another variation is to only show document windows, but when the last document is closed, the window remains open to let the user open or create documents. This model is employed by recent versions of Microsoft Word and is similar to the Mac OS model, except that on Mac OS where there are no documents open, there is no visible frame, only a menu bar.

Perhaps the simplest model is to have one main window, no separate child frames, and only one document open at a time: an example is Windows WordPad. This is the style chosen for our Doodle application.

Finally, you can create your own style, perhaps combining different elements of the others. DialogBlocks is an example of an application that mixes styles to give the user different ways of working. The most common way of working with DialogBlocks is to show one view at a time. When you select a document in the project tree, the current view is hidden, and the new view is shown. Optionally, you can enable tabs for quick switching between the set of the documents you're most interested in. In addition, you can drag the view's title bar to the desktop to undock the view and see multiple documents simultaneously. Internally, DialogBlocks manages the relationships between documents, views, and windows differently from the standard wxWidgets document/view management. Obviously, creating your own document management system is time-consuming, so you will probably want to choose one of the standard methods.

Step 2: Create and Use Frame Classes

For an MDI application, you will use wxDocMDIParentFrame and wxDocMDIChildFrame. For an application with one main frame and multiple separate document frames, use wxDocParentFrame and wxDocChildFrame. If you have a main frame and only one document shown at a time, use wxDocParentFrame.

If your application does not have a main frame, just multiple document frames, use either wxDocParentFrame or wxDocChildFrame. However, if using wxDocParentFrame, intercept the window close event handler (EVT_CLOSE) and delete all the views for the associated document because the default behavior is to delete all views known to the document manager (which will close all documents).

Here's our frame class definition. We store a pointer to the doodle canvas and a pointer to the edit menu that will be associated with the document command processor so the system can update the Undo and Redo menu items.

 // Define a new frame class DoodleFrame: public wxDocParentFrame {     DECLARE_CLASS(DoodleFrame)     DECLARE_EVENT_TABLE() public:     DoodleFrame(wxDocManager *manager, wxFrame *frame, wxWindowID id,          const wxString& title, const wxPoint& pos,         const wxSize& size, long type);     /// Show About box     void OnAbout(wxCommandEvent& event);     /// Get edit menu     wxMenu* GetEditMenu() const { return m_editMenu; }     /// Get canvas     DoodleCanvas* GetCanvas() const { return m_canvas; } private:     wxMenu *        m_editMenu;     DoodleCanvas*   m_canvas; }; 

The implementation of DoodleFrame is shown next. The constructor creates a menu bar and a DoodleCanvas object with a pencil cursor. The file menu is passed to the manager object to be used for displaying recently used files.

 IMPLEMENT_CLASS(DoodleFrame, wxDocParentFrame) BEGIN_EVENT_TABLE(DoodleFrame, wxDocParentFrame)     EVT_MENU(DOCVIEW_ABOUT, DoodleFrame::OnAbout) END_EVENT_TABLE() DoodleFrame::DoodleFrame(wxDocManager *manager, wxFrame *parent,                   wxWindowID id, const wxString& title,                  const wxPoint& pos, const wxSize& size, long type): wxDocParentFrame(manager, parent, id, title, pos, size, type) {     m_editMenu = NULL;     m_canvas = new DoodleCanvas(this,                     wxDefaultPosition, wxDefaultSize, 0);     m_canvas->SetCursor(wxCursor(wxCURSOR_PENCIL));     // Give it scrollbars     m_canvas->SetScrollbars(20, 20, 50, 50);     m_canvas->SetBackgroundColour(*wxWHITE);     m_canvas->ClearBackground();         // Give it an icon     SetIcon(wxIcon(doodle_xpm));     // Make a menu bar     wxMenu *fileMenu = new wxMenu;     wxMenu *editMenu = (wxMenu *) NULL;     fileMenu->Append(wxID_NEW, wxT("&New..."));     fileMenu->Append(wxID_OPEN, wxT("&Open..."));     fileMenu->Append(wxID_CLOSE, wxT("&Close"));     fileMenu->Append(wxID_SAVE, wxT("&Save"));     fileMenu->Append(wxID_SAVEAS, wxT("Save &As..."));     fileMenu->AppendSeparator();     fileMenu->Append(wxID_PRINT, wxT("&Print..."));     fileMenu->Append(wxID_PRINT_SETUP, wxT("Print &Setup..."));     fileMenu->Append(wxID_PREVIEW, wxT("Print Pre&view"));     editMenu = new wxMenu;     editMenu->Append(wxID_UNDO, wxT("&Undo"));     editMenu->Append(wxID_REDO, wxT("&Redo"));     editMenu->AppendSeparator();     editMenu->Append(DOCVIEW_CUT, wxT("&Cut last segment"));     m_editMenu = editMenu;     fileMenu->AppendSeparator();     fileMenu->Append(wxID_EXIT, wxT("E&xit"));     wxMenu *helpMenu = new wxMenu;     helpMenu->Append(DOCVIEW_ABOUT, wxT("&About"));     wxMenuBar *menuBar = new wxMenuBar;     menuBar->Append(fileMenu, wxT("&File"));     menuBar->Append(editMenu, wxT("&Edit"));     menuBar->Append(helpMenu, wxT("&Help"));     // Associate the menu bar with the frame     SetMenuBar(menuBar);     // A nice touch: a history of files visited. Use this menu.     manager->FileHistoryUseMenu(fileMenu); } void DoodleFrame::OnAbout(wxCommandEvent& WXUNUSED(event) ) {     (void)wxMessageBox(wxT("Doodle Sample\n(c) 2004, Julian Smart"),                        wxT("About Doodle")); } 

Step 3: Define Your Document and View Classes

Your document class should have a default constructor, and you should use the DECLARE_DYNAMIC_CLASS and IMPLEMENT_DYNAMIC_CLASS to enable the framework to create document objects on demand. (If you don't supply these, you should instead override wxDocTemplate::CreateDocument.)

You also need to tell the framework how the object will be saved and loaded. You can override SaveObject and LoadObject if you will be using wxWidgets streams, as in our example. Or if you want to implement file handling yourself, override DoSaveDocument and DoOpenDocument, which take a file name argument instead of a stream. wxWidgets streams are described in Chapter 14, "Files and Streams."

Note

The framework does not use temporary files when saving data. This is a good reason for overriding DoSaveDocument and making a stream out of a wxTempFile, as described in Chapter 14.


Here's our DoodleDocument declaration:

 /*  * Represents a doodle document  */ class DoodleDocument: public wxDocument {     DECLARE_DYNAMIC_CLASS(DoodleDocument) public:     DoodleDocument() {};     ~DoodleDocument();     /// Saves the document     wxOutputStream& SaveObject(wxOutputStream& stream);     /// Loads the document     wxInputStream& LoadObject(wxInputStream& stream);     inline wxList& GetDoodleSegments() { return m_doodleSegments; }; private:     wxList m_doodleSegments; }; 

Your document class will also contain the data that's appropriate to the document type. In our case, we have a list of doodle segments, each representing the lines drawn in a single gesture while the mouse button was down. The segment class knows how to stream itself, which makes it simpler to implement the document's streaming functions. Here are the declarations for segments and lines.

 /*  * Defines a line from one point to the other  */ class DoodleLine: public wxObject { public:     DoodleLine(wxInt32 x1 = 0, wxInt32 y1 = 0,                wxInt32 x2 = 0, wxInt32 y2 = 0)     { m_x1 = x1; m_y1 = y1; m_x2 = x2; m_y2 = y2; }     wxInt32 m_x1;     wxInt32 m_y1;     wxInt32 m_x2;     wxInt32 m_y2; }; /*  * Contains a list of lines: represents a mouse-down doodle  */ class DoodleSegment: public wxObject { public:     DoodleSegment(){};     DoodleSegment(DoodleSegment& seg);     ~DoodleSegment();     void Draw(wxDC *dc);     /// Saves the segment     wxOutputStream& SaveObject(wxOutputStream& stream);     /// Loads the segment     wxInputStream& LoadObject(wxInputStream& stream);     /// Gets the lines     wxList& GetLines() { return m_lines; } private:      wxList m_lines; }; 

The DoodleSegment class knows how to render an instance of itself to a device context by virtue of its Draw function. This will help when implementing the doodle drawing code.

Here's the implementation of these classes.

 /*  * DoodleDocument  */ IMPLEMENT_DYNAMIC_CLASS(DoodleDocument, wxDocument) DoodleDocument::~DoodleDocument() {     WX_CLEAR_LIST(wxList, m_doodleSegments); } wxOutputStream& DoodleDocument::SaveObject(wxOutputStream& stream) {     wxDocument::SaveObject(stream);     wxTextOutputStream textStream( stream );     wxInt32 n = m_doodleSegments.GetCount();     textStream << n << wxT('\n');     wxList::compatibility_iterator node = m_doodleSegments.GetFirst();     while (node)     {         DoodleSegment *segment = (DoodleSegment *)node->GetData();         segment->SaveObject(stream);         textStream << wxT('\n');         node = node->GetNext();     }     return stream; } wxInputStream& DoodleDocument::LoadObject(wxInputStream& stream) {     wxDocument::LoadObject(stream);     wxTextInputStream textStream( stream );     wxInt32 n = 0;     textStream >> n;     for (int i = 0; i < n; i++)     {         DoodleSegment *segment = new DoodleSegment;         segment->LoadObject(stream);         m_doodleSegments.Append(segment);     }     return stream; } /*  * DoodleSegment  */ DoodleSegment::DoodleSegment(DoodleSegment& seg) {     wxList::compatibility_iterator node = seg.GetLines().GetFirst();     while (node)     {         DoodleLine *line = (DoodleLine *)node->GetData();         DoodleLine *newLine = new DoodleLine(line->m_x1, line->m_y1, line->m_x2, line->m_y2);         GetLines().Append(newLine);         node = node->GetNext();     } } DoodleSegment::~DoodleSegment() {     WX_CLEAR_LIST(wxList, m_lines); } wxOutputStream &DoodleSegment::SaveObject(wxOutputStream& stream) {     wxTextOutputStream textStream( stream );     wxInt32 n = GetLines().GetCount();     textStream << n << wxT('\n');     wxList::compatibility_iterator node = GetLines().GetFirst();     while (node)     {         DoodleLine *line = (DoodleLine *)node->GetData();         textStream             << line->m_x1 << wxT(" ")             << line->m_y1 << wxT(" ")             << line->m_x2 << wxT(" ")             << line->m_y2 << wxT("\n");         node = node->GetNext();     }     return stream; } wxInputStream &DoodleSegment::LoadObject(wxInputStream& stream) {     wxTextInputStream textStream( stream );     wxInt32 n = 0;     textStream >> n;     for (int i = 0; i < n; i++)     {         DoodleLine *line = new DoodleLine;         textStream             >> line->m_x1             >> line->m_y1             >> line->m_x2             >> line->m_y2;         GetLines().Append(line);     }     return stream; } void DoodleSegment::Draw(wxDC *dc) {     wxList::compatibility_iterator node = GetLines().GetFirst();     while (node)     {         DoodleLine *line = (DoodleLine *)node->GetData();         dc->DrawLine(line->m_x1, line->m_y1, line->m_x2, line->m_y2);         node = node->GetNext();     } } 

So far, we haven't touched on how doodle segments actually get added to the document, except by loading them from a file. We can model the commands that will modify the document independently of the code that interprets mouse or keyboard input, and this is the key to implementing undo and redo. DoodleCommand is a class deriving from wxCommand: it implements the Do and Undo virtual functions that will be called by the framework at the appropriate point. So instead of directly modifying the document, the input event handlers will create a DoodleCommand with the appropriate information and submit it to the document's command processor (an instance of wxCommandProcessor). The command processor stores the command on an undo/redo stack before calling Do for the first time. The command processor is automatically created when the framework initializes the document, which is why you don't see it being created explicitly in this example.

Here is DoodleCommand's declaration:

 /*  * A doodle command  */ class DoodleCommand: public wxCommand { public:     DoodleCommand(const wxString& name, int cmd, DoodleDocument *doc, DoodleSegment *seg);     ~DoodleCommand();     /// Overrides     virtual bool Do();     virtual bool Undo();     /// Combine do/undo code since the commands are symmetric     bool DoOrUndo(int cmd); protected:     DoodleSegment*  m_segment;     DoodleDocument* m_doc;     int             m_cmd; }; /*  * Doodle command identifiers  */ #define DOODLE_CUT          1 #define DOODLE_ADD          2 

We define two commands, DOODLE_ADD and DOODLE_CUT. The user can delete the last segment or add a segment by drawing. Here, the two commands are both represented by the same command class, but this doesn't have to be the case. Each command object stores a pointer to the document, a segment object, and the command identifier. The implementation for DoodleCommand is:

 /*  * DoodleCommand  */ DoodleCommand::DoodleCommand(const wxString& name, int command,                               DoodleDocument *doc, DoodleSegment *seg):     wxCommand(true, name) {     m_doc = doc;     m_segment = seg;     m_cmd = command; } DoodleCommand::~DoodleCommand() {     if (m_segment)         delete m_segment; } bool DoodleCommand::Do() {     return DoOrUndo(m_cmd); } bool DoodleCommand::Undo() {     switch (m_cmd)     {     case DOODLE_ADD:         {             return DoOrUndo(DOODLE_CUT);         }     case DOODLE_CUT:         {             return DoOrUndo(DOODLE_ADD);         }     }     return true; } bool DoodleCommand::DoOrUndo(int cmd) {     switch (cmd)     {     case DOODLE_ADD:         {             wxASSERT( m_segment != NULL );             if (m_segment)                 m_doc->GetDoodleSegments().Append(m_segment);             m_segment = NULL;             m_doc->Modify(true);             m_doc->UpdateAllViews();             break;         }     case DOODLE_CUT:         {             wxASSERT( m_segment == NULL );             // Cut the last segment             if (m_doc->GetDoodleSegments().GetCount() > 0)             {                 wxList::compatibility_iterator node = m_doc->GetDoodleSegments().GetLast();                 m_segment = (DoodleSegment *)node->GetData();                 m_doc->GetDoodleSegments().Erase(node);                 m_doc->Modify(true);                 m_doc->UpdateAllViews();             }             break;         }     }     return true; } 

Because Do and Undo share code in our example, we have factored it out into the function DoOrUndo. So to get an undo of a DOODLE_ADD, we use the code for doing DOODLE_CUT. To get the undo of a DOODLE_CUT, we use the code for doing a DOODLE_ADD.

When a segment is added (or a cut command is undone), DoOrUndo simply adds the segment to the document's segment list and clears the pointer to it in the command object so that it doesn't get deleted when the command object is destroyed. A cut (or undo for an add command) removes the last segment from the document's list but keeps a pointer to it in case the command needs to be reversed. DoOrUndo also marks the document as modified (so that closing the application will cause a save prompt) and tells the document to update all of its views.

To define your view class, derive from wxView and again use the dynamic object creation macros. Override at least OnCreate, OnDraw, OnUpdate, and OnClose.

OnCreate is called by the framework when the view and document objects have just been created: at this point, you may need to create a frame and associate it with the view using SetFrame.

OnDraw takes a pointer to a wxDC object and implements drawing. In fact, this doesn't have to be implemented, but if drawing is implemented another way, the default print/preview behavior will not work.

OnUpdate is called with a pointer to the view that caused the update and an optional hint object to help the view optimize painting, and it notifies the view that it should be updated. OnUpdate is typically called when a command has changed the document contents and all views need to be redrawn appropriately. The application can request an update with wxDocument::UpdateAllViews.

OnClose is called when the view is about to be deleted. The default implementation calls wxDocument::OnClose to close the associated document.

Here is the declaration of our DoodleView class. As well as overriding the functions mentioned previously, it handles the DOODLE_CUT command via OnCut. The reason why DOODLE_ADD is not represented in the view's event table is that a segment is added with a mouse gesture, and mouse events are handled by DoodleCanvas, as we'll see shortly.

 /*  * DoodleView mediates between documents and windows  */ class DoodleView: public wxView {     DECLARE_DYNAMIC_CLASS(DoodleView)     DECLARE_EVENT_TABLE() public:     DoodleView() { m_frame = NULL; }     ~DoodleView() {};     /// Called when the document is created     virtual bool OnCreate(wxDocument *doc, long flags);     /// Called to draw the document     virtual void OnDraw(wxDC *dc);     /// Called when the view needs to be updated     virtual void OnUpdate(wxView *sender, wxObject *hint = NULL);     /// Called when the view is closed     virtual bool OnClose(bool deleteWindow = true);     /// Processes the cut command     void OnCut(wxCommandEvent& event); private:     DoodleFrame*    m_frame; }; 

The following is the implementation of DoodleView.

[View full width]

IMPLEMENT_DYNAMIC_CLASS(DoodleView, wxView) BEGIN_EVENT_TABLE(DoodleView, wxView) EVT_MENU(DOODLE_CUT, DoodleView::OnCut) END_EVENT_TABLE() // What to do when a view is created. bool DoodleView::OnCreate(wxDocument *doc, long WXUNUSED(flags)) { // Associate the appropriate frame with this view. m_frame = GetMainFrame(); SetFrame(m_frame); m_frame->GetCanvas()->SetView(this); // Make sure the document manager knows that this is the // current view. Activate(true); // Initialize the edit menu Undo and Redo items doc->GetCommandProcessor()->SetEditMenu(m_frame->GetEditMenu()); doc->GetCommandProcessor()->Initialize(); return true; } // This function is used for default print/preview // as well as drawing on the screen. void DoodleView::OnDraw(wxDC *dc) { dc->SetFont(*wxNORMAL_FONT); dc->SetPen(*wxBLACK_PEN); wxList::compatibility_iterator node = ((DoodleDocument *)GetDocument ())->GetDoodleSegments().GetFirst(); while (node) { DoodleSegment *seg = (DoodleSegment *)node->GetData(); seg->Draw(dc); node = node->GetNext(); } } void DoodleView::OnUpdate(wxView *WXUNUSED(sender), wxObject *WXUNUSED(hint)) { if (m_frame && m_frame->GetCanvas()) m_frame->GetCanvas()->Refresh(); } // Clean up windows used for displaying the view. bool DoodleView::OnClose(bool WXUNUSED(deleteWindow)) { if (!GetDocument()->Close()) return false; // Clear the canvas m_frame->GetCanvas()->ClearBackground(); m_frame->GetCanvas()->SetView(NULL); if (m_frame) m_frame->SetTitle(wxTheApp->GetAppName()); SetFrame(NULL); // Tell the document manager to stop routing events to the view Activate(false); return true; } void DoodleView::OnCut(wxCommandEvent& WXUNUSED(event)) { DoodleDocument *doc = (DoodleDocument *)GetDocument(); doc->GetCommandProcessor()->Submit( new DoodleCommand(wxT("Cut Last Segment"), DOODLE_CUT, doc, NULL)); }

Step 4: Define Your Window Classes

You will probably need to create specialized editing windows for manipulating the data in your views. In the Doodle example, DoodleCanvas is used for displaying the data, and to intercept the relevant events, the wxWidgets event system requires us to create a derived class. The DoodleCanvas class declaration looks like this:

 /*  * DoodleCanvas is the window that displays the doodle document  */ class DoodleView; class DoodleCanvas: public wxScrolledWindow {     DECLARE_EVENT_TABLE() public:     DoodleCanvas(wxWindow *parent, const wxPoint& pos,                  const wxSize& size, const long style);     /// Draws the document contents     virtual void OnDraw(wxDC& dc);     /// Processes mouse events     void OnMouseEvent(wxMouseEvent& event);     /// Set/get view     void SetView(DoodleView* view) { m_view = view; }     DoodleView* GetView() const { return m_view; } protected:     DoodleView *m_view; }; 

DoodleCanvas contains a pointer to the view (initialized by DoodleView::OnCreate) and handles drawing and mouse events. Here's the implementation of the class.

 /*  * Doodle canvas implementation  */ BEGIN_EVENT_TABLE(DoodleCanvas, wxScrolledWindow)     EVT_MOUSE_EVENTS(DoodleCanvas::OnMouseEvent) END_EVENT_TABLE() // Define a constructor DoodleCanvas::DoodleCanvas(wxWindow *parent, const wxPoint& pos,                            const wxSize& size, const long style):     wxScrolledWindow(parent, wxID_ANY, pos, size, style) {     m_view = NULL; } // Define the repainting behavior void DoodleCanvas::OnDraw(wxDC& dc) {     if (m_view)         m_view->OnDraw(& dc); } // This implements doodling behavior. Drag the mouse using // the left button. void DoodleCanvas::OnMouseEvent(wxMouseEvent& event) {     // The last position     static int xpos = -1;     static int ypos = -1;     static DoodleSegment *currentSegment = NULL;         if (!m_view)         return;     wxClientDC dc(this);     DoPrepareDC(dc);     dc.SetPen(*wxBLACK_PEN);     // Take into account scrolling         wxPoint pt(event.GetLogicalPosition(dc));     if (currentSegment && event.LeftUp())     {         if (currentSegment->GetLines().GetCount() == 0)         {             delete currentSegment;             currentSegment = NULL;         }         else         {             // We've got a valid segment on mouse left up, so store it.             DoodleDocument *doc = (DoodleDocument *) GetView()->GetDocument();             doc->GetCommandProcessor()->Submit(                 new DoodleCommand(wxT("Add Segment"), DOODLE_ADD, doc, currentSegment));             GetView()->GetDocument()->Modify(true);             currentSegment = NULL;         }     }     if (xpos > -1 && ypos > -1 && event.Dragging())     {         if (!currentSegment)             currentSegment = new DoodleSegment;         DoodleLine *newLine = new DoodleLine;         newLine->m_x1 = xpos;          newLine->m_y1 = ypos;         newLine->m_x2 = pt.x;          newLine->m_y2 = pt.y;         currentSegment->GetLines().Append(newLine);         dc.DrawLine(xpos, ypos, pt.x, pt.y);     }     xpos = pt.x;     ypos = pt.y; } 

As you can see, when the mouse handling code detects that user has drawn a segment, a DOODLE_ADD command is submitted to the document, where it is stored in case the user wants to undo (and perhaps later redo) the command. In the process, the segment is added to the document's segment list.

Step 5: Use wxDocManager and wxDocTemplate

You will need to create an instance of wxDocManager that exists for the lifetime of the application. wxDocManager has overall responsibility for handling documents and views.

You will also need at least one wxDocTemplate object. This class is used to model the relationship between a document class and a view class. The application creates a document template object for each document/view pair, and the list of document templates managed by the wxDocManager instance is used to create documents and views. Each document template knows what file filters and default extension are appropriate for a document/view combination and how to create a document or view.

For example, if there were two views of the data in a Doodle documenta graphical view and a list of segmentsthen we would create two view classes (DoodleGraphicView and DoodleListView). We would also need two document templates, one for the graphical view and another for the list view. You would pass the same document class and default file extension to both document templates, but each would be passed a different view class. When the user clicks on the Open menu item, the file selector is displayed with a list of possible file filters, one for each wxDocTemplate. Selecting the filter selects the wxDocTemplate, and when a file is selected, that template will be used for creating a document and view. Similarly, when the user selects New, wxWidgets will offer a choice of templates if there is more than one. In our Doodle example, where there is only one document type and one view type, a single document template is constructed, and dialogs will be appropriately simplified.

You can store a pointer to a wxDocManager in your application class, but there's usually no need to store a pointer to the template objects because these are managed by wxDocManager. Here's the DoodleApp class declaration:

 /*  * Declare an application class  */ class DoodleApp: public wxApp { public:     DoodleApp();     /// Called on app initialization     virtual bool OnInit();     /// Called on app exit     virtual int OnExit(); private:     wxDocManager* m_docManager; }; DECLARE_APP(DoodleApp) 

In the DoodleApp implementation, the wxDocManager object is created in OnInit, along with a document template object associating the DoodleDocument and DoodleView classes. We pass to the template object the document manager, a template description, a file filter to use in file dialogs, a default directory to use in file dialogs (here it's empty), a default extension for the document (drw), unique type names for the document and view classes, and finally the class information for document and view classes. The DoodleApp class implementation is shown in the following.

[View full width]

IMPLEMENT_APP(DoodleApp) DoodleApp::DoodleApp() { m_docManager = NULL; } bool DoodleApp::OnInit() { // Create a document manager m_docManager = new wxDocManager; // Create a template relating drawing documents to their views (void) new wxDocTemplate(m_docManager, wxT("Doodle"), wxT("*.drw"), wxT(""), wxT ("drw"), wxT("Doodle Doc"), wxT("Doodle View"), CLASSINFO(DoodleDocument), CLASSINFO(DoodleView)); // Register the drawing document type on Mac #ifdef __WXMAC__ wxFileName::MacRegisterDefaultTypeAndCreator( wxT("drw") , 'WXMB' , 'WXMA' ) ; #endif // If we have only got one window, we only get to edit // one document at a time. m_docManager->SetMaxDocsOpen(1); // Create the main frame window DoodleFrame* frame = new DoodleFrame(m_docManager, NULL, wxID_ANY, wxT("Doodle Sample"), wxPoint(0, 0), wxSize(500, 400), wxDEFAULT_FRAME_STYLE); frame->Centre(wxBOTH); frame->Show(true); SetTopWindow(frame); return true; } int DoodleApp::OnExit() { delete m_docManager; return 0; }

Because we only show one document at a time, we need to tell the document manager this by calling SetMaxDocsOpen with a value of 1. To cater for Mac OS, we also add the MacRegisterDefaultTypeAndCreator call to tell Mac OS about this document type. The function takes the file extension, a document type identifier, and a creator identifier. (It is customary to choose four-letter identifiers for the document type and the application that creates it. You can optionally register them on the Apple web site to avoid potential clashes.)

For the complete Doodle application, please see examples/chap19/doodle.

    team bbl



    Cross-Platform GUI Programming with wxWidgets
    Cross-Platform GUI Programming with wxWidgets
    ISBN: 0131473816
    EAN: 2147483647
    Year: 2005
    Pages: 262

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net