Document/View and you

Back Up Next

(Cover OWL 2.5)

Table of contents:

Introduction
Doc/View class reference
Introduction to Doc/View
TDocument:
TView
TWindowView
TListView
TEditView
TOleView
TOleLinkView Class
TFileDocument
TStorageDocument |
TOleDocument
TDocManager
TDocTemplate and TDocTemplateT<D, V>
TStream, TInStream, TOutStream
Support for Doc/View in other classes:
    TApplication
    TMenuDescr
Events and how to make them
Frequently asked Questions
Common Pitfalls with Doc/View
Glossary
Appendix 1: Modes
Appendix 2: Template constants
Appendix 3: Property flags
Appendix 4: Borland C++ and OWL versions
Resources for OWL programmers
© Copyright

Introduction

Here are the basics to Doc/View, as far as I can figure them. I suspect they are pretty hard to figure out, as none of the aftermarket BC4 books explain them, except Swan’s. They seem to be discussed only in the OWL manuals, in the on-line help, and with comments in the header files and source code itself. The source code seems to be the place to look to figure out Doc/View, as the other material is a little sparse at best.

To explain where a bit of information comes from, I have used the following convention. "1)" means copied directly from the help text, "2)" means from the source code, and "3)" means my own explanation.

This document assumes that you are an OWL user and have compiled and run the DOCVIEW example on the BC4 disks and have worked through the tutorial. It was at that point that I started pulling my hair out trying to figure out TDocument and TDocument and it is there that I begin:

 

Doc/View class reference

Document and View classes
Other Doc/View-related classes:
   TDocManager
    TDocTemplate
    TDocTemplateT<D,V>
    TOleDocViewFactory<T>
    TOleDocViewAutoFactory<T>
    TOleFactoryDocView<T,Auto>
    TStream, TInStream, TOutStream

 

Introduction to Doc/View

Adopting the Document/View model makes it easier to develop large, flexible, document-centric GUI applications. Doc/View users get a big win developing programs which either have multiple windows open on the same set of data or the ability to switch the format of the screen dramatically over the same set of data.

If you have never seen what Doc/View does, compile and run the \bc45\examples\owl\owlapi\docview.ide example program. Open up a "Draw Line File", then select Window|Add View and add two more line draw views and also add a drawlist view. Tile the windows, and start scribbling in one of the drawing views.

By using the Edit menu items in the DrawList view, each line’s properties can be edited individually. Being able to work with data more than one way can be very satisfying. Data can be viewed as both a graph or a spreadsheet, for example. The "data" would be considered the Document, and the graph and the spreadsheet both would be kinds of views. This terminology is unfortunately confusing. A word processing application wants to give the user the feeling of working directly with the document but it can feel at first as if instead the user is removed from the document and working with a ‘view’. In a well designed program, though, the user feels as if he is directly manipulating the document in each view, which is of course what is actually happening.

The DOCVIEW.IDE example is an outgrowth of the OWL tutorial. There is a lot of really good stuff in the tutorial, and if you are new to Doc/View I recommend working on the step where Doc/View is added to the tutorial.

How does it work?

In the scribble application, pressing down on the mouse button starts drawing, and moving the mouse with the button down adds points to the line. When the mouse button is released, this function is called:

void TDrawView::EvLButtonUp(UINT, TPoint&)
{
    if (DragDC) {
        ReleaseCapture();

    if (Line->GetItemsInContainer() > 1)
        DrawDoc->AddLine(*Line);

    Line->Flush();

    delete DragDC;

    delete Pen;

    DragDC = 0;

    }
}

The only thing new about this function is the call to TDrawDocument::AddLine(). This function looks like this:

int TDrawDocument::AddLine(TLine& line)
{
    int index = Lines->GetItemsInContainer();
    Lines->Add(line);
    SetDirty(true);
    NotifyViews(vnDrawAppend, index);
    UndoState = UndoAppend;
    return index;
}

First, the new line is added to the documents line data structure, as the lines are kept in the document as opposed to in the window. The document is marked as "dirty," meaning it is different than it was when it was initially loaded/created. Finally, NotifyViews() is called, sending the custom vnDrawAppend message to each view of the document. To define such a custom message, three lines are needed:

const int vnDrawAppend = vnCustomBase+0;

NOTIFY_SIG(vnDrawAppend, unsigned int)

#define EV_VN_DRAWAPPEND VN_DEFINE(vnDrawAppend, VnAppend, int)

And a line of code needs to be added to the DEFINE_RESPONSE_TABLEx for the view class. The ClassExpert doesn’t know about custom messages, so you need to add it by hand, after the "//{{TMyViewRSP_TBL_END}}" comment so you don’t interfere with the ClassExpert, like this:

//{{TDrawViewRSP_TBL_END}}

EV_VN_DRAWAPPEND,

END_RESPONSE_TABLE;

Finally, you write the function that a view uses to handle getting this update from its document:

    bool TDrawView::VnAppend(unsigned int index)
    {
        TClientDC dc(*this);
        const TLine* line = DrawDoc->GetLine(index);
        line->Draw(dc);
        return true;
    }

The view’s Paint() needs to be modified to get data from the document as well:

    void TDrawView::Paint(TDC& dc, BOOL, TRect&)
    {
        // Iterates through the array of line objects.
        int i = 0;
        const TLine* line;
        while ((line = DrawDoc->GetLine(i++)) != 0)
            line->Draw(dc);
    }

One thing that is interesting about this Paint() method is that all of the information is held in the document. The view doesn’t send a message to the document to have it call line->Draw() on the view, because the document is supposed to be 100% isolated from anything do to with the view, and it should be possible to add new views without changing the document at all. The view, on the other hand, is required to know about the document. It needs to know how to handle every message the document is going to send. It is also allowed, for efficiency reasons, to keep information about the document cached inside itself. For example, lets see what happens when a line is deleted.

TDrawView doesn’t cache anything about the document, and when it gets a notification that a line has deleted it simply invalidates itself and lets its Paint() method redraw all of the lines:

    bool TDrawView::VnDelete(unsigned int /*index*/)
    {
        Invalidate(); // force full repaint
        return true;
    }

TDrawListView, the listbox of line information takes a different approach. Because it is a listbox all the text about the lines is saved inside a Windows data structure. When it sees a line has been deleted, it just deletes that particular line. The TListBox::DeleteString() can set up and do the redraw without a Paint() member at all and no calls to the document:

    bool TDrawListView::VnDelete(unsigned int index)
    {
        DeleteString(index);
        HandleMessage(WM_KEYDOWN,VK_DOWN); // force selection
        return true;
    }

When I first began developing with Doc/View, I had a lot of difficulty figuring out how to structure my program, and understanding the often inaccurate and incomplete help files. To aid the Doc/View programmer, I have extracted a good deal of the Doc/View help from the OWL help file and added my comments, and I have written a FAQ on how to do certain things with Doc/View.

TDocument:

1) TDocument is an abstract base class that serves as an interface between the document, its views, and the document manager (the TDocManager class). TDocument creates, destroys, and sends messages about the view. For example, if the user changes a document, TDocument tells the view that the document has been updated. In order to send messages to its associated views, the document maintains a list of all the views existing for that document and communicates with the views using ObjectWindows event-handling mechanism. Rather than using the function SendMessage, the document accesses the view's event table. The views can update the document's data by calling the member functions of the particular document. Views can also request streams, which are constructed by the document. Both documents and views have lists of properties for their applications to use. When documents and views are created or destroyed, messages are sent to the application, which can then query the properties to determine how to process the document or view. It is the document manager's responsibility to determine if a particular view is appropriate for the given document.

Because the property attribute functions are virtual, a derived class (which is called first) might override the properties defined in a base class. Each derived class must implement its own property attribute types of either string or binary data. If the derived class duplicates the property names of the parent class, it should provide the same behavior and data type as the parent.

Although documents are usually associated with files, they do not necessarily have to be files; they can also consist of database tables, mail systems, fax or modem transmissions, disk directories, and so on.

 

TDocument Members (public):

TDocument(TDocument* parent = 0) CONSTRUCTOR

3) This constructor causes the document to inherit the parent's document manager, if there is a parent. Otherwise it just sets the document manager member

virtual ~TDocument() DESTRUCTOR

3) This deletes all children (if there are any) and all views.

virtual TInStream* InStream(int mode, const char far* strmId=0)

1) Generic input for the particular storage medium, InStream returns a pointer to a TInStream. mode is a combination of the ios bits defined in iostream.h. See the document open mode constants for a list of the open modes. Used for documents that support named streams, strmId is a pointer to the name of a stream. Override this function to provide streaming for your document class.

3) The InStream(), OutStream, and Open() members aren't abstract, so that classes that don't use TIn/OutStreams don't have to redefine them, but it does nothing in TDocument. This function expects a 'mode', this is where you tell the stream if it is to be binary or text, etc.

virtual TOutStream* OutStream(int mode, const char far* strmId=0)

3) Again does nothing (just a stub).

virtual bool Open(int mode, const char far* path=0)

3) also a stub, return TRUE.

virtual bool Close();

2) close document, does not delete or detach

3) This member Close()s all children, but doesn't do anything to the

view itself.

virtual bool Commit(bool force=FALSE);

1) Saves the current data to storage. When a file is closed, the document manager calls either Commit or Revert. If force is TRUE, all data is written to storage. TDocument's Commit checks any child documents and commits their changes to storage also. Before the current data is saved, all child documents must return TRUE. If all child documents return TRUE, Commit flushes the views for operations that occurred since the last time the view was checked. Once all data for the document is updated and saved, Commit returns TRUE.

2) save current data, force write

3) Commit()s all children, then notifies all the views that a Commit is taking place. If force is FALSE, data will only be written if the doc is 'dirty' (has changed since it was loaded.) Interestingly, the code contains the comment:
// should we test here for DocPath==0 , or should caller have checked?
Which leads us to suspect that the caller should have checked... This is where you put the code in your TDocument descendent to write out your changes, unless you have having the view hold the data (more on this later.)

virtual bool Revert(bool clear=FALSE);

1) Performs the reverse of Commit and cancels any changes made to the document since the last commit. If clear is TRUE, data is not reloaded for views. Revert also checks all child documents and cancels any changes if all children return TRUE. When a file is closed, the document manager calls either Commit or Revert. Returns TRUE if the operation is successful.

2) abort changes, no reload if TRUE

3) Revert()s all children, doesn't do anything to itself.

virtual TDocument& RootDocument();

1) Returns the this pointer as the root document. [INCORRECT]

3) This member searches up the parent list to find the root of the document hierarchy for this document. (perhaps it was supposed to be "of the root document.")

TDocManager& GetDocManager()

1) Returns a pointer to the current document manager.

3) Not quite sure why you would want more than one document manager?

void SetDocManager(TDocManager& dm);

TDocument* GetParentDoc()

TDocTemplate* GetTemplate()

1) Gets the template used for document creation. The template can be changed during a SaveAs operation.

3) A template is a pair <doc, view>. This was created by a DEFINE_DOC_TEMPLATE most likely.

bool SetTemplate(TDocTemplate* tpl);

virtual bool SetDocPath(const char far* path)

3) Filename of the document. This can be 0 in the case of File|New.

const char far* GetDocPath()

virtual void SetTitle(const char far* title)

const char far* GetTitle()

3) This is usually used to put the title into the views.

virtual bool IsDirty();

1) Returns TRUE if the document or one of its views has changed but has not been saved.

2) also queries doc and view hierarchy

3) When a document changes, it is supposed to be set to being 'dirty' so that when the document is closed without saving the user can be prompted if saving is to be done. When IsDirty() is false, the File|Save menu choice is disabled by the document manager.

void SetDirty(bool dirty = TRUE)

virtual bool IsOpen()

1) Checks to see if the document has any streams in its stream list. Returns FALSE if no streams are open; otherwise, returns TRUE.

3) This can be redefined to check something besides the streams list, the line drawing example just checks a pointer to see if any lines are around.

virtual bool CanClose();

2) returns FALSE if unable to close

bool HasFocus(HWND hwnd);

1) Used by the document manager, HasFocus returns true if this document's view has focus. hwnd is a handle to the document. to determine if the document contains a view with a focus.

3) Uses the vnIsWindow message. This is the reason you need to have a VnIsWindow() member and response table entry.

bool NotifyViews(int event, long item=0, TView* exclude=0);

