Putting It Together: What We ve Learned So Far

Putting It Together: What We've Learned So Far

For our final program in Coding Techniques for Microsoft Visual Basic .NET, let's have a bit of fun while at the same time reinforcing many of the concepts we've learned throughout the previous chapters. We will build a program that creates sticky notes and allows us to add these electronic notes to our screen. Most programmers I know keep notes scattered on scraps of paper around their desks. Well, this program will put the scraps of paper right on the screen.

The program will be operated from a notify icon in the system tray at the bottom of the screen. From here, the user can add a new note, show or hide all of the notes, or quit the program. We will create a serialize class that will manage an internal ArrayList object of sticky note forms. Each sticky note form will in turn have its own menu permitting the user to hide or delete an individual note, or even keep it on top of all other windows.

When the user dismisses the program, we will store the contents of each note in an XML file on disk. When the program is restarted, each note will be displayed with the size and location it had when the user last used the program. An internal data set and data table will be built dynamically to assist in the serialization and deserialization of the XML file for saving and reconstituting each of the notes.

A context menu will be added to the notify icon, and we will add handlers that respond when one of the menu items is clicked. Finally, we will have a "driver" form that builds the context menu and instantiates the serialize class. Remember that a .NET program has to have a main form to keep running. However, we don't want this driver form to be visible, so we will use the new Opacity property of .NET forms. By setting the Opacity property to 0 percent, the form will run but be invisible. So, as you can see, our program has quite a few moving parts. But I know this is one program that you'll use daily and probably want to distribute to your friends. Soon your screen will look like Figure 14-13.

Figure 14-13

The sticky note program in action.

How Do We Save the Notes? XML, Of Course

Our program uses XML to store information so that when the program is run, each form is reconstituted exactly as it was when the user quit. We will use streams and files for our file I/O. Here's the XML file for three notes created when the user quits the program. Notice that we save the title, the message in the note, and the location and size of the form.

<NewDataSet> - <tblSticky> <Title>9/19/2000 11:19:23 PM</Title> <Message>Remember to feed Roscoe.</Message> <Location>{X=408,Y=80}</Location> <Size>{Width=248, Height=93}</Size> </tblSticky> - <tblSticky> <Title>9/19/2000 11:19:53 PM</Title> <Message>Dentist appointment tomorrow at 3:30.</Message> <Location>{X=410,Y=305}</Location> <Size>{Width=248, Height=100}</Size> </tblSticky> - <tblSticky> <Title>9/19/2000 11:20:17 PM</Title> <Message>Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then ser.serialize() If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub</Message> <Location>{X=242,Y=140}</Location> <Size>{Width=248, Height=203}</Size> </tblSticky> </NewDataSet>

As I mentioned, the program is run from a notify icon in the tool tray that has an icon of a note. As you can see in Figure 14-14, when a user right-clicks the icon, a context menu is displayed with options for the user. Our approach keeps the program out of the way but also readily accessible.

Figure 14-14

Right-click the notify icon to see the program's options.

Each individual sticky note also has its own menu. We can select individual notes to stay on top of all other windows by clicking the Keep On Top menu choice. Clearing the menu item allows the window to be arranged in the normal z-order with all others. As you can see in Figure 14-15, a user can also hide or delete individual notes. All in all, this is not only useful but also pretty cool.

Figure 14-15

You can ensure that a sticky note stays on top of other windows.

While on the surface the Sticky Notes program looks simple, it illustrates quite a few important concepts. I mentioned the message loop and the Opacity property of .NET forms. To respond to menu clicks, we will add delegates and wire them to the Click events of the menu items. When a new form is added, we will inherit from a form that represents a new note, named BaseNote. Each BaseNote form will also contain its own menu, and each of the new BaseNote forms will be managed by using a shared ArrayList object to track all active notes. Also, an in-memory data set and data table will be built dynamically to permit using the WriteXML method of the data set for saving the contents of the notes. We will cover two new data types, Point and Size, and character arrays will be covered as well as the TrimStart and TrimEnd methods of a String object. Finally, we'll use streams and files when we read and write the contents of the notes to disk. Before we add any code to our default form, we'll build our serialize class. This class will be instantiated by the default form.

Building the Sticky Notes Program

