Introduction: 
This article describes a generic application framework 
that may be of some use in projects that would need an interface similar to 
Visual Studio.  The application demonstrates approaches to providing a toolbox, 
a workspace, an object treeview, and an object editor.  
The application framework will allow the user to create 
objects in the workspace by dragging items out of the toolbox and onto the work 
surface, the user may drag the objects around within the workspace to reposition 
them, and the user may select objects in the workspace and use the property 
editor to change the object's properties.  Changes made in the property editor 
will immediately be reflected in the appearance of the object and in the 
treeview.  The treeview shows all of the workspace objects as well as child 
objects.  The user may also select objects from the treeview to push them into 
the property editor or to delete them from the project.  The user may save the 
workspace as a custom file type and use the application's file open command to 
restore the custom file back into the workspace for subsequent views and edits.
The application is not of any value in and of itself, 
it is just intended to demonstrate an approach to building a useful application 
that needs to incorporate these types of features.  The application is intended 
to serve only as a demonstration of one approach  to building this sort 
interface; it is not the only way to do it but it is a representative of a 
simple way to build a fairly powerful interface that will be familiar to many 
users through the use of similar products.  
The application is not complete in as much as a lot of 
typical functions are not described and mechanized in the project; for example, 
I have not addressed things like printing or object alignment and distribution.  
It is intended to serve as the basis for a more complex application but it is 
not itself a complete or complex application.  You can alter the approach as I 
have described in any number of different ways and still achieve the same sort 
of resulting interface.  For example, I will demonstrate using buttons as the 
objects in the toolbox but in reality any object that works with drag and drop 
can be used in place of the buttons.
This application and article will attempt to 
demonstrate the following concepts:
- Drag and Drop to support an application toolbox 
interface 
- Panel drag and drop to support moving existing 
objects at run time 
- Treeview control used to provide a map to all 
existing parent and child objects  
- Treeview control used to allow object selection
- Treeview control used to delete project objects 
and child objects through simple recursion 
- The property grid control used to edit object 
properties 
- Serialization used to store files containing all 
of the necessary data needed to reconstruct the file between uses 
(collective object persistence through a custom file type) 
- Deserialization used to recover stored, serialized 
objects to support a file open function and subsequently used to restore a 
stored workspace to the active application 
- Processing command line arguments to open a file 
immediately upon initialization 
- Creation of a custom file type through the setup 
and deployment package
Getting Started:
In order to get started, unzip the attachment and load 
the solution into Visual Studio 2005.  Examine the solution explorer and note 
the files contained in the project:
![DesktopAppFramework1.gif]()
Figure 1:  The Solution Explorer Showing the Project 
Files
Beneath the solution title node, observe that there are 
two added folders, one containing graphics, and one containing classes.  The 
classes folder contains four classes; three are user controls and one is a 
standard class file.  These classes are used for demonstration purposes only and 
form the basis for the objects and object data used within the application.  The 
three user controls are:  Animal.vb, Person.vb, and Place.vb.  The remaining 
class (ObjectNote.vb) is the standard class.
The graphics folder contains a collection of icons and 
images used within the application.
In the solution root, note that there are two files:  
FileSerialize.vb which is a module used to handle serialization and 
deserialization within the application.  The remaining class is the frmMain.vb; 
this class is the main application and it contains most of the code used to 
drive the application.
The main form (frmMain.vb) of the application has a 
layout consisting of a left side panel containing a tabbed panel; the tabbed 
panel has two tabs:  Toolbox and Properties.  The toolbox panel contains three 
user controls represented by buttons; these buttons can be dragged onto the form 
and whenever a control is dragged onto the form a control of the associated type 
is added to the workspace panel controls control collection as well as to a 
sorted list containing references to those objects added to the panel.  The 
properties tab is used to expose a treeview control and a property grid 
control.  The treeview control shows all objects contained within the current 
project; the property grid shows the properties for any object selected from 
either the workspace (panel control) or the treeview control.  The user may edit 
any property exposed but the application is only configured to persist the 
custom properties defined in the class; this could be modified to support 
persisting any or all of the values associated with the object but in an effort 
to keep the code small and readable, I opted only to support a few properties in 
serialization and deserialization.
Take a look at figures 2 and 3 to see an example of the 
demonstration application running; figure 2 shows the application with the 
toolbox tab selected; the objects in the toolbox panel may be dragged and 
dropped onto the panel in the right; in this example you can see several objects 
that have been dropped onto the panel.  Figure 3 shows the property tab selected 
in the application, the treeview at the top shows all of the objects currently 
in the workspace.  The property grid control is set to allow edits to be made to 
the last selected object.  Each time a new object is selected from the treeview 
or the panel, the property grid will update to show the properties for that 
selected object.
![DesktopAppFramework2.gif]()
Figure 2:  Application Running with Toolbox Tab 
Selected
![DesktopAppFramework3.gif]()
Figure 3: Application Running with Properties Tab 
Selected
The Code:  User Controls.
There are three user controls contained in the class 
folder in the solution.  They all are basically constructed the same way so I 
will only describe one of them and you can examine the others if you so desire; 
the user controls are arbitrary to this discussion; the application framework 
can be used to edit the properties of any object; the controls I have included 
are only useful for this demonstration.
Open up the Animal.vb class file.  This is user 
control; user controls can't be easily serialized so there is a little extra 
work involved to persist them and to bring them back to life between uses. 
Each of the user controls starts out with a few 
imports; these imports are used to add design support for the controls when in 
use.  The class starts with the following code:
Imports 
System.ComponentModel
Imports 
System.ComponentModel.EditorAttribute
Imports 
System.ComponentModel.Design.DesignerCollection
 
Public
Class Animal
After the class declaration, a region called 
declarations is defined and populated with private member variables and 
enumerations.  I have included the enumerations to demonstrate how the property 
grid displays options based upon an enumeration (they are exposed in a combo box 
within the property editor); the declaration region content is as follows:
#Region
"Declarations"
 
   
Private mUniqueID As 
Guid
   
Private mDisplayName 
As String
   
Private mSpecies As
String
   
Private mType As 
AnimalType
   
Private mHabitat As 
HabitatType
   
Private mColor As 
System.Drawing.Color
   
Private mLocation As 
Point
 
 
   
Public Enum 
AnimalType
        Mammal
        Amphibeon
        Fish
        Bird
        Reptile
   
End Enum
 
   
Public Enum 
HabitatType
        Water
        Desert
        
Forest
        Arid
        Mountain
        Plain
        Coastal
   
End Enum
 
#End
Region
Following the declarations region, the "New" subroutine 
is overloaded; one version of the subroutine is used to create new instances of 
the control whilst the other is used to recreate the controls following 
deserialization:
' Default 
Constructor
Public
Sub New()
 
    InitializeComponent()
 
   
Dim newGuid As 
Guid = Guid.NewGuid
   
Me.mUniqueID = newGuid
    Habitat = 
HabitatType.Arid
    Type = 
AnimalType.Mammal
    DisplayName =