1) Notifies the views of the current document and the views of any child documents of a change, In contrast to QueryViews, NotifyViews sends notification of an event to all views and returns TRUE if any views returned a TRUE result. The event, EV_OWLNOTIFY, is sent with an event code, which is private to the particular document and view class, and a long argument, which can be cast appropriately to the actual type passed in the argument of the response function.

3) The event is something that begins with an EV_, like EV_NEW_VIEW. The item is any 32 bit value, the views have a special mechanism to get the data into the correct type, so you can pass pointers around and stuff if you cast them here. The exclude view doesn't get the information, so for example (although the example doesn't do this) you can have the line drawing program send the notification of a new line to all the views except the one that just drew the line, that way the one that submitted the line doesn't have to check for a callback and ignore it.)

TView* QueryViews(int event, long item=0, TView* exclude=0);

1) Queries the views of the current document and the views of any child documents about a specified event, but stops at the first view that returns TRUE, In contrast to NotifyViews, QueryViews returns a pointer to the first view that responded to an event with a TRUE result. The event, EV_OWLNOTIFY, is sent with an event code (which is private to the particular document and view class) and a long argument

virtual UINT PostError(UINT sid, UINT choice = MB_OK)

3) 'sid' is supposed to be the # for some message in the resource file, and 'choice' are the button flags. This function simply calls the document manager's PostDocError(). (The default document manager TDocManager calls MessageBox() with the string in the resource file. If you need something else to happen, say a BWCCMessageBox(), this is where subclassing the document manager comes into play. Note that if you take the MB_OK default, you automatically get MB_ICONQUESTION for some reason too... it's part of TDocManager)

The following functions deal with properties.

virtual int PropertyCount()

virtual int FindProperty(const char far* name);

2) return property index

virtual int PropertyFlags(int index);

2) pfXxxxx bit array

virtual const char* PropertyName(int index);

2) locale invariant name

3) If you look at the comment in the header file, you might get confused. "locale invariant" means that the name of the property doesn't change along with the national language stuff on 32-bit windows.

virtual int GetProperty(int index, void far* dest, int textlen=0);

virtual bool SetProperty(int index, const void far* src);

2) native type

TStream* NextStream(const TStream* strm);

1) Gets the next entry in the stream. Holds 0 if none exists.

3) TDocuments seem to maintain a linked list of streams which they disguise as a circular list. None of the examples use these streams, and neither does TFileDocument, so I have not a clue as to what they were supposed to be for...

TView* NextView(const TView* view);

1) Gets the next view in the list of views. Holds 0 if none exists.

3) Again, it seems to be emulating a circular list of views, so 0 will return the first view. Anyway, this member lets other classes go through the view list in much the same way as NotifyViews() does.

int GetOpenMode();

1) Gets the mode and protection flag values for the current document.

void SetOpenMode(int mode);

TView* InitView(TView* view); // called from template InitView

3) Private in OWL 2.0. Undocumented. If this view is valid, post the event dnCreate and reindex the frames.

bool IsEmbedded() {return Embedded;}

1) Returns true if the document is embedded in an OLE 2 container.

3) Not in OWL 2.0.

void SetEmbedded(bool embed) {Embedded = embed;}

1) Marks the document as being embedded in an OLE 2 container. Typically, this happens when the server is created and when the factory template class creates the component.

3) Not in OWL 2.0.

virtual bool InitDoc() {return true;}

1) A virtual method that is overridden by TOleDocument::InitDoc. You can use this function to prepare the document before the view is constructed and before the dnCreate event, which indicates that the document has been created and is posted.

3) Not in OWL 2.0.

TView* GetViewList() {return ViewList;}

3) Not in OWL 2.0. Undocumented.

void far* Tag;

1) Tag holds a pointer to the application-defined data. Typically, you can use Tag to install a pointer to your own application's associated data structure. Tag, which is initialized to 0 at the time a TDocument is constructed, is not used otherwise by the document view classes.

2) application hook, not used internally

3) I suspect Tag is for doing tricks with TDocuments and TFileDocuments when you are too lazy to subclass them.

List ChildDoc;

2) linked child document chain

TDocument Members (protected):

bool DirtyFlag;

1) Indicates that unsaved changes have been made to the document. Views can also independently maintain their local disk status.

2) document changed, might not represent views

3) (I am pretty sure that the comment in the source code is incorrect, I think that when a document is dirty it doesn't represent the FILES.)

bool Embedded;

2)document is an embedding

3) Not in OWL 2.0.

virtual void static int UntitledIndex;

2) last used index for Untitled document

AttachStream(TStream& strm);

2) called from TStream constructor

virtual void DetachStream(TStream& strm);

2) called from TStream destructor

 

TView

1) Derived virtually from both TEventHandler and TStreamableBase, TView is the interface presented to a document so it can access its client views. Views then call the document functions to request input and output streams. Views own the streams and are responsible for attaching and deleting them. Instead of creating an instance of TView, you create a derived class that has access to TView’s virtual functions. The derived class must have a way of knowing the associated window (provided by GetWindow), of describing the view (provided by GetViewName), and of displaying a range of data (GetSelection). The view must also be able to restore the view later (SetSelection) and to display the document title in its window (SetDocTitle). TView uses several event handler functions to query views, commit, and close views. For example, to query views to find the one with the focus, you would use the vnIsWindow function, passing a handle to the current window.

View classes can take various forms. For example, a view class can be a window (through inheritance), can contain a window (an embedded object), can reference a window, or can be contained within a window object. A view class might not even have a window, as in the case of a voice mail or a format converter. Some remote views (for example, those displayed by OLE 2.0 or DDE servers) might not have local windows.

Public members:

TView(TDocument& doc) CONSTRUCTOR

1) Constructs a TView object of the document associated with the view. Sets ViewId to NextViewId. Calls TDocument::AttachView to attach the view to the associated document.

3) Also sets Tag and ViewMenu (publicly known as GetViewMenu()/SetViewMenu(). TDocument's private AttachView() takes care of advancing NextViewId.

virtual ~TView() DESTRUCTOR

1) Frees a TView object and calls DetachView to detach the view from the associated document.

TDocument& GetDocument() {return *Doc;}

unsigned GetViewId() {return ViewId;}

TMenuDescr* GetViewMenu() {return ViewMenu;}

void SetViewMenu(TMenuDescr* menu);

1) Sets the menu descriptor for this view. This can be any existing TMenuDescr object. If no descriptor exists, ViewMenu is 0.

3) Look at the on-line help for TMenuDescr on this one. Basically, each view is expected to have a piece of menu-bar, which gets merged with the apps menu bar deep in the bowels of OWL.

bool IsOK()

1) Returns nonzero if the view is successfully constructed.

2) TRUE if successfully created

static unsigned GetNextViewId()

2) next ID to assign

TView* GetNextView() {return NextView;}

3) Undocumented. Not in OWL 2.0.

// static const char far* StaticName() {return "name of view";}

2) must implement, used by template manager for selection.

3) FThis member doesn't actually exist, but is very important. C++ has no way of specifying an abstract static member, so Borland thoughtfully commented it out in the header. The help text for TListBox::StaticName() is:
Overrides TView's function and returns a constant string, "ListView." This information is displayed in the user interface selection box. If you don't specify this, you don't get your view's name in the "Window|Add View" pick menu generated by the document manager.

virtual const char far* GetViewName()=0;

2) return static name of view

3) This is supposed to contain EXACTLY the same thing as StaticName(). In fact, if you look at the code you will have a hard time finding calls to StaticName(), it looks like this is to be used exclusively, but no, you MUST define both and make them the same.

virtual TWindow* GetWindow()

3) This member returns 0. It is supposed to be subclassed in TView/TWindow mix classes.

virtual bool SetDocTitle(const char far* docname, int index)

1) Stores the document title.

3) Actually, it does no such thing. It just calls the parent's SetDocTitle(), if there is a parent. Actually setting the window caption is done by TFrameWindow. To get your window to have a title, you have to override SetDocTitle() in your view class to call TWindow::SetDocTitle() and you should call TView::SetDocTitle() was well in case you will be dealing with child documents. TFrameWindow::SetDocTitle() appends its original title to your document's name and number.

virtual int PropertyCount() {return NextProperty - 1;}

virtual int FindProperty(const char far* name);// return property index

virtual int PropertyFlags(int index); // pfXxxxx bit array

virtual const char* PropertyName(int index); // locale invariant name

virtual int GetProperty(int index, void far* dest, int textlen=0);

virtual bool SetProperty(int index, const void far* src) {return FALSE;}

Protected members:

TDocument* Doc;

3) This member points to the document. However, the type is TDocument so if you plan on using this member to call your own document's functions you had better downcast Doc to your own type.

void NotOK()

1) Sets the view to an invalid state, thus causing IsOK to return 0.

2) to flag errors in creation

3) Neither TView, TDocument, or any of the document manager classes use this. This is for TView descendants to call if their Create() member fails for some reason.

 

TWindowView


1) Derived from both TWindow and TView, TWindowView is a streamable base class that can be used for deriving window-based views. TWindowView's functions override TView's virtual function to provide their own implementation. By deriving a window-view class from TWindow and TView, you add window functionality to the view of your document.

3) This class simply mixes TWindow and TView. It is a good base class to use if you aren't making an edit window (TEditView) or a list box (TListView) and sometimes is a good base to use even if you are. It defines the necessary stuff to make a view act like a window, and has a single response function, VnIsWindow()

 

TListView

3) This class does everything that TWindowView does, and LOTS more things. We will look only at the extra functionality.

Would you ever use TListView? You really don't want to use it unless you are doing something similar to what it does, scrolling over text. For example, the TDrawListView in the drawing example doesn't use TListView, it goes right to TListBox and TView directly, because TListView wants to do too much.

Public members:

TListView(TDocument& doc, TWindow* parent = 0) CONSTRUCTOR

1) Creates a TListView object associated with the specified document and parent window. Sets Attr.AccelTable to IDA_LISTVIEW to identify the edit view. Sets Attr.Style to WS_HSCROLL | LBS_NOINTEGRALHEIGHT. Sets TView::ViewMenu to the new TMenuDescr for this view.

3) Sets Origin, MaxWidth, and DirtyFlag to 0. FNote that DirtyFlag is not the same as TDocument::DirtyFlag. Note that this constructor loads a menu "IDM_LISTVIEW" from the resource file for merging with the menu bar along with accelerators.

bool Create()

1) Overrides TWindow::Create and calls TEditSearch::Create to create the view's window. Calls GetDocPath to determine if the file is new or already has data. If there is data, calls LoadData to add the data to the view. If the view's window can't be created, Create throws a TXInvalidWindow exception.

3) (Actually, Calls TListBox::Create which throws the exception.)
FNote that LoadData() is not virtual and is getting called here. Thus, you get the LoadData() for TListView if you are subclassing instead of your own LoadData(). A good reason not to use this class.

bool DirtyFlag;

1) Is nonzero if the data in the list view has been changed; otherwise, is 0.

3) TListView sometimes sets this to 2 to do 'tricks' with saving and reloading from disk. Don't expect the value to be TRUE or FALSE.

protected members:

long Origin;

1) Holds the file position at the beginning of the display.

3) Unlike the line drawing sample program, a TListView caches data locally, because that is what listboxes do. When it is time to VnCommit, the list box writes all of its data out to a stream instead of letting the document take care of it. Before it writes this data it seeks to Origin. Now, what is supposed to be happening is that the list box is holding one line for each line of text in the file. It might be possible to set up Origin to point to elsewhere in the file, then a different part of the file could be scrolled over, except that TListBox DOESN'T seek to Origin when it reloads it's data...

int MaxWidth;

1) Holds the maximum horizontal extent (the number of pixels by which the view can be scrolled horizontally).

3) This is set to 0 by Clear() and to the length of the longest string ever sent to SetExtent(). SetExtent is called whenever data in the list box changes. This lets the list box have a scroll bar big enough to scroll over the longest string in the listbox.

void SetExtent(const char far* str)

bool LoadData(int top, int sel)

1) Reads the view from the stream and closes the file. Returns TRUE if the view was successfully loaded. Throws an xmsg exception and displays the error message "TListView initial read error" if the file can't be read. Returns FALSE if the view can't be loaded.

3) The parameters, top and sel, have to do with the TListBox members SetTopIndex() and SetSelIndex(), this calls them. This function interprets the file as being lines of text 100 characters or shorter, and it loads each line into a line in the listbox where the file can be scrolled over.

The following members respond to choices on the Edit menu that was loaded in the constructor.

void CmEditUndo()