Start a new Visual Basic .NET Windows application and give it the name Sticky. The default form will actually be our driver for the program. Now follow these steps:

  1. Add a NotifyIcon control to the form and set its Name property to niIcon.

  2. Select Project | Add Class. Click the Class icon and name the class serialize.vb. Click Open to add the new class template to your project.

  3. Add the following code to the serialize class. We will be doing quite a bit in this class, from reading and writing XML to building a data set and data table in memory.

    Imports System Imports System.Collections Imports System.IO Public Class serialize Dim dsDataSet As New DataSet() Dim tblDataTable As DataTable Dim dcDataColumn As DataColumn Dim dcDataRow As DataRow Shared aArraylist As New ArrayList() Dim bSerialized As Boolean = False Sub New() ' Create a new DataTable. tblDataTable = New DataTable("tblSticky") tblDataTable.Clear() ' Declare variables for DataColumn and ' DataRow objects. Dim dcDataColumn As DataColumn dcDataColumn = New DataColumn() With dcDataColumn .DataType = System.Type.GetType("System.String") .ColumnName = "Title" End With tblDataTable.Columns.Add(dcDataColumn) dcDataColumn = New DataColumn() With dcDataColumn .DataType = System.Type.GetType("System.String") .ColumnName = "Message" End With tblDataTable.Columns.Add(dcDataColumn) dcDataColumn = New DataColumn() With dcDataColumn .DataType = System.Type.GetType("System.String") .ColumnName = "Location" End With tblDataTable.Columns.Add(dcDataColumn) dcDataColumn = New DataColumn() With dcDataColumn .DataType = System.Type.GetType("System.String") .ColumnName = "Size" End With tblDataTable.Columns.Add(dcDataColumn) dsDataSet.Tables.Add(tblDataTable) End Sub Sub addNewSticky(ByVal sticky As BaseNote) aArraylist.Add(sticky) End Sub Sub showAll() Dim fSticky As Sticky.BaseNote For Each fSticky In aArraylist fSticky.Show() Next End Sub Shared Sub delete(ByVal sticky As BaseNote) aArraylist.Remove(sticky) End Sub Sub hideAll() Dim fSticky As Sticky.BaseNote For Each fSticky In aArraylist fSticky.Hide() Next End Sub Sub serialize() If bSerialized = True Then Exit Sub Dim fSticky As Sticky.BaseNote '--Remove older data tblDataTable.Clear() For Each fSticky In aArraylist dcDataRow = tblDataTable.NewRow() dcDataRow(0) = fSticky.Text dcDataRow(1) = fSticky.txtNote.Text dcDataRow(2) = fSticky.Location dcDataRow(3) = fSticky.Size tblDataTable.Rows.Add(dcDataRow) Next Dim strStream As Stream = File.Open("c:\sticky.xml", _ FileMode.Create, FileAccess.ReadWrite) dsDataSet.WriteXml(strStream) strStream.Close() bSerialized = True End Sub Sub deserialize() Dim strStream As Stream Try strStream File.Open("c:\sticky.xml", _ FileMode.OpenOrCreate, FileAccess.Read) Dim xmlrXmlReader As New _ System.Xml.XmlTextReader(strStream) dsDataSet.ReadXml(xmlrXmlReader) xmlrXmlReader.Close() Dim dtDataTable As DataTable Dim drDataRow As DataRow Dim dcDataColumn As DataColumn Dim sPoint As String 'Hold {Width=248, ' Height=184} Dim sLocation As String 'Hold "248,184" For Each dtDataTable In dsDataSet.Tables For Each drDataRow In dtDataTable.Rows Dim fSticky As BaseNote = New BaseNote() fSticky.Text = drDataRow(0) fSticky.txtNote.Text = drDataRow(1) sPoint = drDataRow(2) fSticky.Location = formatPoint(sPoint) sPoint = drDataRow(3) fSticky.Size = formatSize(sPoint) fSticky.Show() aArraylist.Add(fSticky) Next Next Catch End Try StrStream.Close() If (aArraylist.Count = 0) Then MessageBox.Show("No notes - please right " & _ "click icon in tray", "Sticky Notes", _ MessageBoxButtons.OK, _ MessageBoxIcon.Information) End If End Sub Function formatPoint(ByVal sPoint As String) As Point Dim sFormattedPoint As String Dim pPoint As New Point() Dim aPoint() As String = sPoint.Split(",") Dim cStart As Char() = {"{"c, "X"c, "="c} pPoint.X = aPoint(0).TrimStart(cStart) Dim cEnd As Char() = {"Y"c, "="c} sFormattedPoint = aPoint(1).TrimStart(cEnd) pPoint.Y = sFormattedPoint.Trim("}"c) Return pPoint End Function Function formatSize(ByVal sPoint As String) As Size Dim sFormattedPoint As String Dim sSize As New Size() '<Size>{Width=248, Height=184}</Size> Dim aPoint() As String = sPoint.Split(",") Dim cWidth As Char() = {"{"c, "W"c, "i"c, "d"c, _ "t"c, "h"c, "="c} sSize.Width = aPoint(0).TrimStart(cWidth) Dim cHeight As Char() = {" "c, "H"c, "e"c, "i"c, _ "g"c, "h"c, "t"c, "="c} sFormattedPoint = aPoint(1).TrimStart(cHeight) sSize.Height = sFormattedPoint.Trim("}"c) Return sSize End FunctionEnd Class

  4. Now that we have the serialize class built, it can be instantiated from our default form. Return to the default form code window, Form1.vb, and add an Imports System statement.

  5. The context menu as well as the menu items that will be added will have class-level scope, so add these lines right after the #Region added by the IDE.

    #Region " Windows Form Designer generated code " Dim cmContextMenu As New ContextMenu() Dim mnuItem1 As New MenuItem("New Note") Dim mnuItem2 As New MenuItem("Hide All") Dim mnuItem3 As New MenuItem("Show All") Dim mnuItem4 As New MenuItem("Quit Program") Shared ser As New serialize() Public Sub New() MyBase.New() 'This call is required by the Windows Form Designer. InitializeComponent() 'Add any initialization after the ' InitializeComponent() call addContextMenu() ser.deserialize() End Sub

  6. In the Dispose procedure, add the following line that will call the Serialize method of the ser class. When the main form, Form1, is disposed of, all existing notes will be written to a stream.

    Protected Overloads Overrides Sub Dispose( _ ByVal disposing As Boolean) If disposing Then ser.serialize() If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub

  7. Now add the following procedures.

    Private Sub addContextMenu() With cmContextMenu .MenuItems.Add(mnuItem1) ' New Note .MenuItems.Add(mnuItem2) ' Hide All .MenuItems.Add(mnuItem3) ' Show All .MenuItems.Add(mnuItem4) ' Quit Program End With niIcon.ContextMenu = cmContextMenu AddHandler mnuItem1.Click, AddressOf mnuItem1Click AddHandler mnuItem2.Click, AddressOf mnuItem2Click AddHandler mnuItem3.Click, AddressOf mnuItem3Click AddHandler mnuItem4.Click, AddressOf mnuItem4Click End Sub Sub mnuItem1Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) '--Create a new note-- Dim fSticky As Form = New BaseNote() With fSticky .Text = Now .Show() End With ser.addNewSticky(fSticky) End Sub Sub mnuItem2Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) ser.hideAll() End Sub Sub mnuItem3Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) ser.showAll() End Sub Sub mnuItem4Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Me.Dispose() End Sub

