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()); }
TMyMDIChild* child = new TMyMDIChild(*mdiClient, view.GetViewName(), // caption view.GetWindow());
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.
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);
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)