1) [This is OWL 2.0 documentation entry. This help item was removed from OWL 2.5] Automatically responds to a menu selection with a menu ID of CM_EDITUNDO by calling TListBox::Undo().

3) INCORRECT. Displays "Feature not implemented" in a message box.

void CmEditCut();

void CmEditCopy();

void CmEditPaste();

void CmEditDelete();

void CmEditClear();

3) These do what they are expected, using text and the clipboard. They set the DirtyFlag if necessary.

void CmEditAdd();

void CmEditItem();

3) These members bring up a little dialog where the user can enter a line of text. They use a static function, so don't be expect to be able to do much with the appearance of the dialog. The dialog prompt comes from the resource file.

UINT EvGetDlgCode(MSG far*)

1) Overrides TWindow's response to a WM_GETDLGCODE message (an input procedure associated with a control that isn't a check box) by calling DefaultProcessing.

bool VnDocClosed(int omode)

1) VnDocClosed indicates that the document has been closed. mode is one of the ofxxxx document open constants.

3) This member calls LoadData() to rebuild the list box, unless it's own VnCommit() decided to set the dirty flag to 2 and wrote the data itself. This member is called by TFileDocument::CloseThisFile() when an I/O completes.
(Huh? OK, I don't quite understand it, but when the document is closed, TListView wants to re-read it from disk if anyone besides itself did the writing...)

bool VnCommit(bool force)

1) VnCommit commits changes made in the view to the document. If force is nonzero, all data, even if it's unchanged, is saved to the document.

3) DirtyFlag is checked to see if the data is unchanged.

bool VnRevert(bool clear)

1) VnRevert indicates if changes made to the view should be erased, and the data from the document should be restored to the view. If clear is nonzero, the data is cleared instead of restored to the view.

3) That way VnRevert() can handle both File|Revert and Edit|Clear, though why those two functions are combined is unclear...

bool VnIsWindow(HWND hWnd) {return HWindow == hWnd;}

1) Returns a nonzero value if the window's handle passed in hWnd is the same as that of the view's display window.

3) OWL 2.5 only (private in 2.0?) @@@

bool VnIsDirty()

1) Returns a nonzero value if changes made to the data in the view have not been saved to the document; otherwise, returns 0.

void CmSelChange()

1) Automatically responds to a LBN_SELCHANGE message (which indicates that the contents of the list view have changed) by calling DefaultProcessing.

2) to prevent interpreting as unprocessed accelerator

 

TEditView

3) This class does basically the same thing that TListView does, except it does it with TEditSearches instead of TListBoxes, so you get nice Find/Next dialogs and stuff. Use this only if you are writing an editor or something...

 

TOleView

1) Derived from TWindowView [not! from TOleWindow] and TView, TOleView supports the View half of the Doc/View pair and creates a window with a view that can display an associated document. Documents use views to display themselves to a user. Regardless of whether a view belongs to a server or a container, TOleView sets up a corresponding TOcDocument object (an entire compound document).

In the case of an OLE-enabled container application, view refers to the window where the container application draws the compound document, which may consist of one or more linked and embedded objects. To display these objects in different formats, a container can be associated with more than one view. Similarly, to display the data properly, each embedded object can also have its own view. Each container view creates a corresponding ObjectComponents TOcView object.

If the view belongs to an OLE-enabled server application, TOleView creates a remote view on the server's document (a TOcRemView object). TOleView takes care of transmitting messages from the server to the container, specifically in the case of merging menus and redrawing embedded objects, and supports merging the server's and the container's pop-up menu items to form a composite menu. Because it knows the dimensions of the server's view, TOleView is responsible for telling the container how to redraw the embedded object.

Similarly to TView, TOleView supports the creation of views and provides several event handling functions that allow the view to query, commit, and close views. TOleView also manages the writing to storage of documents that belong to a container or a server.

3) I haven’t used this class enough to be able to say much about it, except that OLE apps don’t need TOleView, they only need TOleWindow. TOleView only adds the Doc/View view stuff to TOleWindow, so if you are having trouble with the user interface and such you need to look at TOleWindow. For example, if you look at the STEP14 step in the tutorial, you see a simple Doc/View OLE container. When you Edit|Insert Object, your new object shows up about 1/2" from the upper left hand corner. The code that puts it exactly there, drags it around etc. is entirely in TOleWindow. Your code just does the same old line stuff you have been doing since the early examples.

TOleView apps can also use the header file OLEVIEW.RH and pick up the following constants:

#define CM_EDITPASTESPECIAL 24311

#define CM_EDITPASTELINK 24312

#define CM_EDITINSERTOBJECT 24313

#define CM_EDITLINKS 24314

#define CM_EDITOBJECT 24370

#define CM_EDITFIRSTVERB 24371 // 20 verbs at most

#define CM_EDITLASTVERB 24390

#define CM_EDITCONVERT 24391

#define CM_EDITSHOWOBJECTS 24392

//

// Menu ID

//

#define IDM_OLEPOPUP 32405

#define IDM_OLEVIEW 32406

#define IDM_OLEVIEWEMBED 32407

//

// String ID

//

#define IDS_EDITOBJECT 32600

#define IDS_EDITCONVERT 32601

#define IDS_CLOSESERVER 32602

#define IDS_EXITSERVER 32603

//

// Accelerator IDs

//

#define IDA_OLEVIEW 32551

There is also an OLEVIEW.RC which defines these items.

Contstructor/Destructor

TOleView(TDocument& doc, TWindow* parent = 0); CONSTRUCTOR

virtual ~TOleView(); DESTRUCTOR

Public Member Functions

const char far* GetViewName();

1) Overrides TView's virtual GetViewName function and returns the name of the class (TOleView).

3) Wrong. Returns "Ole View" same as StaticName(). Also, the ‘n’ should be capitalized in the TOleView help page.

virtual TWindow* GetWindow();

virtual bool SetDocTitle(const char far* docname, int index);

static const char far* StaticName();

virtual bool OleShutDown();

2) Shut down the associated OCF partners if possible

3) This function is undocumented. Calls TOleWindow::OleShutDown() and then if no other view exists tries to close the document. Always returns true. Inherited from TOleWindow where it isn’t documented either.

Protected Member Functions

virtual bool CanClose();

virtual TOcView* CreateOcView(TDocTemplate* tpl, bool isEmbedded, IUnknown* outer);

1) Creates an ObjectComponents view associated with the embedded object. Associates the view with the document template specified in tpl. The isEmbedded parameter is true if the view is an embedded object. The outer parameter refers to the IUnknown interface with which the view will aggregate itself.

virtual void CleanupWindow();

2) Perform normal CleanupWindow, plus let the OcView object know we have closed

3) Not documented in the on-line help.

bool OtherViewExists();

2) Check if other TOleView already exists

3) Downcasts all of the other views to see if they are TOleLinkViews. If it finds any, other than itself (which makes no sense to me because it isn’t a TOleLinkView), returns true else returns false.
Not documented in on-line help.

bool EvOcViewAttachWindow(bool attach);

1) Attaches this view to its ObjectWindows parent window so the embedded object can be either opened and edited or deactivated. To attach a view to an embedded object, set the attach parameter to true. To detach the embedded object, set the attach parameter to false.

bool EvOcViewBreakLink(TOcLinkView& view);

1) Responds to an OC_VIEWBREAKLINK message that TOcLinkView sends when the server document that provides the link shuts down. EvOcViewBreakLink breaks the link with a server document or a selection by deleting the TOleLinkView associated with the TOcLinkView (view). After the link is broken, the container application is left holding a static representation (that is, a metafile) of the linked document. Returns false if unsuccessful.

bool EvOcViewInsMenus(TOcMenuDescr far& sharedMenu);

1) Inserts the server's menu into the composite menu. Determines the number of groups and the number of pop-up menu items to insert within each group. The shared menu (sharedMenu) is the container's menu merged with the server's menu groups.

3) sharedMenu is called ‘part’ in the header file.

bool EvOcViewClose();

1) Asks the server to close the view associated with this document. Tests to see if the document has been changed since it was last saved. Returns true if the document and its associated view are closed.

bool EvOcViewLoadPart(TOcSaveLoad far& ocLoad);

1) Asks the server to load itself from storage. Loads the document and its associated view.

3) According to the TOleWindow::EvOcViewLoadPart() help, If the ... object is unable to handle the message, EvOcViewLoadPart returns false.

bool EvOcViewOpenDoc(const char far*);

1) Asks the container application to open an existing document so the document can receive embedded and linked objects. (Actually, TOleView calls on the TOleDocument object to read the document from storage, using the standard OLE IStorage and IStream interfaces). Assigns a unique string identifier to the document and returns true if successful.

virtual bool EvOcViewPartInvalid(TOcPart far& changeInfo);

1) Notifies the active view of any changes made to the embedded object's data (changeInfo). Also, notifies any other views associated with this document that the bounding rectangle for the document is invalid and needs to be repainted. EvOcViewPartInvalid always returns true.

bool EvOcViewSavePart(TOcSaveLoad far& ocSave);

1) Asks the server to save the embedded object's data to storage. To save the object, EvOcViewSavePart calls upon the TOleDocument object, which creates storage as necessary for each embedded object. Saves the dimensions of the server's view, which the server uses to tell the container how to redraw the embedded object in the container's window.

3) According to the TOleWindow::EvOcViewSavePart() help, If the ... object is unable to handle the message, EvOcViewSavePart returns false. TOcSaveLoad is an undocumented class that is declared in the ocf\ocview.h header file. It has no members functions, some public data members, and the comment "Use when doing parts save and load"

bool EvOcViewSetLink(TOcLinkView& view);

1) Responds to an OC_VIEWSETLINK message TOcLinkView sends when the server document provides a link to a container document. EvOcViewSetLink establishes the link between a TOleLinkView and a TOcLinkView. The view parameter references the view with which the document or selection is associated. Returns false if unsuccessful.

bool VnInvalidate(TRect& rect);

2) Invalidate the view region specified by rect

3) Undocumented function. See VnInvalidateRect().

bool VnDocOpened(int omode);

1) Ensures that TOleView's data members, such as DragPart, Pos, and Scale, [Inherited from TOleWindow] are initialized properly after a revert operation, which cancels any changes made to the document since the last time the document was saved to storage.

bool VnDocClosed(int omode);

3) Undocumented function. Sets OcDoc to 0.

TMenuDescr* GetViewMenu();

2) Override TView's GetViewMenu to make an on-the-fly decision about which menu to use: normal, or embedded.

3) Undocumented function. If a view menu has been explicitly assigned with SetViewMenu() that menu will be returned. Otherwise, it is going to load from the module the menu IDM_OLEVIEWEMBED or IDM_OLEVIEW and use that. SetViewMenu will keep it around.

bool VnInvalidateRect(LPARAM p);

1) Invalidates the view region specified by p. Use this function to invalidate the bounding rectangle surrounding an embedded object if the object has been changed, usually as a result of in-place editing. If successful, returns true.

3) This documented function doesn’t exist. See VnInvalidate(TRect&).

 

TOleLinkView Class

1) Derived from TView, TOleLinkView provides embedding and linking support for a portion of a document instead of an entire document. With the added functionality of TOleLinkView, a container gains the ability to embed or link to a selection within the server document.

The main purpose of a class derived from TOleLinkView is to attach a view to a portion of a document whenever a link is created to a selection within a server document. After this link is established, any changes made to the linked selection in the server document are sent to the container via the following sequence of steps:

  1. When a user changes the server document, TOleLinkView receives a notification message
  2. TOleLinkView checks to see if the selection it represents has changed. If the selection has changed, TOleLinkView notifies TOcLinkView about the change.
  3. When TOcLinkView receives the change message, it notifies the container that the selection has changed.

Non-Doc/View servers need to maintain a list of the TOleLinkViews attached to the document so that change notifications can be sent to each one of the views.

3) This class does not appear in the help file’s ‘ObjectWindows hierarchy chart’.

Public Constructor and Destructor

TOleLinkView(TDocument& doc, TOcLinkView& view);

~TOleLinkView();

Public Member Functions

TString& GetMoniker();

1) Returns the moniker for the selection in a server document associated with this TOleLinkView container's view. By looking at the moniker, the application can find the corresponding objects in its document.

3) Documentation typo: The OWL help gives the return type as TString but it is TString& in the header file.
This function just returns OcLinkView.GetMoniker(), which returns the moniker (the source file's path name and the object hierarchy) for the selection in a container document associated with this TOleLinkView server's view. See TOcLinkView::GetMoniker() in the OCF help for more details.

virtual const char far* GetViewName();

static const char far* StaticName();

1) Returns the constant string "Link View" that is displayed in the user interface selection box.