Constructing a Sticky Note

Of course, a sticky note is really just a form. Select Project | Add Windows Form, name the form BaseNote.vb, and then click Open. We will design this new form to be a note, and then, because forms are classes, we will instantiate as many of these forms as we need as new notes.

  1. Add a text box control to the form. Next drag a MainMenu control from the toolbox to the design area. Set the properties of our form and its text box control as shown in Table 14-2.

    Table 14-2  Properties for BaseNote Form and Controls

    Object

    Property

    Value

    Form

    Name

    BaseNote

    ControlBox

    False

    Icon

    \Program Files\Microsoft Visual Studio .NET\Common7\Graphics\Icons\Writing\Note02.ico

    MaximizeBox

    False

    Menu

    MainMenu1

    ShowInTaskBar

    False

    MinimizeBox

    False

    StartPosition

    Manual

    Text box

    Name

    TxtNote

    BackColor

    Yellow

    Dock

    Fill

    MultiLine

    True

    note

    Each note is a form and would usually be displayed in the taskbar. If 10 or 20 notes are either displayed or hidden, having them all show up in the taskbar can be distracting. Setting the ShowInTaskBar property to False fixes this problem.

  2. Click the MainMenu control on the BaseNote form. On the top level, add &Note as a menu item. Next add three sublevel menu items—&Keep on Top; &Delete this note; &Hide this note. You can see the results in Figure 14-16.

    Figure 14-16

    Add these menu items.

Adding Code to the Sticky Note

Surprisingly, we don't need a lot of code in this form. We will instantiate a BaseNote form for each new note, but adding, saving, and other operations will be performed in the serialize class. First, add these imports statements.

Imports System.Windows.Forms Imports Sticky.Form1 Imports Sticky.serialize

As I mentioned, users can and will try to close various BaseForm sticky note forms. If they close a note form, it will not be available when we attempt to serialize all notes at the time the program is terminated. This is an easy problem to fix: we don't let users close a form because we eliminated the close button in the upper right corner and also the default form menu. We will simply hide the form, but not close it.

Intercepting the Close Event

You may in your programs find a need to programmatically stop the user from closing a form. To do this, when the user closes the form, you can intercept the Close event and hide the form instead by placing code in the Closing event. Because a form's Closing event template is not included by default, we could write it ourselves or let the IDE do it for us. Select Base Class Events in the drop-down list to the left in the IDE. Click the drop-down box to the right, and you'll see all the event handlers that the form knows how to respond to. Click Closing and the template will be written for us, as you see in Figure 14-17.

Figure 14-17

Click Closing to force the IDE to write a template for the Closing event.

If you ever need to stop a user from closing a form, intercept the Closing event and set the CancelEventArgs property to True. You might want to do this for a form in which a user enters data and might accidentally close the form. Most word processing or data capture forms contain a Boolean, bIsDirty, which is set to False when the form is loaded. If the user changes any information on the form, bIsDirty is set to True. When the user closes the form, the value of bIsDirty is checked. If the value is True, and the data was not yet saved, a message box is displayed alerting the user and asking whether the data should be saved before closing the form. Again, you don't need to add this code to our sample program. I'm showing it for illustrative purposes only.

Private Sub AForms_Closing(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) _ Handles MyBase.Closing e.Cancel = True 'Cancels the form's closing End Sub

Go ahead and add the code to handle the three child menu click events.

Private Sub MenuItem2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuItem2.Click MenuItem2.Checked = Not MenuItem2.Checked If MenuItem2.Checked = True Then Me.TopMost = True Else Me.TopMost = False End If End Sub Private Sub MenuItem3_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuItem3.Click Dim iResult As Integer = MessageBox.Show("Delete this note?", _ "Yellow Sticky", MessageBoxButtons.YesNoCancel, _ MessageBoxIcon.Question) If iResult = DialogResult.Yes Then serialize.delete(Me) Me.Close() Me.Dispose() End If End Sub Private Sub MenuItem4_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuItem4.Click MenuItem2.Checked = False Me.Hide() End Sub

How Does It Work?

Let's start with the sequence of how the code will execute. When our start up form is run, a context menu along with four menu items are dimensioned. Of course, this form will be invisible; we are using it only to display the menu and run the message loop. All of the menu items have class-level scope. The menu items are overloaded, so passing in the title we want in the menu prevents us from having to set the Text property of each later on.

Dim cmContextMenu As New ContextMenu() Dim mnuItem1 As New MenuItem("New Note") Dim mnuItem2 As New MenuItem("Hide All") Dim mnuItem3 As New MenuItem("Show All") Dim mnuItem4 As New MenuItem("Quit Program")