"myAnimal"
End
Sub
This is the default constructor used to create new 
instances of the control; it sets a unique ID for the control which is used in 
managing relationships within the treeview and it sets a few default properties 
for the control.  Next is the alternate constructor:
' Alternate 
constructor
Public
Sub New(ByVal 
theId As Guid, _
               
ByVal theDisplayName 
As String, _
               
ByVal theSpecies As
String, _
               
ByVal theType As
String, _
               
ByVal theHabitat As
String, _
               
ByVal theColor As 
System.Drawing.Color, _
               
ByVal theLocation As 
Point)
 
    InitializeComponent()
 
   
' when restoring from data after deserialization, set 
the UID to
   
' the stored value and the recovered properties for 
the control
   
' including its location are stored into a new 
instance of the 
   
' user control
    mUniqueID = theId
    DisplayName = 
theDisplayName
    Species = theSpecies
    Type = theType
    Habitat = theHabitat
    MajorColor = theColor
   
Me.Location = theLocation
 
End
Sub
This is the alternate constructor used to restore a 
control following deserialization; the values supplied to the constructor will 
be supplied by a structure populated from the original control's properties at 
serialization.  Additional properties could easily be added however in effort to 
keep things simple I opted to only support a few of the available properties and 
only one from the user control class (location).  If you wanted to add in other 
properties such as a border style or background color, you could capture those 
values at serialization and post them back to the control whenever it is 
reconstructed through this alternate constructor.
The rest of the Animal class contains the properties 
used by the control.   When looking at the properties, examine each of the 
attributes assigned to the properties that may be edited within the property 
editor control.  That region is as follows:
#Region
"Properties"
 
   
Public ReadOnly
Property ID() As 
Guid
       
Get
           
Return mUniqueID
       
End Get
   
End Property 
 
   
Public Property 
MyLocation() As Point
       
Get
           
Return mLocation
       
End Get
       
Set(ByVal 
value As Point)
            mLocation = 
value
       
End Set
   