virtual bool UpdateLinks();

virtual bool VnLinkMoniker(TString moniker);

virtual bool VnLinkView(TOcLinkView& view);

Protected Data Member

TOcLinkView& OcLinkView;

1) The TOcLinkView connector object associated with this view.

3) Note that TOcLinkView is not a Doc/View class. It’s an OCF class derived from TUnknown.

 

TFileDocument

1) Derived from TDocument, TFileDocument opens and closes views and provides stream support for views. Streams are created on top of DOS files using Windows file services. TFileDocument has member functions that continue to process FileNew and FileOpen messages after a view is constructed. You can add support for specialized file types by deriving classes from TFileDocument. TFileDocument makes this process easy by hiding the actual process of storing file types.

public members:

TFileDocument(TDocument* parent = 0) CONSTRUCTORS

3) Initializes FHdl and InfoPresent

~TFileDocument()

3) Does nothing

bool Open(int mode, const char far* path=0)

1) Overrides TDocument_Open and opens the file using the specified path. If the file is already open, returns 0. Calls TDocument::SetDocPath to set the directory path. If omode isn't 0, sets TDocument::OpenMode to omode. If the file can't be opened, returns 0.

3) This function set up a few things and calls OpenThisFile(). As OpenThisFile() isn't virtual, if you want to be doing your own stuff at opening time, you don’t call the old open() to set the file handle. (See the line drawing program for an example of this.) FHdl is used to keep track of the state of the document, open or closed.

bool Close();

1) Closes the document but does not delete or detach any associated views. Before closing the document, Close calls TDocument's Close to make sure all child documents are closed. If any children are open, Close returns 0 and doesn't close the document. If all children are closed, checks to see if any associated streams are open, and if so, returns 0 and doesn't close the document. If there are no open streams, closes the file.

TInStream* InStream(int mode, const char far* strmId=0)

1) Overrides Tdocument::InStream and provides generic input for the particular storage medium. InStream returns a pointer to a TInStream. mode is a combination of the ios bits defined in iostream.h. strmId is not used for file documents. The view reads data from the document as a stream or through stream functions.

3) The stream gets it's file handle from FHdl.

TOutStream* OutStream(int mode, const char far* strmId=0);

bool Commit(bool force = FALSE)

1) Calls TDocument::Commit and clears TDocument's DirtyFlag data member, thus indicating that there are no unsaved changes made to the document.

3) Commit doesn't write any data to the disk. I think it is intended to be used with views like TListView which carry all of their data in their windows. The line drawing example program doesn't even call the base Commit().

bool Revert(bool clear = FALSE);

3) Same as commit.

bool IsOpen()

2) { return FHdl != HFILE_ERROR || TDocument::IsOpen(); }

All of the property functions are redefined:

int FindProperty(const char far* name); // return index

int PropertyFlags(int index);

const char* PropertyName(int index);

int PropertyCount() {return NextProperty - 1;}

int GetProperty(int index, void far* dest, int textlen=0);

bool SetProperty(int index, const void far* src);

bool Open(HFILE fhdl); // open on existing file handle

1) Opens a file document using an existing file handle. Sets TDocument::OpenMode to PREV_OPEN and read/write. Sets the document path to 0. Sets FHd to fhdl. Always returns nonzero.

3) While I have not seen this in use, I suspect that it is for compound documents where a document is embedded in another document. When the parent document gets to a certain point in this situation it creates a child document which reads bytes from its file.

Protected members

HFILE FHdl; // file handle if held open at the document level

HFILE OpenThisFile(int omode, const char far* name, streampos* pseekpos);

3) You can call this if you redefine Open() and don't want to call the original Open(). THIS IS NOT VIRTUAL, SO DON'T SUBCLASS IT!

void CloseThisFile(HFILE fhdl, int omode);

3) You can call this if you redefine Close() and don't want to call the original Close(). THIS IS NOT VIRTUAL, SO DON'T SUBCLASS IT!

 

TStorageDocument

1) Derived from TDocument, TStorageDocument supplies functionality that supports OLE's compound file structure. A compound file structure is a file-management system that stores files in a hierarchical structure within a root file. This storage structure is analagous [sic] to the directory or folder and file scheme used on a disk, except that a directory is called a storage and a file is called a stream.

In addition, TStorageDocument provides support for OLE's compound document mechanism. A compound document can store many different kinds of embedded objects, for example, spreadsheets as well as bitmaps.

Basically, TStorageDocument supports having a document read and write its own storage. In order to provide this functionality, TStorageDocument overrides several virtual methods from TDocument and, following TDocument's strategy, also applies property lists both to documents and to their views. In this way, documents can use these attributes to read files in from storage and write files out to storage

Messages are sent to the application, which queries the properties in order to determine how to process the document or view. Each derived class must implement its own property attribute types - either string or binary data.

Type Definitions

enum TStgDocProp{PrevProperty, CreateTime, ModifyTime, AccessTime, StorageSize, IStorageInstance, NextProperty};

Public

TStorageDocument(TDocument* parent = 0) CONSTRUCTOR

1) Constructs a TStorageDocument object with the specified TDocument parent window. Sets the data members StorageI and OpenCount to 0. Sets CanRelease to false. Later, the member function ReleaseDoc sets this flag to true so that the document can be closed.

~TStorageDocument(); DESTRUCTOR

virtual bool ReleaseDoc();

1) Releases the storage for the document and closes the document.

implement virtual methods of TDocument

TInStream* InStream(int omode, const char far* strmId=0);

TOutStream* OutStream(int omode, const char far* strmId=0);

bool Open(int omode, const char far* stgId);

1) Opens or creates a document based on IStorage. The name parameter specifies the name of the document, if any, to open. The omode parameter contains a combination of the document open and sharing modes (for example, ofReadWrite) defined in docview.h.

bool Close();

1) Releases the IStorage if CanRelease is true. (CanRelease is set to true when ReleaseDoc is called.) Before closing the document, Close checks any child documents and tries to close them.

bool Commit(bool force = false);

1) Saves the current data to storage. When a file is closed, the document manager calls either Commit or Revert. If force is true, all data is written to storage. TDocument's Commit checks any child documents and commits their changes to storage also. Before the current data is saved, all child documents must return true. If all child documents return true, Commit flushes the views for any operations that occurred since the last time the view was checked. Once all data for the document object is updated and saved, Commit returns true.

bool CommitTransactedStorage();

1) If a file is opened or created in transacted mode, call CommitTransactedStorage to commit a document to permanent storage. By default, a document uses transacted instead of direct storage. With transacted storage, a document written to IStorage is only temporary until it is committed permanently. If a compound file is opened or created in direct mode, then CommitTransactedStorage does not need to be called.

bool Revert(bool clear = false);

1) Performs the reverse of Commit and cancels any changes made to the storage document since the last commit. If clear is true, data is not reloaded for views. Revert also checks all child documents and cancels any changes if all children return true. When a file is closed, the document manager calls either Commit or Revert. Revert returns true if the revert operation is successful.

bool SetDocPath(const char far* path);

1) Sets the document path for the Open and Save file operations.

bool IsOpen() {return (StorageI != 0);}

1) Checks to see if the storage document has any IStorage created. If there is no storage, IsOpen returns false; otherwise, it returns true.

int FindProperty(const char far* name); // return index

int PropertyFlags(int index);

const char* PropertyName(int index);

int PropertyCount() {return NextProperty - 1;}

int GetProperty(int index, void far* dest, int textlen=0);

bool SetProperty(int index, const void far* src);

3) These are the standard Property functions, same as TDocument.

additional methods for obtaining IStorage

virtual bool SetStorage(IStorage* stg, bool remember = true); // Set a new IStorage

virtual bool RestoreStorage();

virtual bool OpenHandle(int omode, HANDLE hGlobal); // open on global memory

1) OpenHandle writes data to a memory block. OpenHandle first creates an ILockBytes interface on the global handle and then creates an IStorage based on the ILockBytes interface. The parameter omode contains a combination of the document open and sharing modes (for example, ofReadWrite) defined in docview.h.

virtual bool SetHandle(int omode, HANDLE hGlobal, bool create = false, bool remember = false);

virtual bool GetHandle(HGLOBAL* handle);

IStorage* GetStorage() {return StorageI;}

protected

int ThisOpen; // actual mode bits used for opening storage

IStorage* StorageI; // current IStorage instance, 0 if not open

IStorage* OrgStorageI; // original IStorage

ILockBytes* LockBytes; // ILockBytes used, if any

TOleDocument

1) Derived from TStorageDocument, TOleDocument implements the document half of the Doc/View pair. It manages the document's data while the corresponding TOleView object determines how the data is displayed on the screen. Basically, TOleDocument is a TStorageDocument with a knowledge of TOcDocument through its pointer to TOcDocument.

TOleDocument is responsible for creating compound documents, closing documents, reading documents from storage, and writing documents to storage. In the case of a server, the document consists of a single object. In the case of a container, the document can consist of one or more embedded objects (also referred to as parts).

To accomplish these tasks, TOleDocument talks to the underlying ObjectComponents classes through the use of functions such as GetOcApp, GetOcDoc, and SetOcDoc.

3) TOleDocument adds two private members to TStorageDocument, TOcDocument* OcDoc and bool Closing. This is a relatively small class that manages those variables and calls TStorageDocument.

 

Public Members

TOleDocument(TDocument* parent = 0);

~TOleDocument();

Accessors

void SetOcDoc(TOcDocument* doc) {OcDoc = doc;}

TOcDocument* GetOcDoc() {return OcDoc;}

TOcApp* GetOcApp();

Overridables

virtual bool Commit(bool force);

1) Commits the current document's data to storage. If force is true and the data is not dirty, all data is written to storage and Commit returns true. If force is false, the data is written only if it is dirty.

virtual bool CommitSelection(TOleWindow& oleWin, void* userData) {return true;}

3) Undocumented function. Does nothing. Called by TOleView::EvOcViewSavePart() only.

virtual bool Open(int mode, const char far* path = 0);

virtual bool Close();

bool Revert(bool clear);

virtual void PreOpen();

1) Before the document is actually opened, PreOpen gives the derived class a chance to perform a particular operation; for example, setting a different open mode for the compound document.

virtual bool Write();

virtual bool Read();

virtual bool SetStorage(IStorage* stg, bool remember = true);

virtual bool RestoreStorage();

virtual bool CanClose();

virtual bool ReleaseDoc();

virtual bool InitDoc();

virtual IStorage* GetNewStorage();

Utilities

bool PathChanged();

1) Checks to see if the current document's path is the same as the TOcDocument's path. If the paths are not the same, PathChanged returns true.

void OleViewClose();

2) Shut down the TOleView's

3) Undocumented function. All views of this document which can be downcasted to TOleView get OleShutDown()’d.

TDocManager

1) TDocManager creates a document manager object that manages the list of current documents and registered templates, handles standard file menu commands, and displays the user-interface for file and view selection boxes. To provide support for documents and views, an instance of TDocManager must be created by the application and attached to the application.

The document manager normally handles events on behalf of the documents by using a response table to process the standard CM_FILENEW, CM_FILEOPEN, CM_FILECLOSE, CM_FILESAVE, CM_FILESAVEAS, and CM_VIEWCREATE File menu commands. In response to a CM_FILENEW or a CM_FILEOPEN command, the document manager creates the appropriate document based on the user's selections. In response to the other commands, the document manager determines which of the open documents contains the view associated with the window that has focus. The menu commands are first sent to the window that is in focus and then through the parent window chain to the main window and finally to the application, which forwards the commands to the document manager.

When you create a TDocManager or a derived class, you must specify that it has either a multi-document (dmMDI) or single-document (dmSDI) interface. In addition, if you want the document manager to handle the standard file commands, you must OR these with dmMenu.

You can also enable or disable the document manager menu options by passing dmSaveEnable or dmNoRevert in the constructor. If you want to enable the File|Save menu option if the document is unmodified, pass the dmSaveEnable flag in the constructor. To disable the "Revert to Saved" menu option, pass dmNoRevert in the constructor.

When the application directly creates a new document and view, it can attach the view to its frame window, create MDI children, float the window, or create a splitter. However, when the document manager creates a new document and view from the File|Open or File|New menu selection, the application doesn't control the process. To give the application control, the document manager sends messages after the new document and view are successfully created. Then, the application can use the information contained in the template to determine how to install the new document or view object.