The next step is to instantiate an instance of our serialize class. We do this by assigning a new instance to our reference variable ser. Note that ser is shared. We shared this variable because we want to access this class from both this form and each individual BaseNote form. Therefore, we want only a single instance of serialize to be shared among entities in the program. The serialize class maintains an ArrayList of all forms currently active in our program, so we want to be sure that all forms work with this single ArrayList. Setting the access to Shared ensures that only a single copy of serialize will exist.

Shared ser As New serialize()

Now we call the routine addContextMenu to build our context menu for the program. Because the opacity of the form Form1 is 0 percent, it is invisible. The context menu is the only interface to our program. After the context menu is built, the deserialize method of the serialize class is called. This method reads an XML file that contains any current sticky note information.

'Add any initialization after the ' InitializeComponent() call addContextMenu() ser.deserialize()

The addContextMenu routine adds each of the four menu items we declared above to the MenuItems collection of the context menu. The MenuItems property contains the entire menu structure for the control. For the context menu, the MenuItems property contains the list of submenu items associated with the context menu. With the reference to the collection of menu items for the menu (provided by this property), you can add and remove menu items, determine the total number of menu items, and clear the list of menu items from the collection.

Private Sub addContextMenu() With cmContextMenu .MenuItems.Add(mnuItem1) ' New Note .MenuItems.Add(mnuItem2) ' Hide All .MenuItems.Add(mnuItem3) ' Show All .MenuItems.Add(mnuItem4) ' Quit Program End With

Now that the context menu has been built, we can assign it to the notify icon's contextMenu property.

niIcon.ContextMenu = cmContextMenu

We next have to add event handlers to manage the Click event of each menu item. AddHandler takes two arguments: the name of an event from an event sender such as menuItem1, and an expression that evaluates to a delegate such as mnuItem1Click. You do not need to explicitly specify the delegate class when using AddHandler because the AddressOf statement always returns a reference to the delegate.

AddHandler mnuItem1.Click, AddressOf mnuItem1Click AddHandler mnuItem2.Click, AddressOf mnuItem2Click AddHandler mnuItem3.Click, AddressOf mnuItem3Click AddHandler mnuItem4.Click, AddressOf mnuItem4Click

Adding Event Handler Delegates

The delegates' event handlers are where all of the action takes place in Form1. We added a handler for the mnuItem1.Click event and pointed it to the address of the mnuItem1Click event handler, which we must build on our own. Remember that mnuItem1 is for adding a new sticky note. When the user clicks this item, the mnuItem1Click delegate that was wired to that event is fired. We first instantiate a new instance of BaseNote and assign it to the reference variable fSticky. With the new form, we add the current time to the title and then immediately display the new note. After the new sticky note form is displayed, the form is passed to the addNewSticky method of the serialize class, which inserts the form in an ArrayList. We will soon visit the serialize class to see how this is done.

Sub mnuItem1Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) '--Create a new note-- Dim fSticky As Form = New BaseNote() With fSticky .Text = Now .Show() End With ser.addNewSticky(fSticky) End Sub

The next menu item permits the user to hide all of the sticky notes on the screen. A simple call to the hideAll method in the serialize class handles this.

Sub mnuItem2Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) ser.hideAll() End Sub

The same concept applies when the user clicks the Show All menu item. The showAll method is called from the serialize class.

Sub mnuItem3Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) ser.showAll() End Sub

When the user clicks the fourth menu item, Quit Program, we must take care to save all of the current notes to disk. To accomplish this, the Dispose method of Form1 is called. Because Form1 is invisible, this call is the only way to dismiss the program short of the task manager.

Sub mnuItem4Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Me.Dispose() End Sub

In this built-in method, we add a call to the serialize method of the serialize class. This method is responsible for writing all of the notes to disk. Only when serialization is accomplished is program control returned and the form dismissed.

Protected Overloads Overrides _ Sub Dispose(ByVal disposing As Boolean) If disposing Then ser.serialize() If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub

It's easy to see that all of the sticky note form manipulation was encapsulated in the serialize class. When we want to add, hide, or show the notes, methods in serialize are called. Likewise, when the program starts, the deserialize method of the serialize class reads notes from disk. When the program ends, the serialize method writes the sticky notes to disk again. The serialize class juggles all of the notes and acts as a traffic cop for each one of them.

The serialize Class in More Detail

The serialize class builds a data table and adds it to an in-memory data set to store the information from each sticky note. The ArrayList that holds the forms will be accessed both from the driver form (Form1) when we add a new form and from each of the sticky notes as well. The sticky note (a BaseNote form) will need to access the ArrayList that contains it when a user deletes that particular form. Making the ArrayList shared ensures that all the forms in the program use a single copy.

Imports System Imports System.Collections Imports System.IO Public Class serialize Dim dsDataSet As New DataSet() Dim tblDataTable As DataTable Dim dcDataColumn As DataColumn Dim dcDataRow As DataRow Shared aArraylist As New ArrayList() Dim bSerialized As Boolean = False

The New subroutine is the constructor of the serialize class and is called from our driver form, Form1. When a new, shared instance of this class is instantiated, we build a new DataTable object with the name tblSticky. The DataTable is then cleared just for good form.

Sub New() 'Create a new DataTable tblDataTable = New DataTable("tblSticky") tblDataTable.Clear()

Of course, we have to add columns to the new table in which to store information from our sticky notes. After a new DataColumn class is instantiated and referenced with the reference variable dcDataColumn, we give the column a data type and a name. This new column is then added to the Columns collection of our new table.

'Declare variables for DataColumn and DataRow objects. Dim dcDataColumn As DataColumn dcDataColumn = New DataColumn() With dcDataColumn .DataType = System.Type.GetType("System.String") .ColumnName = "Title" End With tblDataTable.Columns.Add(dcDataColumn)

