http://www.vb-helper.com/HowTo/compat.zip

	Purpose
Make a simple drawing program that demonstrates a lot of useful techniques

	Method
This program demonstrates a lot of techniques. The following sections give an overview of how these topics work. For more information, see the code. My book Ready-to-Run Visual Basic Code Library also describes some of these topics. For more information, go to: http://www.vb-helper.com/vbcl.htm

The selected topics are:

	File Selection Start Directory
	Keeping Data Safe
	MRU Lists
	Drawing Objects
	Saving and Loading Objects from Files
	Saving Space in Object Files
	Backward Compatibility
	Forward Compatibility

    **********************************
    * File Selection Start Directory *
    **********************************
When the program starts, it uses GetSetting to get the program's InitDir value value from the registry. It sets its CommonDialog control's InitDir property to this value. That makes the control begin file searches in the same directory it used the last time the program ran.

If the registry value is not found, the program uses App.Path as the default.

When you select a file using the CommonDialog control, the control does not automatically update its InitDir property. The program does that like this:

    dlgFile.InitDir = Left$(file_name, Len(file_name) - Len(file_title))

Here file_name and file_title are the file name (including path) and title (without path) selected by the CommonDialog control.

The program also saves the new InitDir value in the registry.

Many programs update this kind of registry value only in the Form_Unload event handler. That is fine unless the program crashes in which case the new value is never saved. This program saves registry values as soon as they change so they cannot be lost. (This is a bit of a pet peeve of mine).

    *********************
    * Keeping Data Safe *
    *********************
The program's m_Dirty flag indicates whether the data is dirty or not. Whenever any data changes, the program calls subroutine SetDirty. It sets m_Dirty and puts an asterisk in the program's caption to let the user know the data is dirty.

	Private Sub SetDirty()
	    ' Do nothing if the data is already dirty.
	    If m_Dirty Then Exit Sub

	    m_Dirty = True

	    ' Put a * in the caption.
	    Caption = "Compat2*[" & m_FileTitle & "]"
	End Sub

The DataSafe function returns True if it is safe to discard the currently loaded data. If m_Dirty is False, the data has not been modified so it is safe to exit. If m_Dirty is True, the function asks the user if it should save the data.

If the user says No, the program can discard the changes so the data is safe to discard. If the user says Cancel, it is not safe to discard the data. If the user says Yes, the program tries to save the data. If it succeeds, m_Dirty is set to False and it is now safe to discard the data.

If the save fails, m_Dirty remains True and it is not safe to discard the unsaved data. This can happen, for example, if the program tries to save the data but has no file name. It asks the user to select a file and the user cancels the file selection dialog.

        ' Return True if it is safe to discard the data.
        Private Function DataSafe() As Boolean
            If Not m_Dirty Then
                ' The data is not dirty therefore safe.
                DataSafe = True
            Else
                ' The data is dirty. See if the user
                ' wants to save it.
                Select Case MsgBox("The data has been modified. Do you want to save it?", _
                        vbYesNoCancel Or vbQuestion)
                    Case vbYes
                        ' The user wants to save the data.
                        mnuFileSave_Click
                        DataSafe = Not m_Dirty
                    Case vbNo
                        ' The user doesn't care about the data.
                        DataSafe = True
                    Case vbCancel
                        ' Cancel the operation.
                        DataSafe = False
                End Select
            End If
        End Function

When the program successfully saves or loads data, and when it starts a new file, the program sets m_Dirty = False and removes the asterisk from the caption.

    *************
    * MRU Lists *
    *************
An MRU (Most Recently Used) list shows the last several files used by a program. You have probably seen these in the File menu of many programs. If you select one of the MRU files, that file is instantly loaded.

This program stores the file names (including path) and the file title (without path) in the registry. For example, the file title for the first file in the list is in the program's MRU_List section with the key name Title1. The program locally holds the list in the collections m_MRUTitles and m_MRUPaths.

The program's File menu contains four entries with the name mnuFileMRU and indexes 1 through 4.