3) It shouldn't be too hard to subclass the manager to change some small thing about it, such as using BWCC message boxes for errors and such, but I wouldn't want to rewrite it...

public members:

TDocument::List DocList; // list of attached documents

TDocManager(int mode,TDocTemplate*& templateHead = DocTemplateStaticHead);

1) Constructs a TDocManager object that supports either single (SDI) or multiple (MDI) open documents depending on the application. mode is set to either dmMenu, dmMDI, dmSDI, dmSaveEnable, or dmNoRevert. To install the standard TDocManager File menu commands, you must OR dmMDI or dmSDI with dmMenu. For example,
DocManager = new TDocManager(DocMode | dmMenu);
The document manager can then use its menu and response table to handle these events. If you do not specify the dmMenu parameter, you must provide the menu and functions to handle these commands. However, you can still use your application object's DocManager data member to access the document manager's functions.

3) The help doesn't document the second parameter, templateHead. The first TDocManager created gets all of the already existing "static" templates, and sets templateHead to 1, so that if a second document manager is created it DOESN'T GET ANY TO OWN ANY TEMPLATES UNLESS THEY ARE IN A LIST AND THE HEAD IS EXPLICITLY PASSED IN. All of the templates have their document manager set to this document manager.

virtual ~TDocManager();

virtual TDocument* CreateAnyDoc(const char far* path, long flags = 0);

1) Creates a document based on the directory path and the specified template. flags, one of the document view constants, determines how the document template is created. If path is 0 and this is not a new document (the flag dtNewDoc is not set), it displays a dialog box. If path is 0, dtNewDoc is not set, and more than one template exists, it displays a dialog box and a list of templates.

3) If flags is dtNewDoc, bring up the menu of visible document types using SelectDocType(), otherwise brings up a file open dialog with SelectDocPath(). After it has figured out which template to use, returns the template's CreateDoc(filepath, flags).

virtual TView* CreateAnyView(TDocument& doc,long flags = 0);

1) Creates a document view based on the directory path and specified template. flags, one of the document view constants, determines how the document template is created.

2) selects from registered templates supporting this doc.

3) Calls SelectViewType() from the templates supporting this doc, if a template is selected calls the template's CreateView(). The flags are just passed in to CreateView() which ignores them, they are supposed to be the dtxxxx flags.

TDocument* CreateDoc(TDocTemplate* tpl, const char far* path,

TDocument* parent=0, long flags=0);

1) CreateDoc creates a document based on the directory path and the specified template. The flags parameter contains one of the document template constants that determines how the document is created.

3) Not in OWL 2.0. This function creates a document by calling a document’s constructor (via TDocTemplateT<D,V>::ConstructDoc(), passing in a temporary document for the parent?!? It then sets the template and calls InitDoc(). CreateDoc() can be called directly from the application to open up files much as if they were opened with the File|Open. It is at a lower level, though, and it won’t catch multiple openings of the same document, so you may call FindDocument() first to check.

TView* CreateView(TDocument& doc);

1) Creates a view of the specified document.

3) Not in OWL 2.0. Calls the template’s ConstructDoc() and then calls InitDoc().

TDocument* InitDoc(TDocument* doc, const char far* path, long flags);

1) Initializes the documents, the directory path for the document, and the dtxxxx document flag values (such as dtNewDoc ) used to create document templates.

2) prompts for pathname if none supplied and not creating a new document

3) Not in OWL 2.0. Note: The flags will be xor’ed with the flags in the template for this document, so you pass in CHANGES not the flags you want the document to be initialized with.

bool SelectSave(TDocument& doc);

1) Prompts the user to select a file name for the document. Filters out read-only files.

3) Not in OWL 2.0.

virtual TDocTemplate* SelectAnySave(TDocument& doc, bool samedoc = TRUE);

1) Selects a registered template to save with this document.

virtual TDocTemplate* MatchTemplate(const char far* path);

1) Returns the first registered template whose pattern matches the given file name. If no template is compatible with the supplied file name, or if the template is open already, it returns 0.

virtual TDocument* GetCurrentDoc(); // return doc with focus, else 0

1) Calls TWindow::GetFocus to determine the window with the focus. Searches the list of documents and returns the document that contains the view with the focus. Returns 0 if no document has a view with focus.

3) If this function returns 0 and one of your documents has the focus, then you will not be able to save your document, etc., from the menu. Probably HasFocus() is failing in that case because of a problem with VnIsWindow().

virtual bool FlushDoc(TDocument& doc); // attempt to update changes

1) Updates the document with any changes and prompts the user for confirmation of updates.

TDocument* FindDocument(const char far* path); // 0 if not found

1) Returns the first document whose pattern matches the given file name. If no document is compatible with the supplied file name, or if the document is open already, it returns 0.

3) Borland help file incorrectly gives the return type as
TDocTemplate* FindDocument(const char far* path);

 

TApplication* GetApplication() {return Application;}

bool IsFlagSet(int flag) {return (Mode & flag) != 0;}

1) Returns true if the dtxxxx document template constant specified in Flag is set.

void RefTemplate(TDocTemplate&); // add template ref

1) Adds a template to the list of templates attached to the document.

void UnRefTemplate(TDocTemplate&); // drop template ref

1) Removes a template from the list of templates attached to the document.

void DeleteTemplate(TDocTemplate&); // remove from list

1) Removes a template from the list of templates attached to the document.

void AttachTemplate(TDocTemplate&); // append to list

1) Inserts a template into the chain of templates.

TDocTemplate* GetNextTemplate(TDocTemplate* tpl)

{return tpl ? tpl->NextTemplate : TemplateList;}

1) Returns the next document template.

3) If tpl is 0, returns the first template.

// primary event handlers, public to allow direct invocation from app

//

virtual void CmFileOpen();

3) Just calls CreateAnyDoc(0, 0);

virtual void CmFileNew();

3) Just calls CreateAnyDoc(0, dtNewDoc);

virtual void CmFileClose();

1) Responds to a file close message. Tests to see if the document has been changed since it was last saved, and if not, prompts the user to confirm the save operation.

3) Calls the current document's CanClose(), it the document can close calls Close(), if Close() returns TRUE deletes the document

virtual void CmFileSave();

1) Responds to a file save message. Set doc to the current document. Calls IsDirty() and returns IDS_NOTCHANGED if the document hasn't been changed since the last time it was saved.

3) If the document doesn't have a path (created with CmFileNew()), calls CmFileSaveAs(). If the document has a path, does the checking described above and saved the document with it's Commit(). NOTES: The "doc" referred to in the help is a local variable of CmFileSave() that isn't accessible or meaningful. The IDS_NOTCHANGED isn't returned, as the function is void. It is submitted to PostDocError(), but you should never encounter this, as the private command enablers keep options that will trigger errors dim on the menu.

virtual void CmFileSaveAs();

1) Prompts the user to enter a new name for the document.

3) Finds the current document, calls SelectAnySave() to figure out the file name and template to use for saving, then call's the document's Commit() with TRUE to force writing.

virtual void CmFileRevert();

1) Reverts to the previously saved document.

3) Call's the document's Revert() method if the document IsDirty(), if the document isn't it will trigger a PostDocError() message.

virtual void CmViewCreate();

1) Responds to a view create message by creating a document view based on the specified directory path.

3) Calls CreateAnyView(*doc), to create a new view for this document.

// overrideable document manager UI functions

//

virtual UINT PostDocError(TDocument& doc, UINT sid, UINT choice = MB_OK);

1) Displays a message box with the error message passed as a string resource ID in sid. By default, the message box contains either an OK push button or a question mark icon. If an error message can't be found, PostDocError() displays a "Message not found" message. choice can be one or more of the Windows MB_Xxxx style constants. See the Windows API on-line Help for a description of the values. This function can be overridden.

virtual void PostEvent(int id, TDocument& doc); // changed doc status

1) If the current document changes, calls ::SendMessage and passes a WM_OWLDOCUMENT message to indicate a change in the status of the document.

virtual void PostEvent(int id, TView& view); // changed view status

1) If the current view changes, calls ::SendMessage and passes a WM_OWLVIEW message to indicate a change in the status of the view.

3) The ids appear to be dnxxxx message constants.

1) TDocManager uses the dnxxxx message constants to indicate that a document or view has been created or closed. You can set up response table entries for these messages using the EV_OWLVIEW or EV_OWLDOCUMENT macros.

Constant Meaning

------------ -------------------------------------------------------

dnCreate A new document or view has been created.

dnClose A document or view has been closed.

3) An application uses receives these dnxxxx events by putting in a response table something like this, from the docviewx example:

EV_OWLVIEW(dnCreate, EvNewView),

EV_OWLVIEW(dnClose, EvCloseView),

Now, EvNewView() and EvCloseView() are not defined in the header file for TApplication, the names are just place holders, but these are the names generated by the AppExpert for Doc/View programs. In a standard Doc/View program, the EvNewView() either (mdi) creates a new mdi child and puts the view as the child's client or (sdi) puts the new view as the main window's client. The docviewx example program uses this to modify the menu, as well. EvCloseView() does nothing under mdi, as mdi takes care of it, under sdi it should set the main window's client to 0, and can alter the menu, title, etc.

// delegated methods from TApplication

//

void EvPreProcessMenu(HMENU hMenu);

1) Called from MainWindow, EvPreProcessMenu loads and deletes a menu at the position specified by MF_POSITION or MF_POPUP. Your application can call EvPreProcessMenu to process the main window's menu before it is displayed.

3) Called by TApplication::PreProcessMenu(HMENU fmenu) if there is a document manager, using WM_OWLPREPROCMENU. TApplication::PreProcessMenu is called by TFrameWindow::MergeMenu(const TMenuDescr& childMenuDescr) and TFrameWindow::AssignMenu(TResId menuResId).

bool EvCanClose();

1) Checks to see if all child documents can be closed before closing the current document. If any child returns FALSE, returns FALSE and aborts the process. If all children return TRUE, EvCanClose calls TDocManager::FlushDoc for each document. If FlushDoc finds that the document is dirty, it displays a message asking the user to save the document, discard any changes, or cancel the operation. If the document is not dirty and EvCanClose returns TRUE, EvCanClose returns TRUE.

3) Called by TApplication::CanClose(), using WM_OWLCANCLOSE.

void EvWakeUp();

3) This undocumented member calls ReindexFrames() for each document. This member is bound to the message WM_OWLWAKEUP which is send by OWL to the application during TDocManager::Streamer::Read(), so that document managers can be streamed in from disk.

protected members:

// overrideable document manager UI functions

//

virtual int SelectDocPath(TDocTemplate** tpllist, int tplcount,

char far* path, int buflen, long flags, bool save=FALSE);

1) Prompts the user to select one of the templates to use for the file to be opened. Returns the template index used for the selection or 0 if unsuccessful. For a file open operation, save is FALSE. For a file save operation, save is TRUE. This function can be overridden to provide a

customized user-interface.

3) Uses the file common dialogs directly, not the OWL versions.

virtual int SelectDocType(TDocTemplate** tpllist, int tplcount);

1) SelectDocType, which can be overridden, lets the user select a document type from a list of document templates. Returns the template index used for the selection or 0 if unsuccessful.

3) Uses a private TPickList class for this which makes a popup menu below the mouse cursor.

virtual int SelectViewType(TDocTemplate** tpllist, int tplcount);

1) SelectViewType, which can be overridden, lets the user select a view name for a new view from a list of view names. Returns the template index used for the selection or 0 if unsuccessful.

3) (Same as SelectDocType)

Note:

3) There are private members for all of the Cmxxx members:

virtual void CmEnableNew(TCommandEnabler& hndlr);

virtual void CmEnableOpen(TCommandEnabler& hndlr);

virtual void CmEnableSave(TCommandEnabler& hndlr);

virtual void CmEnableSaveAs(TCommandEnabler& hndlr);

virtual void CmEnableRevert(TCommandEnabler& hndlr);

virtual void CmEnableClose(TCommandEnabler& hndlr);

virtual void CmEnableCreate(TCommandEnabler& hndlr);

These members dim illegal menu choices.

 

TDocTemplate and TDocTemplateT<D, V>

1) TDocTemplate is an abstract base class that contains document template functionality. TDocTemplate classes create documents and views from resources and handle document naming and browsing. The document manager maintains a list of the current template objects. Each document type requires a separate document template.