When we want to store a form, we could stream the entire form to disk with a BinaryFormatter class. However, this approach would be overkill for our purposes. It's much easier to simply store our information in XML. So because we are not storing the entire BaseForm class but only its important information, we want to create fields for the actual message of the note, the location of the note, and the size of the note, in addition to the sticky note's title. With this critical information we can re-create as many notes as required. This table will be used when we save each note.

dcDataColumn = New DataColumn() With dcDataColumn .DataType = System.Type.GetType("System.String") .ColumnName = "Message" End With tblDataTable.Columns.Add(dcDataColumn) dcDataColumn = New DataColumn() With dcDataColumn .DataType = System.Type.GetType("System.String") .ColumnName = "Location" End With tblDataTable.Columns.Add(dcDataColumn) dcDataColumn = New DataColumn() With dcDataColumn .DataType = System.Type.GetType("System.String") .ColumnName = "Size" End With tblDataTable.Columns.Add(dcDataColumn) dsDataSet.Tables.Add(tblDataTable)

As soon as a new shared instance of the serialize class is instantiated from Form1, we call the deserialize method. The purpose of this method is to read an XML file that might contain sticky notes that were previously saved. The XML file is read from disk into the data set we created in the constructor. For each saved note, we create a new instance of the sticky note and reconstitute its message, size, and location.

When we dismiss the Sticky program, the serialize method writes any notes to a file, Sticky.xml. In serialize we open a stream. As mentioned earlier in the book, the Stream class and its derived classes provide a generic view of data sources and repositories, isolating the programmer from the specific details of the operating system and underlying devices. When we open the file and assign it to the stream, we use the FileMode and FileAccess parameters to specify that the operating system should open a file if it exists or create a new file if one does not. We could have first used the File.Exists("c:\sticky.xml") method, but using that method would mean another line of code and another Boolean check. So using FileMode is simply a design decision.

Once strStream points to the XML file containing our saved notes, we can read the XML file into our data set by passing in the stream as a parameter to a new XmlTextReader. The XmlTextReader object represents a reader that provides fast, noncached, forward-only access to XML data. The XmlReader checks that the XML is well formed and throws XmlExceptions if an error is encountered. It can read a stream or a document, and it implements the namespace requirements outlined in the recommendation provided by the W3C, located at www.w3.org/TR/REC-xml-names.

As you might recall from our discussion of ADO.NET, to read just the schema from an XML document, use the ReadXmlSchema method. To read the data from an XML document that contains only data into a data set, use the ReadXml method. Once the XML file has been read into the data set, we close the XmlTextReader and free up its resources. Using Try…Catch…End Try structured error handling protects us in the event that we can't read the file or errors occur when assigning properties.

Sub deserialize() Dim strStream As Stream Try strStream = File.Open("c:\sticky.xml", _ FileMode.OpenOrCreate, FileAccess.Read) Dim xmlrXmlReader As New _ System.Xml.XmlTextReader(strStream) dsDataSet.ReadXml(xmlrXmlReader) xmlrXmlReader.Close()

Now that the data set has been populated, we know that a single table is defined in the XML file. This code dimensions DataTable, DataRow, and DataColumn objects so that we can read what's in the data set. In addition, when we save the location and size of the form, these values are saved as groups of data. However, when we try to assign this data back to a new form to set its location and size, we have to format the information differently. The sticky note's Location property requires a Point data type and the Size property requires a Size data type, while we are storing this information in strings in our XML file.

 <Location>{X=408,Y=80}</Location> <Size>{Width=248, Height=131}</Size> 

We can easily get the table we want by iterating through the tables collection of our data. We, however, know there is only a single table, tblDataTable. Next we loop through each of the rows in the Rows collection of the data table. Each row represents all the information about a single sticky note. On looping through a new row, a new BaseNote class (a sticky note) is instantiated. We know that the first row contains the contents of the title of the form, which we set to the time the note was created. The second field is the actual message of the note. This field is assigned to the Text property of the txtNote text box on the form.

<NewDataSet> - <tblSticky> <Title>9/19/2000 11:19:23 PM</Title> <Message>Remember to feed Roscoe.</Message> <Location>{X=408,Y=80}</Location> <Size>{Width=248, Height=131}</Size> </tblSticky> Dim dtDataTable As DataTable Dim drDataRow As DataRow Dim dcDataColumn As DataColumn Dim sPoint As String 'Hold {Width=248, ' Height=184} Dim sLocation As String 'Hold "248,184" For Each dtDataTable In dsDataSet.Tables For Each drDataRow In dtDataTable.Rows Dim fSticky As BaseNote = New BaseNote() fSticky.Text = drDataRow(0) fSticky.txtNote.Text = drDataRow(1)

The location of the form is stored as an x, y coordinate. However, this represents a Point data type, which contains both an X and a Y property that needs to be set. If you expand the Location property on the properties window of the BaseNote form, you can see the two properties, as shown in Figure 14-18.

Figure 14-18

The two properties of the Point data type.

sPoint = drDataRow(2) fSticky.Location = formatPoint(sPoint)

We take the contents of the third element of the record and assign it to the string sPoint. The string will look something like this:

<Location>{X=408,Y=80}</Location> 

The string will have to be converted to a type Point. Our function, formatPoint, is passed the string containing the location. The function returns a Point data type that can now be assigned safely to the Location property of the new sticky form.

