Step 12 introduces the Doc/View model of programming, which is based on
the principle of separating data from the interface for that data. Essentially, the data
is encapsulated in a document object, which is derived from the TDocument class, and
displayed on the screen and manipulated by the user through a view object, which is
derived from the TView class.
The Doc/View model permits a greater degree of flexibility in how you
present data than does a model that links data encapsulation and user interface into a
single class. Using the Doc/View model, you can define a document class to contain any
type of data, such as a simple text file, a database file, or in this tutorial, a line
drawing. You can then create a number of different view classes, each one of which
displays the same data in a different manner or lets the user interact with that data in a
different way.
For Step 12, however, you'll simply convert the application from its
current model to the Doc/View model. Step 12 uses the SDI model so that you can more
easily see the changes necessary for converting to Doc/View without being distracted by
the extra code added in Step 11 to support MDI functionality. (You'll create an MDI
Doc/View application in Step 13.) But even though the code for Step 12 will look very
different from the code from Step 10, the running application for Step 12 will look nearly
identical to that of Step 10. You can find the source for Step 12 in the files STEP12.CPP,
STEP12.RC, STEP12DV.CPP, and STEP12DV.RC in the directory EXAMPLES\OWL\TUTORIAL.
Organizing the application source
The source for Step 12 is divided into four source files:
- STEP12.CPP contains the application
object and its member definitions. It also contains the OwlMain function.
- STEP12.RC contains identifiers for
events controlled by the application object, the resources for the frame window and its
decorations, the About dialog box, and the application menu.
- STEP12DV.CPP contains the TLine
class, the document class TDrawDocument, the view class TDrawView, and the associated
member function definitions for each of these classes.
- STEP12DV.RC contains identifiers for
events controlled by the view object and the resources for the view.
You should divide your
Doc/View code this way to distinguish the document and its supporting view from the
application code. The application code provides the support framework for the document and
view classes, but doesn't contribute directly to the functionality of the Doc/View model.
This also demonstrates good design practice for code reusability.
Doc/View model
The Doc/View model is based on three ObjectWindows classes:
- The TDocument class encapsulates and
controls access to a set of data. A document object handles user access to that data
through input from associated view objects. A document object can be associated with
numerous views at the same time (for the sake of simplicity in this example, the document
object is associated with only a single view object).
- The TView class provides an
interface between a document object and the user interface. A view object controls how
data from document object is displayed on the screen. A view object can be associated with
only a single document object at any one time.
- The TDocManager class coordinates
the associations between a document object and its view objects. The document manager
provides a default File menu and default handling for each of the choices on the File
menu. It also maintains a list of document templates, each of which specifies a
relationship between a document class and a view class.
The TDocument and TView
classes provide the abstract functionality for document and view objects. You must provide
the specific functionality for your own document and view classes. You must also
explicitly create the document manager and attach it to the application object. You must
also provide the document templates for the document manager. These steps are described in
the following sections.
TDrawDocument class
The TDrawDocument class is derived from the ObjectWindows class
TFileDocument, which is in turn derived from the TDocument class. TDocument provides a
number of input and output functions. These virtual functions return dummy values
and have no real functionality. TFileDocument provides the basic functionality required to
access a data file in the form of a stream.
TDrawDocument uses the functionality contained in TFileDocument to
access line data stored in a file. It uses a TLines array to contain the lines, the same
as in earlier steps. The array is referenced through a pointer called Lines.
Creating and destroying TDrawDocument
TDrawDocument's constructor takes a single parameter, a TDocument *,
that is a pointer to the parent document. A document can be a parent of a number of other
documents, treating the data contained in those documents as if it were part of the
parent. The constructor passes the parent pointer on to TFileDocument. The constructor
also initializes the Lines data member to 0.
The destructor for TDrawDocument deletes the TLines object pointed to
by Lines.
Storing line data
The document class you're going to create controls access to the
data contained in a drawing. But you still need some way to store the data. You've already
created the TLine class and the TLines array in previous steps. Luckily, this code can be
recycled. The line data for each document is stored in a TLines array, and accessed by the
document through a protected TLines * data member called Lines.
The TPoints and TLines arrays, their iterators, and the TLine class
are now defined in the STEP12DV.CPP file. In the Doc/View model, these classes are an
integral part of the document class you're about to build. The code for these classes
doesn't change at all from Step 10.
Implementing TDocument virtual functions
TDrawDocument needs to implement a few of the virtual
functions inherited from TDocument. These functions provide streaming and the ability to
commit changes to the document or to discard all changes made to the document since the
last save.
Opening and closing a drawing
Although TFileDocument provides the basic functionality required for
stream input and output, it doesn't know how to read the data for a line. To provide this
ability, you need to override the Open and Close functions.
Here's the signature of the Open function:
bool Open(int mode, const char far* path=0);
where:
- mode is the file open mode. In this
case, you can ignore the mode parameter; the file is opened the same way each time, with
the ofRead flag.
- path contains the document path. If
a path is specified, the document's current path is changed to that path. If no path is
specified (that is, path takes its default value), the path is left as it is. The path is
used by the document when creating the document's streams.
The Open function is
similar to the OpenFile function used in earlier steps in the tutorial. There are
differences, though:
- The Open function creates the TLines
array for the document object. In earlier steps, this was done in the TDrawWindow
constructor, because TDrawWindow was responsible for containing all the TLine objects. Now
the document is responsible for containing all the TLine objects, so it needs to create
storage space for the data before it reads it in.
- If path is passed in, Open sets the
document path to path with the SetDocPath function.
- Open checks whether the document has
a path. If the document doesn't have a path, it is a new document, in which case there's
no need to read in data from a file. If the document has a path, Open calls the InStream
function. This function is defined in TFileDocument and returns a TInStream *. TInStream
is the standard input stream class used by Doc/View classes. TInStream is derived from
TStream and istream. TStream is an abstract base class that lets documents access standard
streams. TInStream is essentially a standard istream adapted for use with the Doc/View
model. There's also a corresponding TOutStream class, derived from TStream and ostream.
You'll use TOutStream when you create the Commit function.
- After the input stream has been created, the data
is read in and placed in the TLines array pointed to by Lines. When all the data is read
in, the input stream is deleted.
- Open then calls the SetDirty
function, passing false as the function parameter. The SetDirty function, and its
equivalent access function isDirty, are the equivalent of the IsDirty flag in earlier
steps of the tutorial. A document is considered to be dirty if it contains any changes to
its data that have not been saved or committed.
- The last thing the Open function
needs to do is return. If the document was successfully opened, Open returns true.
Here's how the code for
your Open function might look:
bool
TDrawDocument::Open(int /*mode*/, const char far* path)
{
Lines = new TLines(5, 0, 5);
if (path)
SetDocPath(path);
if (GetDocPath()) {
TInStream* is = InStream(ofRead);
if (!is)
return false;
unsigned numLines;
char fileinfo[100];
*is >> numLines;
is->getline(fileinfo, sizeof(fileinfo));
while (numLines-) {
TLine line;
*is >> line;
Lines->Add(line);
}
delete is;
}
SetDirty(false);
NotifyViews(vnRevert, false);
return true;
}
Closing the drawing is less complicated.
The Close function discards the document's data and cleans up. In this case, it deletes
the TLines array referenced by the Lines data member and returns true. Here's how the code
for your Close function should look:
bool TDrawDocument::Close() { delete Lines; Lines = 0; return true; }
Lines
is set to 0, both in the constructor and after closing the document, so that you can
easily tell whether the document is open. If the document is open, Lines points to a
TLines array, and is therefore not 0. But setting Lines to 0 makes it easy to check
whether the document is open. The IsOpen function lets you check this from outside the
document object:
bool IsOpen() { return Lines != 0; }
Saving and discarding changes
TDocument provides two functions for saving and discarding changes
to a document:
- The Commit function commits changes
made in the document's associated views by incorporating the changes into the document,
then saving the data to persistent storage. Commit takes a single parameter, a bool. If
this parameter is false, Commit saves the data only if the document is dirty. If the
parameter is true, Commit does a complete write of the data. The default for this
parameter is false.
- The Revert function discards any
changes in the document's views, then forces the views to load the data contained in the
document and display it. Revert takes a single parameter, a bool. If this parameter is
true, the view clears its window and does not reload the data from the document. The
default for this parameter is false.
For TDrawDocument, the
document is updated as each line is drawn in the view window. The only function of Commit
for the TDrawDocument class is to save the data to a file.
Commit checks to see if the document is dirty. If not, and if the
force parameter is false, Commit returns true, indicating that the operation was
successful.
If the document is dirty, or if the force parameter is true, Commit
saves the data. The procedure to save the data is similar to the SaveFile function in
previous steps, but, as with the Open function, there are a few differences.
Commit calls the OutStream function to open an output stream. This
function is defined in TFileDocument and returns a TOutStream *. Commit then writes
the data to the output stream. The procedure for this is almost exactly identical to that
used in the old SaveFile function.
After writing the data to the output stream, Commit turns the IsDirty
flag off by calling SetDirty with a false parameter. It then returns true, indicating that
the operation was successful.
Here's how the code for your Commit function might look:
bool
TDrawDocument::Commit(bool force)
{
if (!IsDirty() && !force)
return true;
TOutStream* os = OutStream(ofWrite);
if (!os)
return false;
// Write the number of lines in the figure
*os << Lines->GetItemsInContainer();
// Append a description using a resource string
*os << ' ' << string(*GetDocManager().GetApplication(),IDS_FILEINFO) << '
// Get an iterator for the array of lines
TLinesIterator i(*Lines);
// While the iterator is valid (i.e. you haven't run out of lines)
while (i) {
// Copy the current line from the iterator and increment the array.
*os << i++;
}
delete os;
SetDirty(false);
return true;
}
There's only one thing in the Commit
function that you haven't seen before:
// Append a description using a resource string
*os << ' ' << string(*GetDocManager().GetApplication(), IDS_FILEINFO) << '
This uses a special constructor for the
ANSI string class:
string(HINSTANCE instance, uint id, int len = 255);
This constructor lets you get a string
resource from any Windows application. You specify the application by passing an HINSTANCE
as the first parameter of the string constructor. In this case, you can get the current
application's instance through the document manager. The GetDocManager function returns a
pointer to the document's document manager. In turn, the GetApplication function returns a
pointer to the application that contains the document manager. This is converted
implicitly into an HINSTANCE by a conversion operator in the TModule class. The second
parameter of the string constructor is the resource identifier of a string defined in
STEP12DV.RC. This string contains version information that can be used to identify the
application that created the document.
The Revert function takes a single parameter, a bool indicating
whether the document's views need to refresh their display from the document's data.
Revert calls the TFileDocument version of the Revert function, which in turn calls the
TDocument version of Revert. The base class function calls the NotifyViews function with
the vnRevert event. The second parameter of the NotifyViews function is set to the
parameter passed to the TDrawDocument::Revert function. TFileDocument::Revert sets IsDirty
to false and returns. If TFileDocument::Revert returns false, the TDrawDocument should
also return false.
If TFileDocument::Revert returns true, the TDrawDocument function
should check the parameter passed to Revert. If it is false (that is, if the view needs to
be refreshed), Revert calls the Open function to open the document file, reload the data,
and display it.
Here's how the code for your Revert function might look:
bool
TDrawDocument::Revert(bool clear)
{
if (!TFileDocument::Revert(clear))
return false;
if (!clear)
Open(0);
return true;
}
Accessing the document's data
There are two main ways to access data in TDrawDocument: adding a
line (such as a new line when the user draws in a view) and getting a reference to a line
in the document (such as getting a reference to each line when repainting the window). You
can add two functions, AddLine and GetLine, to take care of each of these actions.
The AddLine function adds a new line to the document's TLines array.
The line is passed to the AddLines function as a TLine &. After adding the line
to the array, AddLine sets the IsDirty flag to true by calling SetDirty. It then returns
the index number of the line it just added. Here's how the code for your AddLines function
might look:
int
TDrawDocument::AddLine(TLine& line)
{
int index = Lines->GetItemsInContainer();
Lines->Add(line);
SetDirty(true);
return index;
}
The GetLine function takes an int
parameter. This int is the index of the desired line. GetLine should first check to
see if the document is open. If not, it can try to open the document. If the document
isn't open and GetLine can't open it, it returns 0, meaning that it couldn't find a valid
document from which to get the line.
Once you know the document is valid, you should also check to make
sure that the index isn't too high. Compare the index to the return value from the
GetItemsInContainer function. As long as the index is less, you can return a pointer to
the TLine object. Here's how the code for your GetLine function might look:
TLine* TDrawDocument::GetLine(int index)
{
if (!IsOpen() && !Open(ofRead | ofWrite))
return 0;
return index < Lines->GetItemsInContainer() ? &(*Lines)[index] : 0;
}
TDrawView class
The TDrawView class is derived from the ObjectWindows TWindowView
class, which is in turn derived from the TView and TWindow classes. TView doesn't have any
inherent windowing capabilities; a TView-derived class gets these capabilities by either
adding a window member or pointer or by mixing in a window class with a view class.
TWindowView takes the latter approach, mixing TWindow and TView to
provide a single class with both basic windowing and viewing capabilities. By deriving
from this general-purpose class, TDrawView needs to add only the functionality required to
work with the TDrawDocument class.
The TDrawView is similar to the TDrawWindow class used in previous
steps. In fact, you'll see that a lot of the functions from TDrawWindow are brought
directly to TDrawView with little or no modifications.
TDrawView data members
The TDrawView class has a number of protected data members.
TDC *DragDC;
TPen *Pen;
TLine *Line;
TDragDocument *DrawDoc;
Three of these should look familiar to
you. DragDC, Pen, and Line perform the same function in TDrawView as they did in
TDrawWindow.
Although a document can exist with no associated views, the opposite
isn't true. A view must be associated with an existing document. TDrawView is attached to
its document when it is constructed. It keeps track of its document through a
TDrawDocument * called DrawDoc. The base class TView has a TDocument *
member called Doc that serves the same basic purpose. In fact, during base class
construction, Doc is set to point at the TDrawDocument object passed to the TDrawView
constructor. DrawDoc is added to force proper type compliance when the document pointer is
accessed.
Creating the TDrawView class
The TDrawView constructor takes two parameters, a TDrawDocument &
(a reference to the view's associated document) and a TWindow * (a pointer to the
parent window). The parent window defaults to 0 if no value is supplied. The constructor
passes its two parameters to the TWindowView constructor, and initializes the DrawDoc
member to point at the document passed as the first parameter.
The constructor also sets DragDC to 0 and initializes Line with a new
TLine object.
The last thing the constructor does is set up the view's menu. You
can use the TMenuDescr class to set up a menu descriptor from a menu resource. Here's the
TMenuDescr constructor:
TMenuDescr(TResId id);
where id is the resource identifier of the
menu resource.
The TMenuDescr constructor takes the menu resource and divides it up
into six groups. It determines which group a particular menu in the resource goes into by
the presence of separators in the menu resource. The only separators that actually divide
the resource into groups are at the pop-up level; that is, the separators aren't contained
in a menu, but they're at the level of menu items that appear on the menu bar. For
example, the following code shows a small snippet of a menu resource:
COMMANDS MENU
{
// Always starts with the File group
POPUP "&File"
{
MENUITEM "&Open", CM_FILEOPEN
MENUITEM "&Save", CM_FILESAVE
}
MENUITEM SEPARATOR
// Edit group
MENUITEM SEPARATOR
// Container group
MENUITEM SEPARATOR
// This one is in the Object group
POPUP "&Objects"
{
MENUITEM "&Copy object", CM_OBJECTCOPY
MENUITEM "Cu&t object", CM_OBJECTCUT
}
// No more items, meaning the Window group and Help group are also empty
}
A menu descriptor would separate this
resource into groups like this: the File menu would be placed in the first group, called
the File group. The second group (Edit group) and the third group (Container group) are
empty, because there' s no pop-up menus between the separators that delimit those groups.
The Tools menu is in the Object group. Because there are no menu resources after the Tools
menu, the last two groups, the Object group and Help group, are also empty.
Although the groups have particular names, these names just
represent a common name for the menu group. The menu represented by each group does not
necessarily have that name. The document manager provides a default File menu, but the
other menu names can be set in the menu resource.
In this case, the view supplies a menu resource called IDM_DRAWVIEW,
which is contained in the file STEP12DV.RC. This menu is called Tools, which has the same
choices on it as the Tools menu in earlier steps: Pen Size and Pen Color. To insert the
Tools menu as the second menu on the menu bar when the view is created or activated, the
menu resource is set up to place the Tools menu in the second group, the Edit group, so
that the menu resource looks something like this:
IDM_DRAWVIEW MENU
{
// Edit Group
MENUITEM SEPARATOR
POPUP "&Tools"
{
MENUITEM "Pen &Size", CM_PENSIZE
MENUITEM "Pen &Color", CM_PENCOLOR
}
}
You can install the menu descriptor as the
view menu using the TView function SetViewMenu function, which takes a single parameter, a
TMenuDescr *. SetViewMenu sets the menu descriptor as the view's menu. When the
view is created, this menu is merged with the application menu.
Here's how the call to set up the view menu should look:
SetViewMenu(new TMenuDescr(IDM_DRAWVIEW));
The destructor for the view deletes the
device context referenced by DragDC and the TLine object referenced by Line.
Naming the class
Every view class should define the function StaticName, which takes
no parameters and returns a static const char far *. This function should return
the name of the view class. Here's how the StaticName function might look:
static const char far* StaticName() {return "Draw View";}
Protected functions
TDrawView has a couple of protected access functions to
provide functionality for the class.
The GetPenSize function is identical to the TDrawWindow function
GetPenSize. This function opens a TInputDialog, gets a new pen size from the user, and
changes the pen size for the window and calls the SetPen function of the current line.
The Paint function is a little different from the Paint function in
the TDrawWindow class, but it does basically the same thing. Instead of using an iterator
to go through the lines in an array, TDrawView::Paint calls the GetLine function of the
view's associated document. The return from GetLine is assigned to a const TLine *
called line. If line is not 0 (that is, if GetLine returned a valid line), Paint then
calls the line's Draw function. Remember that the TLine class is unchanged from Step 10.
The line draws itself in the window.
Here's how the code for the Paint function might look:
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);
}
Event handling in TDrawView
The TDrawView class handles many of the events that were previously
handled by the TDrawWindow class. Most of the other events that TDrawWindow handled that
aren't handled by TDrawView are handled by the application object and the document
manager; this is discussed later in Step 12.
In addition, TDrawView handles two new messages: VN_COMMIT and
VN_REVERT. These view notification messages are sent by the view's document when the
document's Commit and Revert functions are called.
Here's the response table definition for TDrawView:
DEFINE_RESPONSE_TABLE1(TDrawView, TWindowView)
EV_WM_LBUTTONDOWN,
EV_WM_RBUTTONDOWN,
EV_WM_MOUSEMOVE,
EV_WM_LBUTTONUP,
EV_COMMAND(CM_PENSIZE, CmPenSize),
EV_COMMAND(CM_PENCOLOR, CmPenColor),
EV_VN_COMMIT,
EV_VN_REVERT,
END_RESPONSE_TABLE;
The following functions are nearly the
same in TDrawView as the corresponding functions in TDrawWindow. Any modifications to the
functions are noted in the right column of the table:
Function |
TDrawView version |
EvLButtonDown |
Does not set IsDirty. This is
taken care of in EvLButtonUp. |
EvRButtonDown |
No change. |
EvMouseMove |
No change. |
EvLButtonUp |
Checks to see if the mouse was moved
after the left button press. If so, calls the document's AddLine function to add
the point. |
CmPenSize |
No change. |
CmPenColor |
No change. |
The VnCommit function always
returns true. In a more complex application, this function would add any cached data to
the document, but in this application, the data is added to the document as each line is
drawn.
The VnRevert function invalidates the display area, clearing it and
repainting the drawing in the window. It then returns true.
Defining document templates
Once you've created a document class and an accompanying view class,
you have to associate them so they can function together. An association between a
document class and a view class is known as a document template class. The document
template class is used by the document manager to determine what view class should be
opened to display a document.
You can create a document template class using the macro
DEFINE_DOC_TEMPLATE_CLASS, which takes three parameters. The first parameter is the name
of the document class, the second is the name of the view class, and the third is the name
of the document template class. The macro to create a template class for the TDrawDocument
and TDrawView classes would look like this:
DEFINE_DOC_TEMPLATE_CLASS(TDrawDocument, TDrawView, DrawTemplate);
Once you've created a document template
class, you need to create a document registration table. Document registration tables
contain information about a particular Doc/View template class instance, such as what the
template class does, the default file extension, and so on. A document registration table
is actually an object of type TRegList, although you don't have to worry about what the
object actually looks; you'll very rarely need to directly access a document registration
table object.
Start creating a document registration table by declaring the
BEGIN_REGISTRATION macro. This macro takes a single parameter, the name of the document
registration class, which is used as the name of the TRegList object.
The next lines in your document registration table create entries in
the document registration table. For a Doc/View template, you need to enter four items
into this table:
- A description of the Doc/View
template
- The default file extension when
saving a file
- A filter string that is used to
filter file names in the current directory
- Document creation flags
For the first three of
these, you specify them using the REGDATA macro:
REGDATA(key, value)
key indicates what the value string
pertains to. There are three different keys you need for creating a document registration
table:
- description indicates value is the
template description
- extension indicates value is the
default file extension
- docfilter indicates value is the
file-name filter
- The other macro you need to use to
create a document registration table is the REGDOCFLAGS macro. This macro takes a single
parameter, one or more document creation flags; if you specify more than one, the flags
should be ORed together. For now, you can get by using two flags, dtAutoDelete and
dtHidden. These flags are described in the ObjectWindows Reference Guide and
"Doc/View objects" of the ObjectWindows Programmer's Guide.
A typical document
registration table looks something like this:
BEGIN_REGISTRATION(DrawReg)
REGDATA(description, "Point Files (*.PTS)")
REGDATA(extension, ".PTS")
REGDATA(docfilter, "*.pts")
REGDOCFLAGS(dtAutoDelete | dtHidden)
END_REGISTRATION
Once you've created a document
registration table, all you need to do is create an instance of the class. The class type
is the name of the document template class. You also should give the instance a meaningful
name. The constructor for any document template class looks like this:
TplName name(TRegList& reglist);
where:
- TplName is the class name you
specified when defining the template class.
- name is whatever name you want to
give this instance.
- reglist is the name of the
registration table you created; it's the same name you passed as the parameter to the
BEGIN_REGISTRATION macro.
Here's how the template
instance for TDrawDocument and TDrawView classes might look:
DrawTemplate drawTpl(DrawReg);
Supporting Doc/View in the application
STEP12.CPP contains the code for the application object and the
definition of the main window. The application object provides a framework for the
Doc/View classes defined in STEP12DV.CPP. This section discusses the changes to the
TDrawApp class that are required to support the new Doc/View classes. The OwlMain function
remains unchanged.
InitMainWindow function
The InitMainWindow function requires some minor changes to support
the Doc/View model:
- The TDecoratedFrame constructor
takes a 0 in place of the TDrawWindow constructor for the frame's client window. The
client window is set in the EvNewView function.
- The AssignMenu call is changed to a
SetMenuDescr call. The SetMenuDescr function, which is inherited from TFrameWindow, takes
a TMenuDescr as its only parameter. The TMenuDescr object should be built using the
COMMANDS menu resource. This call looks something like this:
GetMainWindow()->SetMenuDescr(TMenuDescr("COMMANDS"));
- A call to SetDocManager is added.
This function sets the DocManager member of the TApplication class. It takes a single
parameter, a TDocManager *.
- The TDocManager constructor takes a
single parameter, which consists of one or more flags ORed together. The only flag that is
required is either dmSDI or dmMDI. These flags set the document manager to supervise a
single-document interface (dmSDI) or a multiple-document interface (dmMDI) application. In
this case, you're creating an SDI application, so you should specify the dmSDI flag. In
addition, you should specify the dmMenu flag, which instructs the document manager to
provide its default menu.The call to the SetDocManager function should look
like this:
SetDocManager(new TDocManager(dmSDI | dmMenu, this));
In OWL5 and OWLNext the constructor of TDocManager was changed
to include a TApplication parameter.
InitInstance function
The InitInstance function is overridden because there are a couple
of function calls that need to be made after the main window has been created.
InitInstance should first call the TApplication version of InitInstance. That function
calls the InitMainWindow function, which constructs the main window object, then creates
the main window.
After the base class InitInstance function has been called, you need
to call the main window's DragAcceptFiles function, specifying the true parameter. This
enables the main window to accept files that are dropped in the window. Drag and drop
functionality is handled through the application's response table, as discussed in the
next section.
To enable the user to begin drawing in the window as soon as the
application starts up, you also need to call the CmFileNew function of the document
manager. This creates a new untitled document and view in the main window.
The InitInstance function should look something like this:
void
TDrawApp::InitInstance()
{
TApplication::InitInstance();
GetMainWindow()->DragAcceptFiles(true);
GetDocManager()->CmFileNew();
}
Adding functions to TDrawApp
The TDrawApp class adds a number of new functions. It overrides the
TApplication version of InitInstance. It adds a response table and takes the CmAbout
function from the TDrawWindow class. It adds drag and drop capability by adding the
EV_WM_DROPFILES macro to the response table and adding the EvDropFiles function to handle
the event. It also handles a new event, WM_OWLVIEW, that indicates a view request message.
Two functions handle this message. EvNewView handles a WM_OWLVIEW message with the
dnCreate parameter. EvCloseView handles a WM_OWLVIEW message with the dnClose parameter.
Here's the new declaration of the TDrawApp class, along with its
response table definition:
class TDrawApp : public TApplication
{
public:
TDrawApp() : TApplication() {}
protected:
// Override methods of TApplication
void InitInstance();
void InitMainWindow();
// Event handlers
void EvNewView (TView& view);
void EvCloseView(TView& view);
void EvDropFiles(TDropInfo dropInfo);
void CmAbout();
DECLARE_RESPONSE_TABLE(TDrawApp);
};
DEFINE_RESPONSE_TABLE1(TDrawApp, TApplication)
EV_OWLVIEW(dnCreate, EvNewView),
EV_OWLVIEW(dnClose, EvCloseView),
EV_WM_DROPFILES,
EV_COMMAND(CM_ABOUT, CmAbout),
END_RESPONSE_TABLE;
CmAbout function
The CmAbout function is nearly identical to the TDrawWindow version.
The only difference is that the CmAbout function is no longer contained in its parent
window class. Instead of using the this pointer as its parent, it substitutes a
call to GetMainWindow function. The function should now look like this:
void
TDrawApp::CmAbout()
{
TDialog(GetMainWindow(), IDD_ABOUT).Execute();
}
EvDropFiles function
The EvDropFiles function handles the WM_DROPFILES event. This
function gets one parameter, a TDropInfo object. The TDropInfo object contains functions
to find the number of files dropped, the names of the files, where the files were dropped,
and so on.
Because this is a SDI application, if the number of files is greater
than one, you need to warn the user that only one file can be dropped into the application
at a time. To find the number of files dropped in, you can call the TDropInfo function
DragQueryFileCount, which takes no parameters and returns the number of files dropped. If
the file count is greater than one, pop up a message box to warn the user.
Now you need to get the name of the file dropped in. You can find
the length of the file path string using the TDropInfo function DragQueryFileNameLen,
which takes a single parameter, the index of the file about which you're inquiring.
Because you know there's only one file, this parameter should be a 0. This function
returns the length of the file path.
Allocate a string of the necessary length, then call the TDropInfo
function DragQueryFile. This function takes three parameters. The first is the index of
the file. Again, this parameter should be a 0. The second parameter is a char *,
the file path. The third parameter is the length of the file path. This function fills in
the file path in the char array from the second parameter.
Once you've got the file name, you need to get the proper template
for the file type. To do this, call the document manager's MatchTemplate function. This
function searches the document manager's list of document templates and returns a pointer
to the first document template with a pattern that matches the dropped file. This pointer
is a TDocTemplate *. If the document manager can't find a matching template, it
returns 0.
Once you've located a template, you can call the template's
CreateDoc function with the file path as the parameter to the function. This creates a new
document and its corresponding view, and opens the file into the document.
Once the file has been opened, you must make sure to call the
DragFinish function. This function releases the memory that Windows allocates during drag
and drop operations.
Here's how the EvDropFiles function should look:
void
TDrawApp::EvDropFiles(TDropInfo dropInfo)
{
if (dropInfo.DragQueryFileCount() != 1)
::MessageBox(0,"Can only drop 1 file in SDI mode","Drag/Drop Error",MB_OK);
else {
int fileLength = dropInfo.DragQueryFileNameLen(0)+1;
char* filePath = new char [fileLength];
dropInfo.DragQueryFile(0, filePath, fileLength);
TDocTemplate* tpl = GetDocManager()->MatchTemplate(filePath);
if (tpl)
GetDocManager()->CreateDoc(tpl, filePath);
//Old tpl->CreateDoc(filePath); // TDocTemplate::CreateDoc() is obsolete, use TDocManager::CreateDoc() instead
delete filePath;
}
dropInfo.DragFinish();
}
EvNewView function
The WM_OWLVIEW event informs the application when a view-related
event has happened. All functions that handle WM_OWLVIEW events return void and
take a single parameter, a TView &. When the event's parameter is dnCreate,
this indicates that a new view object has been created and requires the application to set
up the view's window.
In this case, you need to set the view's window as the client of the
main window. There are two functions you need to call to do this: GetWindow and
SetClientWindow.
The GetWindow function is member of the view class. It takes no
parameters and returns a TWindow *. This points to the view's window.
Once you have a pointer to the view's window, you can set that
window as the client window with the main window's SetClientWindow function, which takes a
single parameter, a TWindow *, and sets that window object as the client window.
This function returns a TWindow *. This return value is a pointer to the old client
window, if there was one.
Before continuing, you should check that the new client window was
successfully created. TView provides the IsOK function, which returns false if the window
wasn't created successfully. If IsOK returns false, you should call SetClientWindow again,
passing a 0 as the window pointer, and return from the function.
If the window was created successfully, you need to check the view's
menu with the GetViewMenu function. If the view has a menu, use the MergeMenu function of
the main window to merge the view's menu with the window's menu.
The code for EvNewView should look like this:
void
TDrawApp::EvNewView(TView& view)
{
GetMainWindow()->SetClientWindow(view.GetWindow());
if (!view.IsOK())
GetMainWindow()->SetClientWindow(0);
else if (view.GetViewMenu())
GetMainWindow()->MergeMenu(*view.GetViewMenu());
}
EvCloseView function
If the parameter for the WM_OWLVIEW event is dnClose, this
indicates that a view has been closed. This is handled by the EvCloseView parameter. Like
the EvNewView function, the EvCloseView function returns void and takes a TView &
parameter.
To close a view, you need to remove the view's window as the client
of the main window. To do this, call the main window's SetClientWindow function, passing a
0 as the window pointer. You can then restore the menu of the frame window to its former
state using the RestoreMenu function of the main window.
When the EvNewView function creates a new view, the caption of the
frame window is set to the file path of the document. You need to reset the main window's
caption using the SetCaption function.
Here's the code for the EvCloseView function:
void
TDrawApp::EvCloseView(TView& /*view*/)
{
GetMainWindow()>SetClientWindow(0);
GetMainWindow()->RestoreMenu();
GetMainWindow()->SetCaption("Drawing Pad");
}
Where to find more information
Here's a guide to where you can find more information on the topics
introduced in this step:
- The InitMainWindow and InitInstance
functions are discussed in "Application and module objects" in the ObjectWindows
Programmer's Guide.
- Menu and menu descriptor objects
are described in "Menu objects" in the ObjectWindows Programmer's Guide.
- The Doc/View classes are discussed
in "Doc/View objects" in the ObjectWindows Programmer's Guide.
- The drag and drop functions are
discussed in the ObjectWindows Reference Guide.
Prev
Up
Next
|