Home
Search
 
What's New
Index
Books
Links
Q & A
Newsletter
Banners
 
Feedback
Tip Jar
 
C# Helper...
 
XML RSS Feed
Follow VBHelper on Twitter
 
 
 
MSDN Visual Basic Community
 
 
 
 
TitleDemonstrate many useful techniques including backward and forward compatibility
Description
Keywordscompatibility, forward compatibility, backward compatibility, drawing, file, serialization, MRU list
CategoriesSoftware Engineering, Graphics, Files and Directories
 
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.

The selected topics are:

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.

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.

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 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.

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.

 
 
Copyright © 1997-2010 Rocky Mountain Computer Consulting, Inc.   All rights reserved.
  Updated