The same concept applies when we want to set the Size property. We take the fourth column and assign it to a string variable. This column's contents will look something like this:

 <Size>{Width=248, Height=131}</Size> 

We created another function, formatSize, that will take the size record stored in the fourth column and return a properly formatted Size data type that can safely be assigned to the Size property of our sticky note.

sPoint = drDataRow(3) fSticky.Size = formatSize(sPoint)

When the new sticky form's properties are all set, the form is displayed and added to our ArrayList, which manages all of the current sticky notes during a session.

 fSticky.Show() aArraylist.Add(fSticky) Next Next Catch End Try StrStream.Close()

In the event no sticky notes are stored on the hard drive, a message is displayed alerting the user to that fact. The Count property of our ArrayList can be interrogated to determine how many sticky forms have been created and stored for this session.

If (aArraylist.Count = 0) Then MessageBox.Show("No notes - please right " & _ "click icon in tray", "Sticky Notes", _ MessageBoxButtons.OK, _ MessageBoxIcon.Information) End If

During a session, the user can right-click the notify icon and select New Note. In that case, our driver form, Form1, instantiates a new BaseNote object and calls serialize.AddNewSticky. As you can see, that call simply inserts the new form into our ArrayList.

Sub addNewSticky(ByVal sticky As BaseNote) aArraylist.Add(sticky) End Sub

If the user wants to display all of the notes—as some might have been hidden previously during a session—the showAll method is called from Form1 when the Show All menu item is clicked. Here we simply iterate through each of the sticky note forms stored in the ArrayList and call the Show method.

Sub showAll() Dim fSticky As Sticky.BaseNote For Each fSticky In aArraylist fSticky.Show() Next End Sub

Remember that the user can delete an individual note by selecting Delete This Note from the menu for that individual note. This procedure is shared so that the same procedure can be accessed from all of the sticky notes to ensure that all forms add themselves to and remove themselves from the same ArrayList. The individual note is passed as a parameter and removed from the ArrayList. Because the ArrayList manages all existing forms, removing a note ensures that the deleted note will not be serialized to disk when the program is exited.

Shared Sub delete(ByVal sticky As BaseNote) aArraylist.Remove(sticky) End Sub

The hideAll procedure is conceptually like showAll. When the user clicks the Hide All menu selection, this subroutine is invoked, the ArrayList containing all the sticky notes is iterated through, and the Hide method is called on each, effectively hiding each form in turn.

Sub hideAll() Dim fSticky As Sticky.BaseNote For Each fSticky In aArraylist fSticky.Hide() Next End Sub

When the User Quits the Sticky Notes Program

When the Sticky Notes program is terminated, the serialize method is called from the dispose method of Form1. Remember that we added this line to be sure our serialize class saved all the notes properly before returning control to the dispose method.

Protected Overloads Overrides _ Sub Dispose(ByVal disposing As Boolean) If disposing Then ser.serialize() If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub

The serialize method essentially iterates through each sticky note form that is stored in the ArrayList, assigns its important properties to a new row in the data table we created in the constructor, and then uses the WriteXML method of the data set to stream out the contents of our sticky notes. Depending on when this routine is called, it might be called twice. To prevent an error, we set the Boolean bSerialized to True the first time through; if it is called again, program control simply exits.

A variable of type Sticky.BaseNote has been declared and assigned to the reference variable fSticky. We want to be sure that the data table is empty before we save our current notes; invoking the Clear method of the table handles this for us.

Sub serialize() If bSerialized = True Then Exit Sub Dim fSticky As Sticky.BaseNote '--Remove older data tblDataTable.Clear()

Next we build new rows for the data table and then add each to the Rows collection. This lets us use the WriteXML method of the data set to create our file.

For Each fSticky In aArraylist dcDataRow = tblDataTable.NewRow() dcDataRow(0) = fSticky.Text dcDataRow(1) = fSticky.txtNote.Text dcDataRow(2) = fSticky.Location dcDataRow(3) = fSticky.Size tblDataTable.Rows.Add(dcDataRow) Next

When the important information for each form in the ArrayList has been translated into a new row in our data table, we open another stream to our Sticky.xml file. By passing the stream to the WriteXML method of the data set, our file is created for us. The stream is closed and the Boolean is set to True, indicating that serialization has already been accomplished.

Dim strStream As Stream = File.Open("c:\sticky.xml", _ FileMode.Create, FileAccess.ReadWrite) dsDataSet.WriteXml(strStream) strStream.Close() bSerialized = True

Our formatPoint function takes in a string and returns a Point data type. In this simple function, we cover some important concepts about strings and arrays. First, we dimension variable pPoint as type Point. Next we dimension an array, aPoint, and assign it on the same line. Because we know that the sPoint string passed into the function looks like {X=408,Y=80}, we need to do something because we want the result to be 408,80 so that we can convert these values to a .NET Point data type. Because we are now dealing with a string, using the Split method of sPoint and the comma as a delimiter, the aPoint array will contain two elements. The Split method returns a zero-based, one-dimensional array containing a specified number of substrings. Now our array will contain two elements. The first element is {X=408 and the second element is Y=80}.

Function formatPoint(ByVal sPoint As String) As Point Dim sFormattedPoint As String Dim pPoint As New Point() Dim aPoint() As String = sPoint.Split(",")

