Date: 24 April 1997
Author: Christopher Kohlhoff
So far we have allowed users to add shapes. Now wouldn't it be nice if they could save their work? In this step we will look at two sides of the problem. Firstly, although not required by OWL, we will examine Persistent Streams as a means of saving data. Secondly we will see how to implement the user interface to support file handling.
Before we start I had better say that I am assuming that you have already had some experience with regular C++ iostreams. If this is not the case then I suggest you find out about them from your favourite C++ tutorial or book.
You may remember from Step 3 that our data consists of an array of (pointers to) shapes - the data member Shapes of the class TTutorialClientWindow. Therefore our goal is to write out and read in this array. What we will do is implement the following two functions:
void TTutorialClientWindow::Open(const char* filename)
{
// Read in data from the file to fill the array.
}
void TTutorialClientWindow::Save(const char* filename)
{
// Write out data from the array to the file.
}
We have the added complication that not all of the objects in the array are of the same type. That is, instead of all the objects being of type TShape we have a mixture of objects which are TLine, TRectangle and TEllipse instead. One way of dealing with this would be to write out an extra value denoting the type before we write each object. Then when reading the data we read in this value and then use a switch to work out which type of object to add to the array. Well, we could do it that way, but we won't. Persistent Streams take care of all this housekeeping for us and are in fact designed for writing out lists of mixed types (amongst other things).
To add persistent stream capability to our TShape class hierarchy we perform these steps:
class TLine : public TShape
{
// ...
};
class TShape : public TStreamableBase
{
// ...
protected:
DECLARE_ABSTRACT_STREAMABLE(, TShape, 1);
};
IMPLEMENT_ABSTRACT_STREAMABLE(TShape);
void* TShape::Streamer::Read(ipstream&, uint32) const
{
// TShape class contains no data so nothing to read.
return GetObject();
}
void TShape::Streamer::Write(opstream&) const
{
// TShape class contains no data so nothing to write.
}
Now we need to add the stream ability to our derived classes. For brevity we will just look at TLine.
class TLine : public TShape
{
// ...
protected:
DECLARE_STREAMABLE(, TLine, 1);
};
IMPLEMENT_STREAMABLE1(TLine, TShape);
void* TLine::Streamer::Read(ipstream& in, uint32) const
{
// Read in the base object. The cast is important
// to prevent infinite recursion.
ReadBaseObject(static_cast<TShape*>(GetObject()), in);
// Read in the data.
in >> GetObject()->P1 >> GetObject()->P2;
return GetObject();
}
void TLine::Streamer::Write(opstream& out) const
{
// Write out the base object. The cast is important
// to prevent infinite recursion.
WriteBaseObject(static_cast<TShape*>(GetObject()), out);
// Write out the data.
out << GetObject()->P1 << GetObject()->P2;
}
That's it! Now let's look at the implementation of TTutorialClientWindow::Open and TTutorialClientWindow::Save.
void TTutorialClientWindow::Open(const char* filename)
{
// Remove all the current shapes.
//
Shapes.Flush();
// Open the file as a persistent stream.
//
ifpstream in(filename);
if (in.bad())
{
MessageBox("Unable to open file for reading.", filename, MB_ICONERROR);
return;
}
// Read each of the shapes from the file.
// Reading each shape as a pointer lets us
// read in a TShape where the actual object
// pointed to is one of the derived classes.
//
int numShapes;
TShape* shape;
in >> numShapes;
while (numShapes--)
{
in >> shape;
Shapes.Add(shape);
}
// ...
}
void TTutorialClientWindow::Save(const char* filename)
{
// Open the file as a persistent stream.
//
ofpstream out(filename);
if (out.bad())
{
MessageBox("Unable to open file for writing.", filename, MB_ICONERROR);
return;
}
// Write each of the shapes out to the file. By
// writing the shape out using a pointer to it
// (rather than the object itself or a reference)
// the persistent stream allows us to store and
// read objects without needing to know their
// exact class (in this case, one of TLine,
// TRectange or TEllipse).
//
int numShapes = Shapes.GetItemsInContainer();
out << numShapes;
for (int i = 0; i < numShapes; i++)
out << Shapes[i];
// ...
}
That was pretty easy wasn't it. In case your interested, the design idiom used in TTutorialClientWindow::Save, where we read in and create an object of a class derived from TShape without knowing its exact type, is called a "virtual constructor".
Coming soon...
Previous Step | Next Step | Tutorial Contents