Doc/View Frequently asked Questions

FAQs
  • Why doesn't my Open() routine get called to read in my data?
  • 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?
  • Where are those document titles coming from? Does SetDocTitle do ANYTHING?
  • 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.)
  • Well, I have an EV_VN_ISWINDOW, and it still isn't working.
  • How can I have different icons for each kind of view?
  • 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?
  • How can I read the command line and open the document on the command line at startup?
  • How can I add recently accessed files to the File menu?
  • Pitfalls
  • Thinking the Class Expert is some kind of 'expert'
  • Expecting TFileDocument to ACTUALLY DO some 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?
  • How do I know whether I want to be holding the data in the document or the view?
  • I have made my own list view, and there is an annoying line at the bottom of the view.
  • I need to be able to deal with errors in input and not open documents that are corrupt.
  • 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.
  • What about TOcDocument and TOcView?
  • How can I get Win'95 new common dialogs in a Doc/View application?
  • -------------

    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. *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. FNote 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.

    With 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.)

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

    For more information on Doc/View, you can download 'Doc/View & You', a Word 6.x document. This includes some documentation written by me, Borland's documentation annotated, and this FAQ. You can get this from docview.zip.

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

    Borland C++ info
    (old link)