We want to get rid of a few characters at the beginning of the first array element. Using the built-in TrimStart method does the trick. This method removes characters from the beginning of a string specified by an array of characters. By dimensioning and initializing the cStart array of characters that we want to remove, we can pass that array as a parameter of the TrimStart method. Each character is designated with a c after the quoted character, which ensures that each symbol is cast as a character and not a byte. Each character is stored in the cStart array. The pPoint variable has an X and a Y property. Because we know the X position is stored in the first array element, that is assigned to our Point data type variable. TrimStart effectively removes the {X= from the element and leaves only the 408 to be assigned to pPoint.X.

Dim cStart As Char() = {"{"c, "X"c, "="c} pPoint.X = aPoint(0).TrimStart(cStart)

We perform the same operation with the second aPoint array element. That element will look something like Y=80}. Therefore the cEnd array contains the first two characters and the TrimStart method removes them and assigns the result to the sFormattedPoint string. However, the result will contain an ending bracket and look like 80}.

Dim cEnd As Char() = {"Y"c, "="c} sFormattedPoint = aPoint(1).TrimStart(cEnd)

Removing the curly bracket is simply a matter of using the Trim method of the sFormattedPoint string and assigning the resulting "80" to the Y property of the pPoint variable. The pPoint Point variable is then returned to the caller and assigned to the sticky form's Location property.

pPoint.Y = sFormattedPoint.Trim("}"c) Return pPoint

The only difference between the formatPoint and formatSize routines is that we are returning different data types. Except for the specific details, the concepts are identical.

Function formatSize(ByVal sPoint As String) As Size Dim sFormattedPoint As String Dim sSize As New Size() '<Size>{Width=248, Height=184}</Size> Dim aPoint() As String = sPoint.Split(",") Dim cWidth As Char() = {"{"c, "W"c, "i"c, "d"c, "t"c, _ "h"c, "="c} sSize.Width = aPoint(0).TrimStart(cWidth) Dim cHeight As Char() = {" "c, "H"c, "e"c, "i"c, _ "g"c, "h"c, "t"c, "="c} sFormattedPoint = aPoint(1).TrimStart(cHeight) sSize.Height = sFormattedPoint.Trim("}"c) Return sSize End Function

How the BaseNote Sticky Yellow Form Works

Our BaseNote form has only a few lines of code, so let's take a close look at them. Sometimes the user will want to keep one or two notes on top of all the other windows. Topmost forms are always displayed at the highest point in the z-order of an application. A program such as one that uses a Find and Replace window also might keep its windows on top of others. In our application, we will check the menu item to provide a visual cue that the window is on top, as you can see in Figure 14-19.

Figure 14-19

The Keep On Top menu item is checked when the TopMost property is True.

Because the menu item can be either checked or unchecked, we will use a little technique that toggles between the two. We can accomplish this in a single line of code.

MenuItem2.Checked = Not MenuItem2.Checked

When the user clicks the menu for the first time, it is not checked (the Checked property is False), so the Not operator sets the Checked property to True and the menu becomes checked. Likewise, when the menu item is clicked again, because the Checked property is True, the Not operator sets it to False. This mechanism simply toggles between checked and unchecked each time the user clicks the menu item. This is much cleaner than writing code that first checks the current value of the property, then uses an If statement to check to see if the menu item is checked, and then unchecks the item. If the menu item is checked, the Topmost property of the form is set to True; otherwise it is False.

Private Sub MenuItem2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuItem2.Click MenuItem2.Checked = Not MenuItem2.Checked If MenuItem2.Checked = True Then Me.TopMost = True Else Me.TopMost = False End If End Sub

If the user wants to delete a specific note, the MenuItem3_Click event procedure handles this. First, we display a message box to confirm the action, shown in Figure 14-20.

Figure 14-20

We ask for confirmation before deleting a note.

The message box returns an integer that contains one of several enumerated values. Because the message box is a variation of a dialog box, the return values are the same. Each return value is an integer, but they are enumerated, so you can simply test for the English value. To determine what the user selected, we assign the result from the message box to an integer variable, iResult. This variable can be checked to see whether the user clicked the Yes button. If the user clicked Yes, the delete method of the serialize class is called with the current form as a parameter. Remember that the delete method simply removes that particular form from the shared ArrayList. Recall also that certain fields, methods, and properties can be associated with the class itself rather than with an instance of the class. As mentioned before, our ArrayList is allocated in memory only once for the entire class and is shared by all instances of the BaseNote form.

Private Sub MenuItem3_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuItem3.Click Dim iResult As Integer = MessageBox.Show("Delete this note?", _ "Yellow Sticky", MessageBoxButtons.YesNoCancel, _ MessageBoxIcon.Question) If iResult = DialogResult.Yes Then serialize.delete(Me) Me.Close() Me.Dispose() End If End Sub

If the user wants to hide a note but keep it around, we simply make sure that the Keep On Top menu item is unchecked (in case it was previously checked) and then hide the form.

Private Sub MenuItem4_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuItem4.Click MenuItem2.Checked = False Me.Hide() End Sub

Deploying Our Sticky Notes Program

We now have this pretty handy program and want to give it to our friends. Some of our friends might not have the .NET files installed on their computers yet, but the files' absence causes no problem because we can add the required files right in our installation program.

The first step is to add another project to our solution that contains a Setup Wizard. From the main IDE menu, select File | Add Project | New Project. When the Add New Project dialog box is displayed, select Setup And Deployment Projects in the Project Types list and then select Setup Wizard in the group of templates. Give the project the name Sticky Notes.

Visual Studio .NET provides several options that make assembling program files, assemblies, and any resources simple and consistent. The Setup Wizard creates an executable that performs the installation tasks to get the program up and running quickly. Table 14-3 lists some guidelines for choosing the correct type of deployment project to add to your project.