3) When AppExpert made your program, it set up something like this:

DEFINE_DOC_TEMPLATE_CLASS(TFileDocument, TWindowView, DocType1);

DocType1 __dvt1("All Files (*.*)", "*.*", 0, "TXT", dtAutoDelete | dtUpdateDir);

This is called a document template.

FNOTE: Document Templates have nothing to do with C++ templates! Document templates are data items that contain a view and a three letter file name extension for each document.

DEFINE_DOC_TEMPLATE has three parameters: docClass, viewClass, and tplClass. If you are used to the container classes, this is kind of like an association, it is saying that there is a type "DocType1". It might be more readable like this:

// (This code won’t compile, it is imaginary.)

DEFINE_A_TYPE_OF_VIEW(TfooDocument, TbarWindowView, TfoobarDocViewPair);

TfoobarDocViewPair dummy("All Files (*.*)", "*.*", 0, "TXT", dtAutoDelete |

dtUpdateDir);

Where 'foo' is your own special I/O routines for saving whatever kind of objects your document holds, and 'bar' is your viewer class. Sometimes you want more than one viewer class, like in the line drawing example there was a graphical viewer and a text viewer, so there were two DEFINE_DOC_TEMPLATE_CLASSes and __dvts. You can have as many as you want. When you select "Window|Add View" you get a menu of the StaticName()s of all views which have the same document class as the current view.

DEFINE_DOC_TEMPLATE_CLASS is supposed to hide from you a bunch of code that it writes that creates a TDocTemplateT<D,V>. While most of the Doc/View stuff becomes clearer when you look at the code, TDocTemplateT<D,V> doesn't, so just use the macro.

The strings in the template define what will appear in the common dialog when a file is being opened. The dtxxxx flags are discussed below.

TDocTemplate public members:

virtual TDocument* ConstructDoc(TDocument* parent = 0) = 0;

1) A pure virtual function that must be defined in a derived class, ConstructDoc creates a document specified by the document template class. Use this function in place of CreateDoc.

3) This function is much lower level than

virtual TView* ConstructView(TDocument& doc) = 0;

1) A pure virtual function that must be defined in a derived class, ConstructView creates the view specified by the document template class.

3) Use this function in place of CreateView().

virtual TDocument* CreateDoc(const char far* path, long flags = 0) = 0;

1) An obsolete pure virtual function that must be defined in a derived class, CreateDoc creates a document based on the directory path (path) and the specified template and flags value. If the path is 0 and the new flag (dtNewDoc) is not set, the dialog box is displayed. This function is obsolete: use ConstructDoc instead.

3) Although Borland recommends the use of ConstructDoc() instead of this function, ConstructDoc() does have the parameters expected. To use ConstructDoc(), do what CreateDoc() does: call InitDoc()@@@

virtual TView* CreateView(TDocument& doc, long flags = 0) = 0;

1) A pure virtual function that must be defined in a derived class, CreateView creates the view specified by the document template class. This function is obsolete: use ConstructView instead.

TDocument* InitDoc(TDocument* doc,const char far* path,long flags);

1) InitDoc is called only from the subclass so that CreateDoc can continue its document processing.

2) Backward compatibility

TView* InitView(TView* view);

2) Backward compatibility

virtual TDocument* IsMyKindOfDoc(TDocument& doc) = 0;

1) A pure virtual function that must be defined in a derived class, IsMyKindOfDoc tests if the template belongs to the same class as the document or to a derived class.

3) See TDocTemplateT<D,V>::IsMyKindOfDoc().

virtual TView* IsMyKindOfView(TView& view) = 0;

virtual const char far* GetViewName() = 0;

bool SelectSave(TDocument& doc);

1) Prompts the user to select a file name for the document. Filters out read-only files.

2) Backward compatibility

bool IsVisible(); // displayable in file select dialogs

1) Indicates whether the document can be displayed in the file selection dialog box. A document is visible if dtHidden isn't set and Description isn't 0.

virtual TDocTemplate* Clone(TModule* module,

TDocTemplate*& phead=DocTemplateStaticHead)=0;

TDocManager* GetDocManager() const {return DocManager;}

void SetDocManager(TDocManager* dm) {DocManager = dm;}

const char far* GetFileFilter() const {return FileFilter;}

2) Backward compatibility

void SetFileFilter(const char far*);

1) Sets the valid document matching pattern to use when searching for files.

2) Backward compatibility

3) Does nothing, unless you are running Diagnostic Expert in which case calling this causes the warning "Obsolete function called" to appear.

const char far* GetDescription() const {return Description;}

2) Backward compatibility

void SetDescription(const char far*);

2) Backward compatibility

3) Undocumented function. Does nothing, unless you are running Diagnostic Expert in which case calling this causes the warning "Obsolete function called" to appear.

 

const char far* GetDirectory() const {return Directory;}

void SetDirectory(const char far*);

void SetDirectory(const char far*, int len);

const char far* GetDefaultExt() const {return DefaultExt;}

2) Backward compatibility

void SetDefaultExt(const char far*);

1) Sets the default extension to use if the user has entered the name of a file without any extension. If there is no default extension, SetDefaultExt contains 0.

2) Backward compatibility

3) Does nothing, unless you are running Diagnostic Expert in which case calling this causes the warning "Obsolete function called" to appear.

long GetFlags() const {return Flags;}

bool IsFlagSet(long flag) {return (Flags & flag) != 0;}

void SetFlag(long flag) {Flags |= flag;}

void ClearFlag(long flag){Flags &= ~flag;}

bool IsStatic() {return (RefCnt & 0x8000) != 0;}

TModule*& GetModule() {return *ModulePtr;}

void SetModule(TModule* module){ModuleDeref = module;

ModulePtr = &ModuleDeref;}

TDocTemplate protected members:

TDocTemplate(const char far* desc, const char far* filt,

const char far* dir, const char far* ext, long flags,

TModule*& module, TDocTemplate*& phead);

1) Constructs a TDocTemplate with the specified file description (desc), file filter pattern (filt), search path for viewing the directory (dir), default file extension (ext), and flags representing the view and creation options (flags). Sets Description to desc, FileFilter to filt, Directory to dir, DefaultExt to ext, and Flags to flags. Then, adds this template to the document manager's template list. If the document manager is not yet constructed, adds the template to a static list, which the document manager will later add to its template list.

3) This constructor is private because it is expected that TDocTemplateT<D, V> will be used to construct the template. In fact, it is TDocTemplateT<D, V>'s constructor that passes around the static list, TDocTemplate doesn't know about the static list.

~TDocTemplate();

1) Destroys a TDocTemplate object and frees the data members (FileFilter, Description, Directory, and DefaultExt). The Destructor is called only when no views or documents are associated with the template. Instead of calling this Destructor directly, use the Delete member function.

TDocument* InitDoc(TDocument* doc, const char far* path, long flags);

1) InitDoc is called only from the subclass so that CreateDoc can continue its document processing.

TView* InitView(TView* view);

1) Called only from the subclass to continue CreateView processing.

TDocTemplateT<D, V> public members:

TDocTemplateT(const char far* filt, const char far* desc,

const char far* dir, const char far* ext, long flags = 0,

TModule*& module = ::Module,

TDocTemplate*& phead = DocTemplateStaticHead);

3) Just calls TDocTemplate's constructor.

TDocTemplateT* Clone(TModule* module,

TDocTemplate*& phead = DocTemplateStaticHead);

D* ConstructDoc(TDocument* parent = 0);

3) Undocumented function, but replaces the documented CreateDoc(). See TDocTemplate. Called by the document manager’s CreateDoc() and CreateAnyDoc() functions.

V* ConstructView(TDocument& doc);

3) Undocumented function, but replaces the documented CreateView(). See TDocTemplate. Called by the document manager’s InitDoc(), CreateView(), and CreateAnyView() members. Also called by TOleFactoryDocView<T, Auto>::CreateObject().

// The following [two] functions are maintained for backward compatibility (OWL 2.5

// comment)

D* CreateDoc(const char far* path, long flags=0);

1) Creates a document of type D based on the directory path (path) and flags value.

3) Obsolete. See ConstructDoc(). No one calls this function in OWL 2.5. The flags are the dtxxxx flags.

TView* CreateView(TDocument& doc, long flags=0);

1) CreateView creates the view specified by the document template class.

3) Obsolete. See ConstructView(). No one calls this function in OWL 2.5. The flags are the dtxxxx flags, but are ignored. The code is:
return (V*)InitView(new V((D&)doc));
which basically is a type safe encapsulation of calling InitView() with the result of calling the view's constructor.

D* IsMyKindOfDoc(TDocument& doc); // returns 0 if template can't support

1) Tests to see if the document (doc) is either the same class as the template's document class or a derived class. If the template can't use the document, IsMyKindOfDoc returns 0.

3) If your class was created by the ClassExpert, it set up a DEFINE_DOC_TEMPLATE_CLASS for you which writes an IsMyKindOfDoc member for you. That member simply does a TYPESAFE_DOWNCAST of the passed in doc to your document type. Note: This works well if each file extension in your program uses a TDocument subclass you wrote yourself, but it can cause problems if you use the stock TFileDocument. Take the DOCVIEW.IDE example provided by Borland. A .pts file can be saved as a dump-view as well as a line-view because the TDrawDocument can be downcast from a TDocument to a TFileDocument as well as to a TDrawDocument, and the dump-view is using the TFileDocument.

V* IsMyKindOfView(TView& view); // returns 0 if template incompatible

virtual const char far* GetViewName() {return V::StaticName();}

 

TStream, TInStream, TOutStream

 

TStream

1) An abstract base class, TStream provides links between streams and documents, views, and document files.

3) This class isn’t abstract in the usual C++ sense with =0 pure virtual functions, it is made abstract because it’s only constructor is protected.

Public members

TDocument& GetDocument() {return Doc;}

~TStream() {Doc.DetachStream(*this);}

int GetOpenMode();

const char far* GetStreamName();

Protected members

TDocument& Doc; // document owning this stream

TStream* NextStream; // next stream in linked list of active streams

TStream(TDocument& doc, const char far* name, int mode)

3)

TInStream and TOutStream simply add public constructors and mix in istream and ostream.

Related classes TStorageInStream, TStorageOutStream, TStorageStreamBase, and TStorageBuf are used by TStorageDocument and TOleDocument but are declared in the .cpp files and so cannot be used.

Support for Doc/View in other classes:

TApplication

TDocManager* GetDocManager() returns the document manager. This is used when you want to programmatically cause Doc/View things to happen, such as loading a document on startup. From any TWindow, you can call GetApplication()->GetDocManager() to get the document manager.

In addition, your application will need an EvNewView() and possibly an EvCloseView() member in your application class.

TMenuDescr

This class is used to alter an applications menu. It is normally used with Doc/View to merge menu items specific to a view with menu items which are always available. See the on-line help and the docview.ide sample program.

Events and how to make them

Let's take a look at how the standard events are defined. They are defined in two pieces: First, the argument types to the various functions are defined, then the EV_VN_ messages are defined, like this:

NOTIFY_SIG(vnViewOpened,TView*)

NOTIFY_SIG(vnViewClosed,TView*)

NOTIFY_SIG(vnDocOpened, int)

NOTIFY_SIG(vnDocClosed, int)

NOTIFY_SIG(vnCommit, BOOL)

NOTIFY_SIG(vnRevert, BOOL)

NOTIFY_SIG(vnIsDirty, void)

NOTIFY_SIG(vnIsWindow, HWND)

#define EV_VN_VIEWOPENED VN_DEFINE(vnViewOpened,VnViewOpened,pointer)

#define EV_VN_VIEWCLOSED VN_DEFINE(vnViewClosed,VnViewClosed,pointer)

#define EV_VN_DOCOPENED VN_DEFINE(vnDocOpened, VnDocOpened, int)

#define EV_VN_DOCCLOSED VN_DEFINE(vnDocClosed, VnDocClosed, int)

#define EV_VN_COMMIT VN_DEFINE(vnCommit, VnCommit, int)

#define EV_VN_REVERT VN_DEFINE(vnRevert, VnRevert, int)

#define EV_VN_ISDIRTY VN_DEFINE(vnIsDirty, VnIsDirty, void)

#define EV_VN_ISWINDOW VN_DEFINE(vnIsWindow, VnIsWindow, int)

Here is a handy table of the meanings of the constants:

Constant Meaning

vnCommit 1) Changes are committed to the document.

2) document is committing, flush cached changes

