The program uses a different class for each type of object. It stores them in a Collection.
Each class provides a Draw method and an IsAt function that returns True if the object is at a specific point. How IsAt works depends on the type of shape.
When the user clicks to select an object, the program looks through the collection in top-to-bottom order invoking each object's IsAt method to see if it is at the point clicked.
The drawing object classes also provide Serialization Property Get and Property Let procedures. These are text strings that describe the objects and their attributes.
Each consists of a token name followed by a value in parentheses like this:
The program uses the parentheses to separate the token name and value. Note that a token value may contain several nested token name/value pairs. For example, here's a typical serialization for an Ellipse object:
The first token name is Ellipse and its value is "X1(2655)Y1(390)X2(4350)Y2(2550)DrawWidth(3)".
This value contains several tokens that give the ellipse's coordinates and drawing width.
Each object's Serialization Property Get procedure returns a serialization of this form. The Property Let procedures take a serialization and initialize the object using the values it contains.
Once you have serializations, it is easy to save and load objects from a file. To save the objects, the program loops through the objects writing their serializations into a file (or database or whatever).
To load objects, the program reads the file and breaks the serializations apart. It creates objects of the correct types and sends them their serializations so they initialize themselves.
NOTE: VB .NET provides serialization tools that can serialize and deserialize objects automatically in many cases. For more information, see the online help or my book Visual Basic .NET and XML.
To save space in object serializations, the classes only save values when they are different from a default value. For example, in this program the DrawWidth property has a default value of 1. If an object's DrawWidth is 1, no value for DrawWidth is stored in the serialization.
When a class initializes an object from a serialization, it first sets each property to its default value. It then reads the properties in the serialization and sets any new values stored in it. If a value is not in the serialization, it keeps its default value as desired.
In this program, this technique saves a little space. In an application where many properties keep their default values, the space savings can be large.
Visual Basic uses the technique to reduce the amount of space it needs. Open a .frm file in a TextEditor and look at the property values stored for a control. Only the values you changed are recorded. Lots of other properties get their default values when the control is loaded.
This technique makes backward compatibility trivial. Suppose you add a new property to an object type in version 2. Now consider a version 1 object file. That version did not know about the new property, so it cannot contain that property's value. That's not a problem. When you load a version 1 file in a version 2 program, the new property just gets its default value.
Now suppose you removed a property in version 2 so the property may exist in version 1 files but not in version 2 files. If the serialization code ignores any properties it does not understand, that is not a problem either.
The following code shows how the Circle class initializes an object using a serialization. If this code encounters an unknown property name, it makes a record using the ErrorRecord subroutine. After the program finishes loading the data, it reports any unknown properties it found.