Table 14-3  Types of Deployment Projects

Setup Template

Description

CAB Project

Creates a compressed cabinet archive file that contains multiple smaller files. This could also be used with the other types of projects if many files are included with your project. CAB projects allow you to create a .cab file to package ActiveX components that can be downloaded from a Web server to a Web browser.

Merge Module Project

When several projects use common files, this permits creation of an intermediate module that can be integrated into other setup projects. The resulting .msm files can be included in any other deployment project.

Setup Project

Creates a setup file that automatically installs files and resources on the target client computer.

Setup Wizard

Creates any of these project types with the help of a wizard to walk through the steps.

Web Setup Project

Builds an installer for a Web application.

For a setup project, the installer will install files into a Program Files directory on a target computer. For a Web Setup project, the installer will install files into a Virtual Root directory on a Web server. Note that once a project is created, the type of a project can't be changed from Web to standard or vice versa. If you have created a standard deployment project and decide later to deploy the project to a Web site, you will need to create a new setup project.

After adding the setup project, the Setup Wizard is displayed. The wizard will walk us through deploying any of the types of projects. Click Next on the introductory screen. On the second screen, keep the default value for creating a setup for a Windows application, shown in Figure 14-21.

Figure 14-21

Keep the default value for creating a setup for a Windows application.

Click Next, and select Primary Output From Sticky, which will be the executable for our compiled program. Click Next again. The fourth screen permits the addition of any help or readme files, bitmaps, or any other resource files you want to include with the program. Our program is so simple we don't need any help files. Click Next to display the last page of the Setup Wizard, shown in Figure 14-22.

Figure 14-22

The summary information of the Setup Wizard.

After reviewing the summary information, click Finish. The new setup project will be added to the Solution Explorer. Either right-click the entry for the primary output from Sticky and select Properties or select the entry and click the Properties button in the upper left of the Solution Explorer, shown in Figure 14-23.

Figure 14-23

Open the Properties window for the primary output from our program.

Take a few minutes to review the properties for the output of the program. Expand the KeyOutput node, shown in Figure 14-24, and examine the entries to gain a sense of what will be included in the build. Clicking Dependencies displays a dialog box showing all the files required to run the Sticky Notes program. Clicking the ellipsis button in the Files entry opens the Files dialog box that will show Sticky.exe. If any readme files had been included, they would also be displayed here.

Figure 14-24

Expand the KeyOutput node.

You can customize the installation process for your own company. Bring up the Solution Explorer, and click the Sticky Notes setup project. Notice the new icons displayed in the Explorer. These icons provide options for you to customize the installation process. Click the fourth button, for the User Interface Editor, shown in Figure 14-25.

Figure 14-25

Click the User Interface Editor button.

A User Interface tab will be displayed in the IDE. Expand all the nodes in the install tree. The properties of each of these items can be selected and customized. The User Interface Editor is used to specify and edit dialog boxes that are displayed during installation on a target computer. Dialog boxes are included for most common installation functions such as gathering user information or reporting progress, but you can also add your own custom dialog boxes.

Right-click the Progress item, and select Properties Window. Here you can suppress the progress bar or even add a bitmap to be displayed during installation, as shown in Figure 14-26.

Figure 14-26

The Properties window for the Progress item.

Now we want to finally build our Sticky Notes project, so let's change the build type from Debug to Release. From the main IDE menu select Build | Configuration Manager. Change the configuration options to Release, and select the Build check box for the Sticky Notes project, shown in Figure 14-27, and then close the window.

Figure 14-27

Change the configuration options to Release, and select the Build check box for the Sticky Notes project.

From the main menu, select Build | Build Solution. The Build window of the Output form displays the progress as our program is being compiled. Notice how all the dependent files are also included in the build.

Installing Our Program on a Client Machine

To find our setup file, navigate to the directory in which you built the program. In the example, we called the setup project Sticky Notes, so a directory was created with the name Sticky Notes. The Sticky Notes.msi file is in the release subdirectory of the Sticky Notes directory, <drive>DirectoryCreatedIn\Sticky Notes\Release.

DLL conflicts will be eliminated as advertised with .NET. All of the files required to run our program are installed in the directory along with our .msi file. In theory you could use the venerable MS-DOS XCOPY command to copy all these files into a single directory, without having to worry about registry entries or shared DLLs in the system directory. Everything needed to run Sticky Notes is contained in the single directory. Even Mscorlib.dll, which contains the common language runtime, is included. That is how the magic is accomplished.

Right-click the Sticky Notes.msi install program, and select Properties. As you can see in Figure 14-28, the program is over 15 megabytes because of all the support files that were installed in the CAB file.

Figure 14-28

The Sticky Notes program's properties dialog box.

Install the Sticky Notes Program

Double-click the Sticky Notes installation icon to install the program on the client machine. The setup program starts its work, as you can see in Figure 14-29.

Figure 14-29

Installing the Sticky Notes program.

To find your program, navigate to <Drive>\Program Files\Default Company Name\Sticky Notes. The file Sticky.exe is in that directory. Right-click Sticky.exe, select Create Shortcut, and then drag the shortcut icon to the desktop so that it will always be available. Run the Sticky Notes program to see your work in action. It's as simple as that.



Coding Techniques for Microsoft Visual Basic. NET
Coding Techniques for Microsoft Visual Basic .NET
ISBN: 0735612544
EAN: 2147483647
Year: 2002
Pages: 123
Authors: John Connell

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net