End Property 
 
    <CategoryAttribute("Animal 
Information"), _
    Browsable(True), 
_
    BindableAttribute(False), 
_
    DescriptionAttribute("The 
display name of this animal object.")>_
   
Public Property 
DisplayName() As 
String
       
Get
           
Return mDisplayName
       
End Get
       
Set(ByVal 
value As String)
            mDisplayName = 
value
            txtTitle.Text =
"ANIMAL: " & mDisplayName
       
End Set
   
End Property 
 
    <CategoryAttribute("Animal 
Information"), _
    Browsable(True), 
_
    BindableAttribute(False), 
_
    DescriptionAttribute("The 
species of this animal object.")>_
   
Public Property 
Species() As String
       
Get
           
Return mSpecies
       
End Get
       
Set(ByVal 
value As String)
            mSpecies = 
value
            lblSpecies.Text 
= "Species: " & mSpecies
       
End Set
   
End Property 
 
    <CategoryAttribute("Animal 
Information"), _
    Browsable(True), 
_
    BindableAttribute(False), 
_
    DescriptionAttribute("The 
type of this animal object.")>_
   
Public Property 
Type() As AnimalType
       
Get
           
Return mType
       
End Get
       
Set(ByVal 
value As AnimalType)
            mType = value
            lblType.Text =
"Type: " & mType.ToString()
       
End Set
   
End Property 
 
    <CategoryAttribute("Animal 
Information"), _
    Browsable(True), 
_
    BindableAttribute(False), 
_
    DescriptionAttribute("The 
habitat used by this animal object.")>_
   
Public Property 
Habitat() As HabitatType
       
Get
           
Return mHabitat
       
End Get
       
Set(ByVal 
value As HabitatType)
            
mHabitat = value
            
lblHabitat.Text = "Habitat: " & 
mHabitat.ToString()
        
End
Set
   
End Property 
 
    <CategoryAttribute("Animal 
Information"), _
    Browsable(True), 
_
    BindableAttribute(False), 
_
    DescriptionAttribute("The 
primary color of this animal object.")>_
   
Public Property 
MajorColor() As System.Drawing.Color
       
Get
           
Return mColor
       
End Get
       
Set(ByVal 
value As System.Drawing.Color)
            mColor = value
            lblColor.Text =
"Color: " & mColor.ToString()
       
End Set
   
End Property
 
#End
Region
As far 
as the attributes go, the Category Attribute is used to group properties within 
the property editor.  If three properties have a common Category Attribute, they 
will appear grouped together during edit.  The Browsable attribute determines 
whether or not the property will appear in the property editor.  The Description 
Attribute provides the text string that appears at the bottom of the editor; 
this is generally used to provide instructions to the user or to describe the 
property.  
![DesktopAppFramework4.gif]()
Figure 
4:  Property Grid in Use
That pretty much wraps up the content of the user 
controls; again all three are basically built the same and therefore I have 
described only one of the three user controls.
The Code:  Serializable Class.
The class folder contains one other class; it is called 
ObjectNote.vb and it is a simple, serializable class.  It is included in the 
demonstration for two reasons.  The first is that everything in a serializable 
class can be serialized (you probably guessed that was coming) and therefore you 
will not need to do anything extra to serialize, deserialize, or to reconstruct 
objects between uses.  The other reason I have included it in the demonstration 
is because I use it to show adding and removing child nodes from the treeview 
control.
The class is very simple and it only contains three 
properties worthy of note:  Its unique ID, its parent's unique ID, and string 
used to hold the note itself.  At runtime, if the user right clicks on an object 
in the treeview or in the panel, the application will reveal a context menu and 
one of the two menu options is to add a note to the object.  This is the 
container class that holds that note.
The code starts with a few imports and a class 
declaration.  Note that the class is marked serializable:
Imports 
System.ComponentModel
Imports 
System.ComponentModel.EditorAttribute
Imports 
System.ComponentModel.Design.DesignerCollection
Imports 
System.Runtime.Serialization 
 
<Serializable()>_
Public
Class ObjectNote
Following the imports and class declaration, 
a default constructor is defined as follows:
   
' Default Constructor
   
Public Sub
New(ByVal 
parent As Guid)
 
       
' In order to keep track of who this note belongs to 
when displayed
       
' in the treeview control, we need to know the 
identity of the parent       
          
object (node)
       
' Here we give the new object a new guid and set its 
parent property 
          to 
the value passed to the constructor.
       
Dim newGuid As 
Guid = Guid.NewGuid
       
Me.mUniqueID = newGuid
       
Me.mParentID = parent 
   
End Sub
As per the comments in the code, the constructor is 
passed in its parents unique ID (a guid) and its unique ID and parent ID 
properties are set.  The parent ID is used to do two things:  Maintain the 
relationship between objects stored in the treeview, and to allow child objects 
to be found and deleted if the primary node is deleted by the user.
Following the constructor, a few declarations are made 
within a region;  that section is as follows:
#Region
"Declarations"
 
   
Private mUniqueID As 
Guid
   
Private mParentID As 
Guid
   
Private mDisplayName 
As String
   
Private mNote As
String 
#End
Region
These private member variables are set either when the 
control is created or by the properties which are added next:
#Region
"Properties"
 
   
Public ReadOnly
Property ID() As 
Guid
       
Get
           
Return mUniqueID
       
End Get
   
End Property 
 
   
Public Property 
MyParent() As Guid
       
Get
           
Return mParentID
       
End Get
       
Set(ByVal 
value As Guid)
            mParentID = 
value
       
End Set
   
End Property 
 
    <CategoryAttribute("Object 
Note"), _
    Browsable(True), 
_
    BindableAttribute(False), 
_
    DescriptionAttribute("Enter 
text to describe the associated object.")>_
   
Public Property 
Note() As String
       
Get
           
Return mNote
       
End Get
       
Set(ByVal 
value As String)
            mNote = value
       
End Set
   
End Property 
 
    <CategoryAttribute("Object 
Note"), _
    Browsable(True), 
_
    BindableAttribute(False), 
_
    DescriptionAttribute("The 
display name of this note object.")>_
   
Public Property 
DisplayName() As 
String
        Get
           
Return mDisplayName
       
End Get
       
Set(ByVal 
value As String)
            mDisplayName = 
value
       
End Set
   
End Property 
#End
Region
Of the properties shown, the unique and parent IDs have 
already been mentioned.  The display name is the name of the note as it is 
displayed at run time and the Note property is the note assigned to the parent 
node.
That wraps up the controls used by the application.  
Again, these classes were only defined to allow the demonstration of the 
approach defined within this demonstration; aside from the demonstration they 
don't serve any useful purpose (well, unless you have some unusual requirements 
and just so happen to need controls to monitor people, animals, and places).
The Code:  Main Form.
Main Form Layout.
The main form contains a menu at the top of the form, a 
tabbed control is docked to the left and has two panels, one is used as a 
toolbox and the other to show the treeview and properties.  The tabbed panel has 
a vertical splitter on its right side; within the property tab, that panel is 
split horizontally with the treeview docked to the top, the horizontal splitter 
under the treeview, and the property grid control set to dock full under the 
horizontal splitter.  On the right side of the splitter, a toolbar is docked to 
the top and a panel is set to dock full beneath the toolbar.  The panel is set 
to have a background color of white and it is used as the container for the 
objects.
Given the panel control is going to be the target for 
objects dragged from the toolbar, the Allow Drop property of the panel has to be 
set to true.
Each of the three user controls is represented by a 
button placed into the toolbox.  Each button has an image and a text label 
describing it.
The toolbar has two buttons added to it; one is called 
select and one is called drag or move.  If the user is in select mode, the 
clicking on an item in the panel will select it and make the control active for 
edit in the property grid; if the user is in drag mode, the mouse may be used to 
drag the objects around on the panel to reposition them.
In addition to the visual elements, the form also 
contains a file open dialog, a save file dialog, a context menu containing two 
options:  Delete Selected Element and Add Note To Selected Element.  There is 
also a tooltip and the main menu strip.
The main menu has a file menu with options to create a 
new file, open an existing file, save a file, and to exit the application.
The Main Form Code.
The main form's code is divided into several regions; 
these regions are as follows:  Declarations, Drag and Drop - Mouse Down 
Handlers, Drag and Drop - Drop Handlers, and Methods.
The declarations region contains a few member variables 
used within the application, that section is as follows:
#Region
"Declarations"
 
   
' Private member variable declarations
   
Private mCurrentObjectName
As String
   
Private mCurrentObject
As Object
   
Private mObjectCollection
As New 
SortedList
   
Private mObjectNotes 
As New SortedList
   
Private mObjectFileBundle
As New 
SortedList
   
Private mSelect As
Boolean = False 
 
   
' Data Containers for Serialization --
   
' This example uses three user controls for the basis 
of the
   
' objects needed to demonstrate the concept; these 
are not
   
' serializable and in order to persist them, you need 
to capture
   
' the user control's properties into a serializable 
construct and
   
' store that instead of the control itself.  The 
following
   
' three structs are used to contain the user control 
data.
   
' if you wish to persist other data from the control 
such as the 
   
' background color, font, etc., you would need to add 
additional
   
' items to these data container structs and populate 
and recover 
   
' those values at runtime during the save and open 
file operations
    <Serializable()> _
   
Private Structure 
AnimalData
       
Public theId As 
Guid
       
Public theDisplayName 
As String
       
Public theSpecies As
String
       
Public theType As
String
       
Public theHabitat As
String
       
Public theColor As 
System.Drawing.Color
       
Public theLocation As 
Point
   
End Structure 
 
    <Serializable()> _
   
Private Structure 
PersonData
       
Public theId As 
Guid
       
Public theDisplayName 
As String
       
Public thePersonName 
As String
       
Public theBirthPlace 
As String
       
Public theDateOfBirth 
As Date
       
Public theOccupation 
As String
       
Public theLocation As 
Point
   
End Structure 
 
    <Serializable()> _
   
Private Structure 
PlaceData
       
Public theId As 
Guid
       
Public theDisplayName 
As String
       
Public thePlaceName 
As String
       
Public theCity As
String
       
Public theState As
String
       
Public theCountry As
String
       
Public theLocation As 
Point
   
End Structure 
#End
Region
The private member variables are used to keep track of 
the currently selected object and the name of that object, there are two other 
collections (sorted lists) used to contain all of the user controls and all of 
the notes, an additional sorted list is used to pack up all of the object data 
and notes for serialization into a single file with a custom extension.
After the member variables have been declared, three 
serializable structures are defined: one for each of the three user controls.  
Since we are not going to try to serialize everything in the user control, we 
are just adding in a few key properties.  As was mentioned earlier, it would be 
easy to add additional properties from the user control and to serialize and 
deserialize those properties between uses.
In the Drag and Drop - Mouse Down region, each of the 
three buttons is set up to support the drag and drop operation.  A handler was 
written for each button but since they are all basically the same, I will only 
show one here:
Private
Sub Button1_MouseDown(ByVal 
sender As Object,
ByVal e As
    
System.Windows.Forms.MouseEventArgs) Handles 
Button1.MouseDown
    
' When the user clicks down on the button, we assume 
we are going to 
      drag 
one to the panel so we instance an animal, set its default 
      
properties and make the DoDragDrop call.  The control is not yet 
      added 
to the panel's control collection
     
' and is not yet included in any data that will be 
serialized; the 
      user 
has to complete the drop onto the panel for this transaction 
      to 
complete.
     
Dim myAnimal As
New Animal
      mCurrentObjectName =
"Animal"
      mCurrentObject = 
myAnimal
      myAnimal.DisplayName 
= "myAnimal"
      
Button1.DoDragDrop(myAnimal, DragDropEffects.All)
 
End
Sub
The mouse down event is used to instance the associated 
class, set a couple of properties on the object, and evoke the button's Do Drag 
Drop method whilst passing it the new object and setting the Drag Drop Effect to 
all.  That sets up the drag side of the house, now we need to handle the drop 
side.  Looking at the drop handlers region, you will see two subroutines defined 
to prepare the panel control to receive the dropped items:  Panel1_DragDrop, and 
Panel1_DragEnter; they are written as follows:
Private
Sub Panel1_DragDrop1(ByVal 
sender As Object,
ByVal e As 
System.Windows.Forms.DragEventArgs) Handles 
Panel1.DragDrop
 
       
' Get the current mouse position and define a point, 
then
       
' convert the point to work on the panel control 
(within its 
          
coordinates)
       
Dim dropX As
Single = e.X
       
Dim dropY As
Single = e.Y
       
Dim dropLocation = 
New Point(dropX, dropY)
       
Dim dropPoint As
New Point()
        dropPoint = 
Panel1.PointToClient(dropLocation)
 
       
' Create new objects based upon what has been dropped 
onto the panel
       
' and set the user control's handlers to work with 
the dropped 
          
object.
       
Select Case 
mCurrentObjectName.ToString()
 
           
Case "Animal"
               
Dim myAnimal As
New Animal
                myAnimal =
CType(Me.mCurrentObject, 
Animal)
                
Panel1.Controls.Add(myAnimal)
                
myAnimal.Location = dropPoint
                
myAnimal.ContextMenuStrip = ContextMenuStrip1
                
mObjectCollection.Add(myAnimal.ID, myAnimal)
               
AddHandler myAnimal.MouseClick,
AddressOf Object_MouseClick
               
AddHandler myAnimal.MouseDown,
AddressOf Object_MouseDown
           
Case "Person"
               
Dim myPerson As
New Person
                myPerson =
CType(Me.mCurrentObject, 
Person)
                
Panel1.Controls.Add(myPerson)
                
myPerson.Location = dropPoint
                
myPerson.ContextMenuStrip = ContextMenuStrip1
                
mObjectCollection.Add(myPerson.ID, myPerson)
               
AddHandler myPerson.MouseClick,
AddressOf Object_MouseClick
               
AddHandler myPerson.MouseDown,
AddressOf Object_MouseDown
           
Case "Place"
               
Dim myPlace As 
New Place
                myPlace =
CType(Me.mCurrentObject, 
Place)
                
Panel1.Controls.Add(myPlace)
                
myPlace.Location = dropPoint
                
myPlace.ContextMenuStrip = ContextMenuStrip1
                
mObjectCollection.Add(myPlace.ID, myPlace)
                AddHandler 
myPlace.MouseClick, AddressOf Object_MouseClick
               
AddHandler myPlace.MouseDown,
AddressOf Object_MouseDown
           
Case Else
                
mCurrentObject.Location = dropPoint
       
End Select
 
       
' With a new item dropped into the panel, update the 
treeview to
       
' reflect the latest addition to the node collection
        UpdateTreeview() 
   
End Sub
The first part of this handler gets the current mouse 
position at the time of the drop and converts it into a value usable by the 
panel control.  This will make sure that the dropped item's upper left hand 
corner will be located at the exact position indicated by the mouse when the 
drop was made.  Following the location conversion, the subroutine evaluates the 
current object's name (set when the button received its mouse down command) 
within a select case statement; depending upon the current object, the 
subroutine then creates an instance of the appropriate object type sets it to be 
the current object, adds the control to the panel, sets its location to be at 
the drop point, and then adds a context menu and mouse click and mouse down 
event handlers to the control .
Once the object has been placed and added to the panel, 
the treeview control is updated to show the newly added object.  The Drag Enter 
event handler is set up to handle whether or not the drag drop will be used to 
move a control or add a new one.
Private
Sub Panel1_DragEnter1(ByVal 
sender As Object,
ByVal e As
    
System.Windows.Forms.DragEventArgs) Handles 
Panel1.DragEnter
 
   
' Setup to either copy or move the dragged control.
   
If (e.Data.GetDataPresent(DataFormats.Text))
Then
        e.Effect = 
DragDropEffects.Copy
   
Else
        e.Effect = 
DragDropEffects.Move
   
End If
 
End
Sub
The last section of code to describe is contained in 
the Methods region; within this region, fifteen separate subroutines are 
defined.  Some of these subroutines are very simple and are barely worth 
mentioning, others will require some explanation.
The first two subroutines in the methods region are two 
generic handlers:  a mouse down handler, and an mouse click handler.
Private
Sub Object_MouseClick(ByVal 
sender As Object,
ByVal e As
    
System.Windows.Forms.MouseEventArgs)
 
   
' A generic mouse click handler used for dynamically 
added user 
     control 
objects.  This sets the current object, current object 
     name, 
and object associated with the property grid in response to a 
     click
   
Try
       
Me.mCurrentObject = sender
       
Me.mCurrentObjectName = 
sender.DisplayName.ToString()
       
Me.PropertyGrid1.SelectedObject = sender
 
   
Catch ex As 
Exception
 
   
End Try
 
End
Sub
Private
Sub Object_MouseDown(ByVal 
sender As Object,
ByVal e As
    
System.Windows.Forms.MouseEventArgs)
 
   
' A generic mouse down handler used for dynamically 
added user 
     control 
objects.
   
' This works such that if the user to swap between 
selecting a 
     control 
to load
   
' it into the property grid control, or to allow the 
user to drag a 
    control 
to a new location on the form panel.
   
Try
       
If mSelect = True
Then
            mCurrentObject 
= sender
            
mCurrentObjectName = sender.DisplayName.ToString()
            
mCurrentObject.DoDragDrop(mCurrentObject, 
            
DragDropEffects.All)
           
Me.PropertyGrid1.SelectedObject = sender
        End
If
 
   
Catch ex As 
Exception
 
    End
Try
 
End
Sub
The comments provided within each of these two 
subroutines defines the purpose of the handler.  In general, these handlers are 
added to new instances of any controls added to the panel control at run time.
The next two subroutines are used to set a Boolean 
value used to allow the application to determine whether or not it is in select 
or drag mode.  This code is the click event handler for the two toolbar 
controls.  That code is as follows:
Private
Sub tbnSelect_Click_1(ByVal 
sender As System.Object,
ByVal e As
    System.EventArgs)
Handles tbnSelect.Click
 
   
' Select mode is used allow the user to click on an 
object without 
    dragging
    mSelect =
False
 
End
Sub
 
Private
Sub tbnDrag_Click_1(ByVal 
sender As System.Object,
ByVal e As
    System.EventArgs)
Handles tbnDrag.Click
 
   
' With select mode disabled the user can drag objects 
around to move 
    them
    mSelect =
True
 
End
Sub
Moving right along, the next thing up is the call that 
updates the treeview.  This call is made whenever a property grid item is edited 
or an item is added or deleted from the current workspace.  That code looks like 
this:
Private
Sub UpdateTreeview()
 
   
' Clears and reloads all of the nodes in the treeview
   
' based upon object data stored in the objects sorted 
list
   
' This first loads the objects, and then checks for 
and loads
   
' any notes associated with the current object.
    TreeView1.Nodes.Clear()
 
   
Dim de As 
DictionaryEntry
   
For Each de
In mObjectCollection
       
Dim nd As
New TreeNode
        nd.Name = 
de.Value.displayname.ToString()
        nd.Text = 
de.Value.displayname.ToString()
        nd.Tag = 
de.Value.ID.ToString()
        Me.TreeView1.Nodes.Add(nd)
       
' add any notes
       
Dim de2 As 
DictionaryEntry
       
For Each de2
In mObjectNotes
           
If de2.Value.MyParent.ToString() = 
nd.Tag.ToString() Then
               
Dim newNoteNode As
New TreeNode
                
newNoteNode.Text = "Note"
                
newNoteNode.Tag = de2.Value.ID.ToString()
                
nd.Nodes.Add(newNoteNode)
           
End If
       
Next
   
Next
 
   
' The treeview will normally collapse all nodes in 
response to a 
    change, 
this call forces all nodes to open.
    TreeView1.ExpandAll()
 
End
Sub
This code is pretty simple; it clears all of the items 
out of the current treeview and then loops through the object collection to 
restore the treeview.  As items are added and removed from the object 
collection, the treeview will reflect only the current status of the workspace.
Equally simple is the handler for the property grid's 
property event changed handler which just calls for the treeview to update:
Private
Sub PropertyGrid1_PropertyValueChanged(ByVal 
s As Object,
ByVal e 
   
As 
System.Windows.Forms.PropertyValueChangedEventArgs) 
Handles 
    
PropertyGrid1.PropertyValueChanged
 
   
' Whenever the user updates a property in the 
property editor, the 
    object 
will update
   
' automatically but the treeview will not; this call 
will update the 
    treeview 
in response
   
' to dynamic edits to the property grid control.
    UpdateTreeview()
 
End
Sub
Whenever the user selects an item from the treeview 
control, the property grid control needs to be updated to display the selected 
item's associated object for edit; this code accomplishes that:
Private
Sub TreeView1_AfterSelect(ByVal 
sender As Object,
ByVal e As  
    System.Windows.Forms.
    TreeViewEventArgs)
Handles TreeView1.AfterSelect
 
   
' Whenever the user clicks on a treeview node, this 
subroutine will
   
' select the object associated with the treeview node 
into the 
    property
   
' grid and make it available for edits.
 
   
' The first check is for user control objects
   
Dim de As 
DictionaryEntry
   
For Each de
In mObjectCollection
       
If de.Key.ToString = e.Node.Tag.ToString()
Then
           
Me.mCurrentObject = de.Value
           
Me.mCurrentObjectName = 
de.Value.DisplayName.ToString()
            
PropertyGrid1.SelectedObject = de.Value
       
End If
 
   
Next
 
   
' The second check is for notes
   
Dim de2 As 
DictionaryEntry
   
For Each de2
In mObjectNotes
       
If de2.Key.ToString = e.Node.Tag.ToString()
Then
            Me.mCurrentObject 
= de2.Value
            Me.mCurrentObjectName 
= de2.Value.DisplayName.ToString()
       
     PropertyGrid1.SelectedObject = de2.Value
       
End If
 
   
Next
 
End
Sub
As you can see, after the user selects an item from the 
treeview control, the application will loop through the collection of objects 
and the collection of notes looking for a match.  When the match is made, the 
control is updated to show the selected object's properties.
The call to delete a selected item works in a similar 
manner:
Private
Sub DelectCurrentObjectToolStripMenuItem_Click(ByVal 
sender As System.Object,
ByVal e As 
System.EventArgs) Handles 
DelectCurrentObjectToolStripMenuItem.Click
 
       
' This function will delete the selected object and 
treeview node
       
' in response to a user request to delete the 
object.  The option to 
          
delete an object is presented in the context menu.
       
Dim de As 
DictionaryEntry
       
For Each de
In mObjectCollection
           
If de.Value.ID.ToString = 
mCurrentObject.ID.ToString() Then
                
mObjectCollection.Remove(de.Key)
                
DeleteOrphanNotes(de.Key)
               
Exit For
           
End If
       
Next
 
        UpdateTreeview()
       
Me.Panel1.Controls.Remove(mCurrentObject)
 
End
Sub
In this code, when the user selects the Delete Current 
Object from the context menu (after right clicking on either the treeview or one 
of the panel control's objects), the object collection is looped through 
searching for a match to the selected (current) object.  When a match is found, 
two things happen, first the collection is updated to remove the current object, 
and second, the unique ID for the object just deleted is passed to function 
called DeleteOrphanNotes.  Delete orphan notes is used to track down any and all 
notes assigned to the deleted object and remove them from the object note 
collection.  After the change is made, the treeview control is updated and the 
panel's control collection is updated to remove the current object from that 
collection as well.
The next bit of code to examine is used to add a new 
note the current selected object.  When this code is executed, a new note object 
will be created and its parent ID will be set to the current object's unique 
ID.  After the note is added, the treeview is updated to show the new child node 
appended to its parent node.  That code is as follows:
Private
Sub AddNoteToolStripMenuItem_Click(ByVal 
sender As System.Object,
ByVal e As 
System.EventArgs) Handles 
AddNoteToolStripMenuItem.Click
 
       
' The option to add a note to a selected object 
(either from the 
          
treeview or panel)
       
' is supported here; the control option appears in 
the context menu.
       
Dim newNote As
New ObjectNote(mCurrentObject.ID)
        newNote.DisplayName 
= "Note"
       
Me.mObjectNotes.Add(newNote.ID, newNote)
        UpdateTreeview()
End
Sub
Next up is the Delete Orphan Notes subroutine, this 
function will continue to search for child notes until no more are found.  In 
response to finding a node, the function will again call itself to look for 
notes with a parent ID that matches the argument passed to the function.  The 
process will continue to operate recursively until the search term is not 
found.  That code looks like this:
Public
Sub DeleteOrphanNotes(ByVal 
parentID As Guid)
 
   
' Recursively deletes all child note objects 
associated with
   
' a parent that has been deleted.  The code continues 
to look
   
' for notes with a matching parent ID until no more 
are found; at 
   
' that point the search will cease.
   
Dim blnFound As
Boolean = False
 
   
Dim de As 
DictionaryEntry
   
For Each de
In Me.mObjectNotes
        If 
de.Value.MyParent = parentID Then
    
        mObjectNotes.Remove(de.Key)
            blnFound =
True
            Exit
For
        End
If
   
Next
 
   
If blnFound = False
Then
        Exit
Sub
   
Else
    
    DeleteOrphanNotes(parentID)
   
End If
 
End
Sub
Next up is a main menu item click event handler.  This 
code is used to handle a request for a new project and all it does is call 
another subroutine called Clear All.  That code and the code for Clear All are 
as follows:
Private
Sub NewToolStripMenuItem_Click(ByVal 
sender As System.Object, 
   
ByVal e As 
System.EventArgs) Handles 
NewToolStripMenuItem.Click
 
   
' Reset all collections and controls to a default
   
' empty state
    ClearAll()
 
End
Sub
 
Private
Sub ClearAll()
 
   
' This just clears and empties everything out of the 
project
    mCurrentObjectName =
""
    mCurrentObject =
New Object()
   
 mObjectCollection.Clear()
    mObjectNotes.Clear()
    
mObjectFileBundle.Clear()
    mSelect =
False
    Panel1.Controls.Clear()
    TreeView1.Nodes.Clear()
    
PropertyGrid1.SelectedObject = Nothing
 
End
Sub
If you take a look at the Clear All subroutine, you 
will note that it empties all of the object collections, object selections, and 
the panel control's control collection. It also empties the treeview control and 
sets the property grid control's selected object to nothing.  This code was 
placed in a separate subroutine since it is called both in response the "New" 
menu option select as well as when  file is opened from the file system.
Moving on the next menu option, "Open", this bit of 
code is used to open a file from the file system and load its content into the 
application.  The Open menu item select's code looks like this:
Private
Sub OpenToolStripMenuItem_Click(ByVal 
sender As System.Object, 
   
ByVal e As 
System.EventArgs) Handles 
OpenToolStripMenuItem.Click
 
   
' Configure the appearance and file association used 
by the 
   
' openfiledialog box; limit the files that may be 
opened to the
   
' custom file type extension.  BXS is arbitrary, you 
can make up
   
' any file extension (well, don't use something like 
.doc, .txt, or 
      .bmp)
    OpenFileDialog1.Title =
"Open BXS Document"
    OpenFileDialog1.Filter 
= "BXS Documents (*.bxs)|*.bxs"
 
   
' if the user cancels, close out the dialog and 
return to the form
   
If OpenFileDialog1.ShowDialog = 
Windows.Forms.DialogResult.Cancel 
   
Then
       
Exit Sub
   
End If
 
   
' Exit if no BXS document is selected
   
Dim sFilePath As
String
    sFilePath = 
OpenFileDialog1.FileName
   
If sFilePath = ""
Then Exit
Sub
 
   
' Verify that the requested file is in place and
   
' exit the subroutine if the file does not exist.
   
If System.IO.File.Exists(sFilePath) =
False Then
       
Exit Sub
   
End If
 
   
' Since we have a valid source file, clear all is 
called
   
' to empty the applications sorted lists and to clear 
the
   
' panel of controls
    ClearAll()
 
    mObjectFileBundle =
New SortedList
    mObjectFileBundle = 
FileSerializer.Deserialize(sFilePath)
 
   
Dim ObjectData As
New SortedList
 
   
' Recover the object collections to populate the 
local
   
' sortedlists used to hold the objects.
   
Dim de As 
DictionaryEntry
   
For Each de
In mObjectFileBundle
       
If de.Key.ToString() =
"TheObjects" Then
            ObjectData = 
de.Value
       
ElseIf de.Key.ToString() =
"TheNotes" Then
            
mObjectNotes.Clear()
            mObjectNotes = 
de.Value
       
End If
 
   
Next
 
   
' Add the objects back into the panel by recovering 
the stored
   
' object data and creating a new set of controls with 
the properties
   
' set to the object data gathered from the original 
object during
   
' file save and serialization
   
Dim de2 As 
DictionaryEntry
   
For Each de2
In ObjectData
        Select
Case de2.Value.GetType.ToString()
            Case
"ObjectManager.frmMain+AnimalData"
                Dim 
ast As New 
AnimalData
                ast = 
de2.Value
                Dim 
animl As New 
Animal(ast.theId, _
    
                                    ast.theDisplayName, _
    
                                    ast.theSpecies, _
    
                                    ast.theType, _
    
                                    ast.theHabitat, _
    
                                    ast.theColor, _
    
                                    ast.theLocation)
    
            animl.ContextMenuStrip = ContextMenuStrip1
                
mObjectCollection.Add(animl.ID, animl)
               
AddHandler animl.MouseClick,
AddressOf Object_MouseClick
               
AddHandler animl.MouseDown,
AddressOf Object_MouseDown
                
Panel1.Controls.Add(animl)
           
Case 
"ObjectManager.frmMain+PersonData"
               
Dim pst As
New PersonData
                pst = 
de2.Value
               
Dim pers As
New Person(pst.theId, _
                                       pst.theDisplayName, _
                         
              pst.thePersonName, _
                                       pst.theBirthPlace, _
                                       pst.theDateOfBirth, _
                                       pst.theOccupation, _
                        
               pst.theLocation)
                
pers.ContextMenuStrip = ContextMenuStrip1
                
mObjectCollection.Add(pers.ID, pers)
               
AddHandler pers.MouseClick,
AddressOf Object_MouseClick
               
AddHandler pers.MouseDown,
AddressOf Object_MouseDown
                
Panel1.Controls.Add(pers)
           
Case 
"ObjectManager.frmMain+PlaceData"
               
Dim pls As
New PlaceData
                pls = 
de2.Value
               
Dim plc As 
New Place(pls.theId, 
_
                                     pls.theDisplayName, _
                                     pls.thePlaceName, _
                                     pls.theCity, _
                                     pls.theState, _
                                     pls.theCountry, _
                                     pls.theLocation)
                
plc.ContextMenuStrip = ContextMenuStrip1
                
mObjectCollection.Add(plc.ID, plc)
                AddHandler 
plc.MouseClick, AddressOf Object_MouseClick
               
AddHandler plc.MouseDown,
AddressOf Object_MouseDown
                
Panel1.Controls.Add(plc)
           
Case Else
               
' do nothing
       
End Select
   
Next
 
   
' update the treeview to show the objects loaded from 
a file
    UpdateTreeview()
 
End
Sub
This code is annotated well enough to follow but in 
general, the option is used to open an Open File Dialog; the dialog is 
configured to show files that match the custom file extension we are using 
(.bxs).  When the user selects a valid file, the path the that file is captured 
as a string and passed to the deserialization handler code; this code recovers 
the local file bundle object  from the serialized bundle (the bundle is a sorted 
list that contains two other sorted lists, one for the control object data, and 
one for the note objects.  
The bundled sorted list is examined and the serialized 
object sorted list and the object note sorted list are used to populate the 
local object list and object note list.  The objects contained in the control 
object sorted list are evaluated and new controls are instanced, their 
properties are set to match the stored property values, and the appropriate 
event handlers are associated with the new control.  Once all of the new 
controls have been defined and added to the panel control, the treeview is 
updated.  Note that the object notes sorted list did not require any processing 
other than to write it over directly to the current object notes sorted list.  
This is because the entire class and all of its properties was directly 
serializable.
The next bit of code accomplishes the save function 
called form the main menu:
Private
Sub SaveToolStripMenuItem_Click(ByVal 
sender As System.Object, 
   
ByVal e As 
System.EventArgs) Handles 
SaveToolStripMenuItem.Click
 
   
Try
       
' Open a file dialog for saving BXS documents
        
SaveFileDialog1.Title = "Save BXS Document"
        
SaveFileDialog1.Filter = "BXS Documents 
(*.bxs)|*.bxs"
 
       
If SaveFileDialog1.ShowDialog() = 
        
Windows.Forms.DialogResult.Cancel Then
           
Exit Sub
       
End If
 
   
Catch ex As 
Exception
 
       
Exit Sub
 
   
End Try
 
   
' Exit if no BXS document is selected
   
Dim sFilePath As
String
    sFilePath = 
SaveFileDialog1.FileName
   
If sFilePath = ""
Then Exit
Sub
 
   
' Create a new sorted list to hold the data contain 
in the
   
' user controls for subsequent serialization
   
Dim ObjectData As
New SortedList
 
   
' Move the notes and object collections into the file 
bundle
   
' for subsequetn serialization
   
Dim de As 
DictionaryEntry
   
For Each de
In mObjectCollection
       
Select Case 
de.Value.GetType.ToString()
           
Case 
"ObjectManager.Animal"
               
Dim ad As
New AnimalData
                ad.theId = 
de.Key
                ad.theColor 
= de.Value.MajorColor
                
ad.theDisplayName = de.Value.DisplayName
                
ad.theHabitat = de.Value.Habitat
                
ad.theSpecies = de.Value.Species
                
ad.theLocation = de.Value.Location
                
ObjectData.Add(ad.theId, ad)
            Case
"ObjectManager.Person"
               
Dim ps As
New PersonData
                ps.theId = 
de.Key
                
ps.theBirthPlace = de.Value.BirthPlace
                
ps.theDateOfBirth = de.Value.DateOfBirth
             
   ps.theDisplayName = de.Value.DisplayName
                
ps.theLocation = de.Value.Location
                
ps.theOccupation = de.Value.Occupation
                
ps.thePersonName = de.Value.PersonName
                
ObjectData.Add(ps.theId, ps)
           
Case 
"ObjectManager.Place"
               
Dim pls As
New PlaceData
                pls.theId = 
de.Key
                
pls.theDisplayName = de.Value.DisplayName
                
pls.thePlaceName = de.Value.PlaceName
                pls.theCity 
= de.Value.City
                
pls.theState = de.Value.State
                
pls.theCountry = de.Value.Country
                
pls.theLocation = de.Value.Location
                
ObjectData.Add(pls.theId, pls)
           
Case Else
               
'do nothing
       
End Select
   
Next
 
   
' Place the object data and notes into a sorted list
   
' for subsequent serialization to a file location
    mObjectFileBundle =
New SortedList
    mObjectFileBundle.Add("TheObjects", 
ObjectData)
    mObjectFileBundle.Add("TheNotes", 
mObjectNotes)
 
   
' Once the values have been set, serialize the 
content into the
   
' file path established using the save file dialog
   
 FileSerializer.Serialize(sFilePath, mObjectFileBundle)
 
End
Sub
 
This code works in a manner similar to the Open File menu 
option, although it is operating in reverse.  Here, the user is presented with a 
File Save dialog box; the dialog box is used to collect a valid path to be used 
to store the serialized application data as a custom (.bxs) file.  Once a valid 
path is selected or created, the subroutine collects the control object data 
from the control object collection and moves it into a separate, serializable 
collection.  The structures defined for each control type are used to contain 
individual copies of each control objects properties; not all of the properties 
but those that we have decided to keep.
Once the control data has been gathered into the object data collection, both 
the object note and object data sorted lists are added to the Object File Bundle 
sorted list (so that all data is now stored in a single sorted list).  The 
object file bundle is then serialized to the file path specified.
The next two sections of code are used to handle 
opening the application by double clicking on an associated file of the custom 
file type from an explorer window.  The first subroutine configures form load to 
look for file path strings passed to the application on load, and if any are 
passed in, it passes the path to the second subroutine which loads it into the 
application on start up.  
That code looks like this:
Private
Sub frmMain_Load(ByVal 
sender As Object,
ByVal e As
    System.EventArgs)
Handles Me.Load
 
   
'check each parameter to get the file name (there is 
only one though)
   
For Each param
As String
In My.Application.CommandLineArgs
 
       
Try
           
' pass the file path if it exists
            
OpenFromPath(param)
       
Catch
           
'do nothing, just open the application with no file
       
End Try
 
   
Next param
 
End
Sub
 
Private
Sub OpenFromPath(ByVal 
sFilePath As String)
 
    mObjectFileBundle =
New SortedList
    mObjectFileBundle = 
FileSerializer.Deserialize(sFilePath)
 
   
Dim ObjectData As
New SortedList
 
   
' Recover the object collections to populate the 
local
   
' sortedlists used to hold the objects.
   
Dim de As 
DictionaryEntry
   
For Each de
In mObjectFileBundle
       
If de.Key.ToString() =
"TheObjects" Then
            ObjectData = 
de.Value
       
ElseIf de.Key.ToString() =
"TheNotes" Then
            
mObjectNotes.Clear()
            mObjectNotes = 
de.Value
       
End If
 
   
Next
 
   
' Add the objects back into the panel by recovering 
the stored
   
' object data and creating a new set of controls with 
the properties
   
' set to the object data gathered from the original 
object during
   
' file save and serialization
   
Dim de2 As 
DictionaryEntry
   
For Each de2
In ObjectData
       
Select Case 
de2.Value.GetType.ToString()
           
Case 
"ObjectManager.frmMain+AnimalData"
               
Dim ast As
New AnimalData
                ast = 
de2.Value
               
Dim animl As
New Animal(ast.theId, _
                                        ast.theDisplayName, _
                                        ast.theSpecies, _
                                        ast.theType, _
                                        ast.theHabitat, _
                                        ast.theColor, _
        
                                ast.theLocation)
                
animl.ContextMenuStrip = ContextMenuStrip1
                
mObjectCollection.Add(animl.ID, animl)
               
AddHandler animl.MouseClick,
AddressOf Object_MouseClick
                AddHandler 
animl.MouseDown, AddressOf Object_MouseDown
                
Panel1.Controls.Add(animl)
           
Case 
"ObjectManager.frmMain+PersonData"
               
Dim pst As
New PersonData
                pst = 
de2.Value
               
Dim pers As
New Person(pst.theId, _
                                       pst.theDisplayName, _
                                       pst.thePersonName, _
                                       pst.theBirthPlace, _
       
                                pst.theDateOfBirth, _
                                       pst.theOccupation, _
                                       pst.theLocation)
                
pers.ContextMenuStrip = ContextMenuStrip1
            
    mObjectCollection.Add(pers.ID, pers)
               
AddHandler pers.MouseClick,
AddressOf Object_MouseClick
               
AddHandler pers.MouseDown,
AddressOf Object_MouseDown
                
Panel1.Controls.Add(pers)
           
Case 
"ObjectManager.frmMain+PlaceData"
               
Dim pls As
New PlaceData
                pls = 
de2.Value
               
Dim plc As 
New Place(pls.theId, 
_
                                     pls.theDisplayName, _
                  
                   pls.thePlaceName, _
                                     pls.theCity, _
                                     pls.theState, _
                                     pls.theCountry, _
                                     pls.theLocation)
                
plc.ContextMenuStrip = ContextMenuStrip1
                
mObjectCollection.Add(plc.ID, plc)
               
AddHandler plc.MouseClick,
AddressOf Object_MouseClick
               
AddHandler plc.MouseDown,
AddressOf Object_MouseDown
                
Panel1.Controls.Add(plc)
           
Case Else
               
' do nothing
       
End Select
   
Next
 
   
' update the treeview to show the objects loaded from 
a file
    UpdateTreeview()
 
End
Sub
The code for the Open From File subroutine is identical 
to a portion of the code used in the File Open menu click event handler except 
for the fact that is works with a file path passed into the function directly 
rather than through a value collected from an open file dialog.  This function 
could be called directly from the File Open menu event handler with no impact on 
the application.
The last subroutine in the application is merely used 
to close the application in response to the user's selection of the exit 
function from the main menu:
Private
Sub ExitToolStripMenuItem_Click(ByVal 
sender As System.Object,
ByVal e As 
System.EventArgs) Handles 
ExitToolStripMenuItem.Click
 
        Application.Exit()
 
End
Sub
That concludes the description of the code contained 
within the main form's code and for all of the application code in general.
The Code:  File Serializer Module
The code module contains two methods; one to serialize 
data and one to deserialize data.  Both methods use a binary formatter to handle 
the serialization and deserialization process.  Take a look at the module and 
note how each was construction.
The Serializer subroutine appears as follows:
Sub 
Serialize(ByVal strPath
As String,
ByVal myFile As 
SortedList)
 
       
' Create a filestream to allow saving the file after 
it has 
       
' been serialized in this method
       
Dim fs As
New FileStream(strPath, FileMode.OpenOrCreate)
 
       
' Create a new instance of the binary formatter
       
Dim formatter As
New BinaryFormatter
        'Dim 
formatter As New SoapFormatter
 
       
Try
 
           
' save the serialized data to the file path specified
            
formatter.Serialize(fs, myFile)
            fs.Close()
 
       
Catch ex As 
SerializationException
 
            
MessageBox.Show(ex.Message & ": " & 
ex.StackTrace, "Error", 
            
MessageBoxButtons.OK, MessageBoxIcon.Error)
 
       
End Try 
End
Sub
The subroutine is quite simple; it opens a file stream 
pointed to the file path string passed in the argument list; the file mode is 
set to open or create.  Following the creation of the file stream, a new 
instance of the BinaryFormatter is created.  The try catch block is then set up; 
the try section is used to evoke the formatters serialize method; this method 
accepts two arguments:  One to identify the file stream to write to, and one to 
accept the file which in this case is the sorted list containing references to 
both the application objects and the note objects.  Once the serializer  
completes writing to the file stream, the file stream is closed.
The other method in the File Serializer class is used 
to deserialize the contents of the stored file back into the application's file 
bundle sorted list.  Once reconstructed from a file, the sorted list is used to 
recreate the work space objects.  That code looks like this:
Public
Function Deserialize(ByVal 
strPath As String)
As SortedList
 
   
' Create filestream allowing the user to open an 
existing file
   
Dim fs As
New FileStream(strPath, FileMode.Open)
 
   
' Create a new instance of the Personal Data class
   
Dim myFile As 
SortedList
    myfile =
New SortedList
 
   
Try
 
       
' Create a binary formatter
       
Dim formatter As
New BinaryFormatter
       
'Dim formatter As New SoapFormatter
 
       
' Deserialize the data stored in the specified file 
and 
       
' use that data to populate the new instance of the 
personal data 
          
class.
        myfile = 
formatter.Deserialize(fs)
        fs.Close()
 
       
' Return the deserialized data back to the calling 
application
       
Return myFile
 
   
Catch ex As 
SerializationException
 
        
MessageBox.Show(ex.StackTrace, ex.Message, MessageBoxButtons.OK, 
        
MessageBoxIcon.Error)
       
Return myFile
 
   
End Try
 
End
Function
This code works much like the serializer subroutine in 
that it opens a file stream and returns the contents of the file (the file 
bundle sorted list) which is passed to a new sorted list call "myFile".  The 
deserializer function returns the reconstructed sorted list to the calling 
method.  The calling method in this case is the file open command in the main 
form; the file open command then reconstructs the objects and notes sorted lists 
from the file bundle sorted list.
Custom File Association.
If you want to create a custom file association such 
that when a user double clicks on a file of the custom type's extension in a 
windows explorer window, the application will open it up directly, you will need 
to set that association up in the setup and deployment package, and you will 
need to modify the main entry point of the application to allow it to process a 
file passed to it immediately upon initialization.
The process of creating a file type association through 
the use of the Visual Studio 2005 setup and deployment project is far easier to 
manage than it once was; you can manually code the necessary information into 
the application, and depending upon the installation package you are using, you 
may need to do that.  If you are interested in that approach, take a look at 
this link, 
http://www.vbcity.com/forums/faq.asp?fid=15&cat=Registry#TID72502, 
[see File Associations the hard way].
First off, in the main form of the application, you 
will need to modify the form load event handler to respond to the receipt of a 
command line argument (the file name), this is now a trivial bit of code and it 
should look something like this:
Private
Sub frmMain_Load(ByVal 
sender As System.Object,
ByVal e As
System.EventArgs)
Handles MyBase.Load
 
   
'check each parameter to get the file name (there is 
only one though)
   
For Each param
As String
In My.Application.CommandLineArgs
 
       
Try
           
' pass the file path if it exists
            
OpenFromPath(param)
       
Catch
           
'do nothing, just open the application with no file
       
End Try
 
   
Next param
 
End
Sub
In this example, when the form loads, it will examine 
the contents of My.Application.CommandLineArgs to see if it contains a file 
name; since that is all we are going pass in, it will either be empty or will 
contain a file path.   If a file path is present, it will passed to a subroutine 
called, "OpenFromPath" which captures the data from the file and then 
reconstructs the file's objects.
In order to pass the file path to the command line 
arguments, you need to set up a couple of things in the Setup and Deployment 
project.  To begin, add a setup and deployment project to the existing solution 
and configure it to suit your requirements.  Once the project has been added, 
click on the setup project's name in the solution explorer, click on the "View" 
and then click on the "File Types" option.  This will bring up a File Types 
Designer in the main window of Visual Studio.
Once the file type designer has been displayed, right 
click on "File Types on Target Machine" and the click on the "Add File Type" 
option.  This will add an empty file type to the tree view, select the new file 
type's node and look in the property editor:
![DesktopAppFramework5.gif]()
Figure 5:  File Type Property Editor
In the property editor, set the name to match the name 
of the custom type, set the command to point to the application (as the Primary 
output from the application), key in a description, set the extension to the 
custom file type's extension, and set an icon for the file type.  Having set 
those values, click on the Open node.
![DesktopAppFramework6.gif]()
Figure 6:  The Open node under the custom file type
Once you have clicked on the open node, the property 
editor will show these properties:
![DesktopAppFramework7.gif]()
Figure 7:  Property Editor Set to the Open Node
These default properties are correct in that the 
default process is set to "open" and the "%1" is set to pass the file name of a 
selected file to the application's command line arguments on startup.  After a 
user installs the application, when they double click on a file of the custom 
file type, it will pass the file path to the application and open it.  Also, if 
the user right clicks on the custom file type icon, they will be able to select 
the "open with" option and be presented with a link to your application as 
indicated in the following figure:
![DesktopAppFramework8.gif]()
Figure 8: Open With option in Windows Explorer context 
menu
Summary.
The purpose of this article has been to demonstrate an 
easy approach to providing a complete framework for an application that is 
centered upon creating and editing objects at run time.  In order to achieve 
that goal, the application demonstrates the following key elements:
- Drag and Drop to support an application toolbox 
interface. 
- Panel drag and drop to support moving existing 
objects at run time. 
- Treeview control used to provide a map to all 
existing parent and child objects. 
- Treeview control used to allow object selection.
- Treeview control used to delete project objects 
and child objects through simple recursion. 
- The property grid control used to edit object 
properties. 
- Serialization used to store files containing all 
of the necessary data needed to reconstruct the file between uses 
(collective object persistence through a custom file type). 
- Deserialization used to recover stored, serialized 
objects to support a file open function and subsequently used to restore a 
stored workspace to the active application. 
- Processing command line arguments to open a file 
immediately upon initialization. 
- Creation of a custom file type through the setup 
and deployment package.