The following routines show how the program loads and displays its MRU list. The program also has routines to add a new file to the list and to remove a file from the list. You can check the code for details.

        ' Load the Most Recently Used file list.
        Private Sub MRULoad()
        Dim i As Integer
        Dim file_title As String
        Dim file_path As String
        
            ' Start with empty MRU list collections.
            Set m_MRUTitles = New Collection
            Set m_MRUPaths = New Collection
        
            ' Get the files from the registry.
            For i = 1 To m_NUM_MRU
                file_title = GetSetting(APP_NAME, _
                    "MRU_List", "Title" & Format$(i), "")
                file_path = GetSetting(APP_NAME, _
                    "MRU_List", "Path" & Format$(i), "")
        
                If Len(file_title) > 0 And Len(file_path) > 0 Then
                    m_MRUTitles.Add file_title
                    m_MRUPaths.Add file_path
                End If
            Next i
        
            ' Display the MRU entries.
            MRUDisplay
        End Sub

        ' Display the MRU entries in the File menu.
        Private Sub MRUDisplay()
        Dim i As Integer
        
            ' Display the menu items.
            For i = 1 To m_MRUTitles.Count
                mnuFileMRU(i).Caption = m_MRUTitles(i)
                mnuFileMRU(i).Visible = True
            Next i
        
            ' Hide any unnecessary menu items.
            For i = m_MRUTitles.Count + 1 To m_NUM_MRU
                mnuFileMRU(i).Visible = False
            Next i
        
            ' Display the separator if we need it.
            mnuFileMRUSep.Visible = (m_MRUTitles.Count > 0)
        End Sub


    *******************
    * Drawing Objects *
    *******************
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.

    *****************************************
    * Saving and Loading Objects from Files *
    *****************************************
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:

	token_name(token_value)

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:

	Ellipse(X1(2655)Y1(390)X2(4350)Y2(2550)DrawWidth(3))

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.

    ********************************
    * Saving Space in Object Files *
    ********************************
To save space in object serializations, the classes only save values when they are different from a default value. For example, in thie 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.

    **************************
    * Backward Compatibility *
    **************************
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.

        ' Initialize the object from its text serialization.
        Public Property Let Serialization(ByVal new_serialization As String)
        Dim token_name As String
        Dim token_value As String
        
            ' Set defaults.
            SetDefaultProperties
        
            ' Set the property values.
            On Error GoTo PropertyError
            Do While Len(new_serialization) > 0
                ' Get the next token.
                GetToken new_serialization, token_name, token_value
        
                Select Case token_name
                    Case "X0"
                        X0 = CSng(token_value)
                    Case "Y0"
                        Y0 = CSng(token_value)
                    Case "Radius"
                        Radius = CSng(token_value)
                    Case "DrawWidth"
                        DrawWidth = CInt(token_value)
                    Case "ForeColor"
                        ForeColor = CLng(token_value)
                    Case "FillColor"
                        FillColor = CLng(token_value)
                    Case "FillStyle"
                        FillStyle = CInt(token_value)
                    Case "DrawStyle"
                        DrawStyle = CInt(token_value)
                    Case Else
                        ErrorRecord "Unknown token '" & _
                            token_name & "' in Circle object"
                End Select
            Loop
            Exit Property
        
        PropertyError:
            ErrorRecord "Error " & Format$(Err.Number) & _
                " processing Circle property '" & _
                token_name & "'" & vbCrLf & Err.Description
            Resume Next
        End Property

Similarly the program can ignore object types it does not understand. Suppose version 1 files may describe a Scribble object but you removed Scribble from version 2. When the version 2 program loads a version 1 object file and finds the object type Scribble, it can ignore that object. Or, as is the case with this example, it can present a message telling the user that it does not understand the object type and will not create it. That's usually the right thing for it to do anyway.

    *************************
    * Forward Compatibility *
    *************************
If you think about it, this technique makes forward compatibility the same as backward compatibility. Suppose you try to load a version 2 file using a version 1 program. The program may find properties or objects that exist in version 2 but not in version 1. It can easily ignore them. It may also use properties that are no longer supported in version 2. If they are not present in the serialization, that's ok--they just take their default values.

At first serializtions and forward/backward compatibility may seem a bit confusing. It's really not all that bad once you get used to it. Step through a few very simple examples and see how it works.

	Disclaimer
This example program is provided "as is" with no warranty of any kind. It is
intended for demonstration purposes only. In particular, it does no error
handling. You can use the example in any form, but please mention
www.vb-helper.com.