3) The document is about to write changes to the file. If the

view is keeping changes from the document for efficiency

reasons, it has to tell the document about them all when it

gets this notification.

vnCustomBase 1) Base event for document notifications.

2) base of document class specific notifications

3) Has the value of 100. Document classes you write are

supposed to use vnCustomBase+x for each of their custom

notifications. Documents don't send events to views that are

not their own, so you don't need to guarantee unique #s among

documents. Define these in your document and not your views

so they can be shared.

vnDocOpened 1&2) Document has been opened.

vnDocClosed 1&2) Document has been closed.

vnIsDirty 1&2) Is TRUE if uncommitted changes are present.

vnIsWindow 1&2) Is TRUE if the HWND passed belongs to this view.

vnRevert 1) Document's previous data is reloaded and overwrites the

view's current data.

2) document has reverted, reload data from doc

vnViewOpened 1) A new view has been constructed.

vnViewClosed 2) A view is about to be destroyed.

 

Now, in the Borland supplied line drawing example, three additional events were defined: Append a line, Delete a line, and Modify a line. Each of these worked with an unsigned int which was the # of the line. These events were defined thusly:

const int vnDrawAppend = vnCustomBase+0;

const int vnDrawDelete = vnCustomBase+1;

const int vnDrawModify = vnCustomBase+2;

NOTIFY_SIG(vnDrawAppend, unsigned int)

NOTIFY_SIG(vnDrawDelete, unsigned int)

NOTIFY_SIG(vnDrawModify, unsigned int)

#define EV_VN_DRAWAPPEND VN_DEFINE(vnDrawAppend, VnAppend, int)

#define EV_VN_DRAWDELETE VN_DEFINE(vnDrawDelete, VnDelete, int)

#define EV_VN_DRAWMODIFY VN_DEFINE(vnDrawModify, VnModify, int)

When I first started trying to make Doc/View apps, I didn't realize that I could put any old type I wanted into NOTIFY_SIG, because I didn't understand how it figured out what to do. But you can, provided it fits into 32 bits. Just follow the examples from DOCVIEW.H above. (DOCVIEW.H has a bunch of secret stuff in it to make this all work, it is quite hard to read...)

 

Frequently asked Questions

Why doesn't my Open() routine get called to read in my data?

The document manager is lazy; it doesn't open your document. Why? Well lets say you are writing the Borland C++ IDE with Doc/View. Now, you are going to need the .IDE, the .EXE (for debugging and browsing) and the .APX (for Class Expert) when a project is opened up. But loading in the whole .EXE might be a waste, so the system waits until it needs it. If you look at the line drawing example it does this to show you how to do it, although you don't get an actual benefit out of it in this case. (I'm not sure though if you wouldn't model a system like the C++ IDE with the .IDE file being a parent document, and the .EXE and .APX being children though...)

What the DOCVIEW.IDE line drawing example does is have the following code at the top of its GetLine() method:

if (!IsOpen() && !Open(ofRead | ofWrite))

return 0;

Paint() calls GetLine() which will call Open() the first time through. If you don’t like this you can add the bit dtAutoOpen in your DocTypeN declaration.

If you decide to add dtAutoOpen to an existing project, be warned that your Open() will not be called for File|New operations as it was when Open() was called in the document.

When I close a view, it is telling me I need to save the document when the view is not the last open view. Why?

You copied the CanClose() member from the line drawing example:

bool CanClose() {return TListBox::CanClose() && Doc->CanClose();}

This bit of code asks the document if it can close when the view closes, even though the document is not going to close. If your view doesn't hold any data, you don't want to call TDocument::CanClose() unless you are the last view on the screen:

bool CanClose()

{ return TListBox::CanClose() &&

(Doc->NextView(this) || Doc->NextView(0) != this ||

Doc->CanClose()); }

Where are those document titles coming from? Does SetDocTitle do ANYTHING?

SetDocTitle doesn't do anything except call the parent document, and there is rarely one of those. The only SetDocTitle() in OWL that does anything interesting is the one in TFrameWindow (which wasn’t in the BC 4.0x help). What it does is takes the frame's title, the document name, and the index number, and make a window caption. If you want to set the frame's title, you can do it in TMyApp::EvNewView by replacing the call to your MDI child constructor with something like this:

TMyMDIChild* child = new TMyMDIChild(*mdiClient,

view.GetViewName(), // caption

view.GetWindow());

My views menu items work fine, but for some reason the document manager has disabled File|Close and Window|Add View. Why? (I'm deriving from TView.)

You have to write a VnIsWindow() member AND you must put a EV_VN_ISWINDOW in the response table. If you don't the document manager thinks that your view isn't the active view, even when it has the focus. See TListBox::VnIsWindow() for an example.

Well, I have an EV_VN_ISWINDOW, and it still isn't working.

If your view has controls on it (for example if you are deriving from TView and TDialog), the example VnIsWindow() will not work for you, because the document manager is passing around the window with the focus, and you have given the focus to a control. Try this:

bool TMyDialog::VnIsWindow(HWND hWnd)

{

return ((HWindow == hWnd) || IsChild(hWnd));

}

This is frustrating for projects created by the "Dialog main window" option of BC 4.5x, because the AppExpert will generate a VnIsWindow() like this:

bool VnIsWindow(HWND hWnd) {return HWindow == hWnd;}

which will NEVER work for a dialog.

How can I have different icons for each kind of view?

I haven't found a clean way to do this, although a property could probably take care of it. In your EvNewView(), set the icon based upon the view class:

if (TYPESAFE_DOWNCAST(&view, TAlphaView))

child->SetIcon(this, IDI_ALPHA);

else if (TYPESAFE_DOWNCAST(&view, TBetaView))

child->SetIcon(this, IDI_BETA);

else if (TYPESAFE_DOWNCAST(&view, TGammaView))

child->SetIcon(this, IDI_GAMMA);

(When I suggested this method on the OWL mailing list, I was blasted for the non-object oriented nature of this solution. Some people think that all the views should inherit from a single view with a GetDesiredIcon() method, and the view should be TYPESAFE_DOWNCAST into that type and its GetDesiredIcon() method called. This is a good solution, although it would be better if the stock TView had something like this or even better if the ClassExpert could derive classes from your own base classes.)

I like the way the menu changes automatically in the docview example program, why can't I seem to get my menus to do this?

Changing the menus is your code's job, not the document manager's. In your EvNewView() member you want to add the following code before the call to setting the icon:

if (view.GetViewMenu())

child->SetMenuDescr(*view.GetViewMenu());

This of course assumes that you are setting the view menu up correctly in the constructor for your view. Once this has been done, TMDIChild::EvMDIActivate() will call TFrameWindow::MergeMenu() when the active view changes.

I believe if you want to change the speed bar for different window types as the Borland IDE does that you would need to do this in EvMDIActivate for your TMDIChild descendent but I have not tried this yet.

How can I read the command line and open the document on the command line at startup?

This is done in your InitInstance() member of your application class AFTER the call to TApplication::InitInstance(), like this:

// If a command line argument was given, try to open it as a file

if (_argc == 2)

GetDocManager()->CreateDoc(&__dvt1, _argv[1], 0, 0);

__dvt1 is a doc/view template given at the top of your application .cpp. If you have more than one document type, you need to get the file extension from _argv[1] and use that to determine which __dvtx variable to use. If you have more than one view, you need to specify the one you want to open by default. F Note that this method doesn’t work with OWL 2.0.

How can I add "recently accessed files" to the File menu?

There is no notification when a Document closes, but you can monitor for view closings and if you see the last view for a document closing add that item to the menu. Here is a code sample for an EvCloseView() method. Two variables, bool fAddedSeparator and the array char szFileCache[5][MAXPATH] were added to the application class, as well as changes in InitInstance() and ~TApplication() to read and write the cache list for the next time the program was started:

void myApp::EvCloseView (TView& view)

{

// Only update menu if this is the last view. TDocument::GetViewList() and

// TView::GetNextView() are undocumented public inline functions.

if (view.GetDocument().GetViewList()->GetNextView())

return;

const char *psz = view.GetDocument().GetDocPath();

// Don't update if there is no filename (After File|New...)

if (!psz)

return;

AddCacheTop(psz);

// Erase the existing menu items

TMenu menu(GetSubMenu(GetMainWindow()->GetMenu(), 0));

for (i = 0; i < nelems(szFileCache); i++)

menu.RemoveMenu(CM_FILE0+i, MF_BYCOMMAND);

// Put 'em on the menu

AddCachedFilenames(menu);

 

 

I don’t know if this is the best solution for updating this menu (other choices are requiring all of your documents to call a function when they are closed/destroyed and subclassing the document manager. nelems is a handy #define from K&R C which gives the number of elements in an array:

#define nelems(a) (sizeof (a)/sizeof (a)[0])

You will need to #define CM_FILE0 .. CM_FILEn in your .rh file, and you will need to write functions and response table entries to handle the menu items. (This example function only handles the first menu item, you would probably modify this to use a subroutine or a catch-all menu command function instead).

void myApp::CmFileCache0()

{

TryLoadDocument(szFileCache[0]);

}

I have not been able to find a good place to get the list of documents out of the document manager when the document manager is shutting down. I put this code in my applications CanClose which is generally called right before shutdown:

TDocument* doc = GetDocManager()->DocList.Next(0);

while (doc) {

AddCacheTop(doc->GetDocPath());

doc = GetDocManager()->DocList.Next(doc);

}

This code uses three subroutines I wrote:

void myApp::TryLoadDocument(const char *psz)

{

TDocument *doc;

if ((doc = GetDocManager()->FindDocument(psz)) != 0) {

GetDocManager()->PostDocError(*doc, IDS_DUPLICATEDOC);

return ;

}

GetDocManager()->CreateDoc(&__dvt2, psz, 0, 0);

}

void myApp::AddCachedFilenames(TMenu &menu)

{

// Add the menu items

char szBuffer[MAXPATH + 10];

for (int i = 0; i < nelems(szFileCache); i++)

if (szFileCache[i][0]) {

if (!fAddedSeparator) {

menu.AppendMenu(MF_BYCOMMAND|MF_SEPARATOR);

fAddedSeparator = TRUE;

}

wsprintf(szBuffer, "&%d %s", i+1, szFileCache[i]);

menu.AppendMenu(MF_BYCOMMAND|MF_ENABLED, CM_FILE0+i, szBuffer);

}

}

void myApp::AddCacheTop(const char *psz)

{

// Don't cache unnamed (File|New) documents

if (!psz)

return;

// psz must now be made first in szFileCache

for (int n = 0; n < nelems(szFileCache); )

if (stricmp(szFileCache[n++], psz) == 0)

break;

// Now copy the files down to allow the latest file to be first

for (int i = n-1; i > 0; i--)

strcpy(szFileCache[i], szFileCache[i-1]);

// Put the newly closed menu item at the top

strcpy(szFileCache[0], psz);

}

This routine adds the items under the last choice on the File menu, as the Borland IDE does. Newer programs like Microsoft Word ’95 seem to be adding the files before the exit item, so you may want to update this code. F Note that this method doesn’t work with OWL 2.0.

 

Common Pitfalls with Doc/View

Thinking the Class Expert is some kind of 'expert'

Class Expert might know a lot about dialogs and such, but it doesn't know much about Doc/View and if you think it does you will make certain common errors:

  1. Class Expert makes you think you want to derive from TListView. You probably don't.
  2. Class Expert doesn't know about multiple inheritance. You are going to be using multiple inheritance if you are using something like a list box that isn't a TWindow. Get used to only seeing a few functions for your class even when there are actually dozens.
  3. Class Expert doesn't know about StaticName(), because it is not virtual.
  4. Class Expert doesn't know about the EV_VN_ notifications.
  5. The code class expert will write for you often doesn't make any sense. For example, it will always put in a call to the base class's GetViewName(). You want to make sure you know what the call to the base class is doing.
Expecting TFileDocument to ACTUALLY DO some I/O

I/O is done by TInStream and TOutStream classes. TFileDocument knows two things: How to open and close files, and how to create those streams. The actual I/O is going to be done by either a TDocument descendent or a TView descendent. TFileDocument isn't going to be doing any I/O.

OK, I have a really nicely designed class hierarchy for the objects in my view container, and I can write them out but not read them in. Why?

TInStream and TOutStream are for writing out structs, not objects. All of the type information falls off your class as you write it to disk. You either need to prepend a word of type information before writing and look up the word in a table to call the correct constructor, or you need to use Borland's pstream, which do that for you. However, pstreams being what they are they don't mix well with regular streams. (You don't have to worry about this if you are writing out all of the same kind of object together, like the lines in the drawing example.)

How do I know whether I want to be holding the data in the document or the view?

First, do an experiment. Bring up the DOCVIEW example, and create a new line document. Set your pen to something thick, like 50, and draw some lazy meandering lines. Then use Window|Add View to add two copies of the view, and a single copy of line list view. Tile the windows, and then draw another line. Changes go to all of the views nicely. Then go to the line list view and change the color of a line. There is an ugly redraw.

In this case, the ugly redraw is unavoidable, because we changed the line and we want to see the change in all of the views. There is no way to "un-draw" a line easily. What you want to do is to make sure that you don't get this symptom unnecessarily. Keeping the data in the view keeps the system fast, but people like to be able to work on a single document in several different ways at the same time.

I have made my own list view, and there is an annoying line at the bottom of the view.

You forgot to set the style bits of your listbox, and it is trying to maintain an integral height. TListView uses the following modifications to the style bits:

Attr.Style &= ~(LBS_SORT);

Attr.Style |= (WS_HSCROLL | LBS_NOINTEGRALHEIGHT);

I need to be able to deal with errors in input and not open documents that are corrupt.

The standard way to not open a document is to return FALSE in ::Open(). However, that will not work unless you have the dtAutoOpen flag set, you will end up with an empty view instead of the "could not open document" message.

dtAutoOpen set up, you can display your own MessageBox and return TRUE if you want to accept the part of the file you could read, or FALSE if you want to fail the open.

If you need to throw exceptions when you fail, you probably want to catch them and return false so that your objects get cleared up correctly. The following seems to work inside open (you need a TRY, too):

CATCH( (xmsg& x)

{

GetDocManager().GetApplication()->Error(x, 0, 0);

return FALSE;

}

)

Not clearing up objects can cause the document manager to refuse to try to load them again if you aren't using dtAutoOpen.

I'm using a dialog box for a view, and it's getting a thick sizing frame about it, and the initial size is wrong.

In your EvNewView(), check for TDialog descendants:

bool fIsDialog = (TYPESAFE_DOWNCAST(&view, TDialog)) ?

TRUE : FALSE;

myMDIChild* child = new myMDIChild(*mdiClient,

view.GetViewName(), // caption

view.GetWindow(),

fIsDialog);

(This sets the frame to shrink to the view's initial size.)

if (fIsDialog)

child->Attr.Style &= ~(WS_THICKFRAME|WS_MAXIMIZEBOX);

(This makes the frame look like a modeless dialog's frame.)

What about TOcDocument and TOcView?

TOcDocument and TOcView are used by TOleDocument and TOleView. By themselves, they are not Doc/View classes. TOcDocument isn’t derived from anything, and TOcView is derived from TUnknown, IBContainer, IBContains, and IBDropDest.

How can I get Win’95 new common dialogs in a Doc/View application?

You need to subclass the document manager. Write a constructor and copy the function TDocManager::SelectDocPath(). Under Win’95, but NOT under NT, or the flag member of the ofn structure with OFN_EXPLORER (0x80000). (Under NT this seems to prevent the box from coming up at all. I haven’t tried this in an environment in which more than only template exists, so I am not sure if the hook still works.)

 

Glossary

Parent

(All of the examples use 0 for a parent, when would I put something else in?) The idea behind the parent is that documents can contain other documents as objects. For example, you can often insert a .BMP file by reference directly into a word processor. Because you aren't inserting the bits, you have another file, and you might want to have a graphical tool and be drawing on the picture in one window, and have the picture in a page of text in another. When you close the .BMP file you want to keep the word processing document up, but when you close the word processing document you want the .BMP to go away. The word processing document would then be considered the parent of the .BMP.

Property

Properties are facts about the document, like its filename and size. A lot of tricks are used with enums to get properties to inherit correctly. Properties also have the annoying problem of being any type, so you have to know what you want before you can get it. Properties defined in the base class TDocument are:

DocumentClass, // text

TemplateName, // text

ViewCount, // int

StoragePath, // text

DocTitle, // text

TFileDocument adds:

CreateTime, // FILETIME

ModifyTime, // FILETIME

AccessTime, // FILETIME

StorageSize, // unsigned long

FileHandle, // platform file handle (HFILE if Windows)

TStorageDocument adds:

CreateTime, // FILETIME

ModifyTime, // FILETIME

AccessTime, // FILETIME

StorageSize, // unsigned long

IStorageInstance, // IStorage*

TOleDocument doesn’t add any.

The example TDrawDocument for drawing lines adds:

LineCount,

Description,

etc.

Mix class

This is a class which is multiply derived from more than one class. It isn’t a Doc/View concept, but it is used in the documentation. An example is TWindowView, which mixes the functionality of TWindow and TView.

Appendix 1: Modes

1) This enum defines the document and open sharing modes used for constructing streams and storing data. Any constants that have the same functionality as those used by OLE 2.0 docfiles are indicated in the following table; for example, STGM_TRANSACTED, STGM_CONVERT, STGM_PRIORITY, and STGM_DELETEONRELEASE.

Although files are typically used for data storage, databases or spreadsheets can also be used. I/O streams rather than DOS use these bit values. Documents open the object used for storage in one of the following modes:

Constant Value Meaning

ofParent 0 A storage object is opened using the parent's mode.

ofRead 1 A storage object is opened for reading.

ofWrite 2 A storage object is opened for writing.

ofReadWrite 1|2 A storage object is opened for reading and writing.

ofAtEnd 4 Seek to end-of-file when opened originally.

ofAppend 8 Data is appended to the end of the storage object.

ofTruncate 10 An already existing file is truncated.

ofNoCreate 20 Open fails if file doesn't exist.

ofNoReplace 40 Open fails if file already exists.

ofBinary 80 Data is stored in a binary, not text, format. Carriage returns.

are not stripped.

ofIosMask FF All of the above bits are used by the ios class.

ofTransacted 1000 Changes to the storage object are preserved until the data is

either committed to permanent storage or discarded.

(STGM_TRANSACTED)

ofPreserve 2000 Backs up previous storage data using before creating a new

storage object with the same name. (STGM_CONVERT)

ofPriority 4000 Supports temporary, efficient reading before opening the storage.

(STGM_PRIORITY)

ofTemporary 8000 The storage or stream is automatically destroyed when it is

destructed. (STGM_DELETEONRELEASE)

3) These modes use used all over the place by the TDocument descendants and the document manager to figure out what it is supposed to be doing. So, when you look at a function, you have to figure out not only where it is called from and when it is called, but what modes were passed into it.

 

Appendix 2: Template constants

1) dtxxxx constants are used by TDocument and TDocTemplate to create templates. Several constants are equivalent to the OFN_xxxx constants defined by Windows in commdlg.h.

Constant

Windows

Meaning

dtAutoDelete

 

Deletes the document when the last view is deleted.

3) This has nothing to do with deleting the FILE the document was from. This flag causes the document manager to shut down the TDocument descendent when the last view for that document is closed. Otherwise it hangs around until the app is closed.

dtAutoOpen

 

Opens a document upon creation.

3) The TDocument's Open() member is called by the document manager after it creates the document in CreateAnyDoc().

THIS IS HOW YOU AVOID HAVING TO PUT THE CALL TO ISOPEN() AND OPEN() IN YOUR PAINT() ROUTINE!

dtCreatePrompt

OFN_CREATEPROMPT

Prompts the user before creating a document that does not currently exist.

dtFileMustExist

OFN_FILEMUSTEXIST

Lets the user enter only existing file names in the File Name entry field. If an invalid file name is entered, causes a warning message to be displayed.

dtHidden

 

Hides the template from the user's selection.

3) What this means is that this Doc/View PAIR will not be available from the File|New or File|Open choices on the menu. An example of this is the draw listbox which could only be created through Window|Add View, you couldn't load a .PTS file directly into one. You use this if you have more than one view for a document type, you make all of the non-primary views HIDDEN.

dtHideReadOnly

OFN_HIDEREADONLY

Hides the read-only check box.

dtNewDoc

 

Creates a new document with no path specified.

3) This flag is passed into the document manager when you want to create an empty of the kind of document, such as with File|New.

dtNoAutoView

 

Does not automatically create the default view type.

3) Not quite sure what this is to be used for. It keeps the view from opening up when you open up the file. I think this is so you can do things like emulate the IDE where you can open up a project and no windows come up if you didn't have any open when you last closed the project.

dtNoReadOnly

OFN_NOREADONLYRETURN

Returns the specified file as writeable.

dtNoTestCreate

OFN_NOTESTFILECREATE

Does not perform document-creation tests. The file is created after the dialog box is closed. If the application sets this flag, there is no check against write protection, a full disk, an open drive door, or network protection. For certain network environments, this flag should be set.

dtOverwritePrompt

OFN_OVERWRITEPROMPT

When the Save As dialog box is displayed, asks the user if it's OK to overwrite the file.

dtPathMustExist

OFN_PATHMUSTEXIST

Allows only valid document paths to be entered. If an invalid path name is entered, causes a warning message to be displayed.

dtProhibited

OFN_ALLOWMULTISELECT

Doesn't support these specified Windows options.

(OFN_ENABLEHOOK)

(OFN_ENABLETEMPLATE)

(OFN_ENABLETEMPLATEHANDLE)

dtReadOnly

OFN_READONLY

Checks the read-only check box when the dialog box is created.

dtSelected

 

Indicates the last selected template.

3) This is for if you have more than one type of document or view. When you define your views, you want one view to have this bit set. This is the view that will initially come up in the "Type of File" part of the common dialog and will give you your initial default extension. When you open a document, the document manager marks that view as dtSelected and clears the other one, this lets the common dialog have the same kind of default when you bring it up to open another document later.

dtSingleView

 

Provides only a single view for each document.

dtUpdateDir

 

Updates the directory with the dialog

3) if you mark all of your templates with this, then when the common dialog is brought up for another file, it starts in the same directory as the last view, instead of the current default directory. (In other words, it updates the document manager's CURRENT directory variable, it doesn't write anything to an MS-DOS directory.)

(All of the OFN_ flags relate to common dialog stuff. Look at the common dialog information in your help text.)

 

Appendix 3: Property flags

pfGetText    Property is accessible in a text format.
pfGetBinary    Property is accessible as a native nontext format.
pfConstant        Property can't be changed for the object instance.
pfSettable    Property can be set as a native format.
pfUnknown    Property is defined but unavailable for the object.
pfHidden           Property should be hidden from the user during normal browsing.
pfUserDef         Property has been user-defined at run time.

 

Appendix 4: Borland C++ and OWL versions

Borland C++ 3.x included OWL 1.x. OWL 1.x did not include Doc/View.

Borland C++ 4.0x included OWL 2.0x. OWL 2.0 included Doc/View, but not the OLE classes. It also didn’t have an AppExpert that could generate "Dialog Main Window" applications.

Borland C++ 4.5x and Turbo C++ 4.5 included OWL 2.5x. OWL 2.5 includes the OLE classes for Doc/View. It also supports a new built-in type, "bool". This replaces the old type "BOOL". The new type has two values, true and false. The old bool had TRUE and FALSE. Because this document was started in the old OWL 2.0 days, it uses bool for classes which existed in OWL 2.0. Note: The old BOOL is compatible with the new bool, so old programs will still compile. New programs should use "bool" unless they need to be distributed with source to people who are still using version 4.02.

Borland C++ 5.0 is to include OWL 3.0. This has not yet been released. At the time of this writing (9/13/95), OWL 3.0 promises "DocView enhancements for improved extensibility" but no details are available.

Resources for OWL programmers

There is an OWL mailing list. Details can be found on the WWW at http://www.mind.net/jfs/owl.html

There is an OWL newsgroup, comp.os.ms-windows.programmer.tools.owl

The only book I have found which discusses Doc/View is Object Windows 2.0 Programming by Tom Swan. Unlike some of the authors out there, Tom Swan knows OWL well, he even wrote some of the classes in OWL 2.0 such as TPrintout.

© Copyright

Regarding copyright, assume that everything marked with a 3) (that is, the stuff I wrote). is public domain.

I'm hoping everything marked with a 1) or a 2) is considered fair-use of Borland's copyrighted works...

Ed Snible

esnible@goodnet.com

Revised: December 04, 1999.


Copyright © 1998-2001 Yura Bidus. All rights reserved.