Wednesday, September 24, 2008

How to get the index (GetIndex) from a ToolStripMenuItem

When we converted our application to VB.NET.  The upgrade wizard created this VB6.MenuItemArray, then using it it set the indices for all the menu items that were in it.  So when a user clicked on one of its menu items.  The .Click event was called and inside this a function called GetIndex was called and in our application we used this index to figure out what menu item was clicked.  Below is a short example of the code in our .Click event.

  Public Sub MenuBrushSize_Click(ByVal eventSender As System.Object, ByVal e As System.EventArgs) Handles MenuBrushSize.Click
    Dim Index As Short = MenuBrushSize.GetIndex(eventSender)
    Select Case Index
      Case 0
        'do something
      Case 1
        'do something
      Case 2
        'do something
      Case Else
        'do something
    End Select
  End Sub

When I moved over to 2005 and decided to use the ContextMenuStrip and subsequently the ToolStripMenuItem, I decided not to use the VB6.MenuItemArray.  But then I didn't want to have separate handler routines for each and every one of my menu items.  I wanted to have it all in one Subroutine.  But there isn't a GetIndex function for the ToolStripMenuItem.  So I had to create one, below is how I was able to do it:

  Public Function GetMenuItemIndex(ByVal sender As System.Object) As Short
    Dim oMenuItem As New System.Windows.Forms.ToolStripMenuItem
    oMenuItem = CType(sender, System.Windows.Forms.ToolStripMenuItem)
    If TypeOf oMenuItem.Owner Is ContextMenuStrip Then
      Dim parentMenu As ContextMenuStrip = oMenuItem.Owner
      For index As Integer = 0 To parentMenu.Items.Count - 1
        If oMenuItem.Name = parentMenu.Items(index).Name Then
          Return index
        End If
      Next
    ElseIf TypeOf oMenuItem.Owner Is ToolStrip Then
      Dim parentMenu As ToolStrip = oMenuItem.Owner
      For index As Integer = 0 To parentMenu.Items.Count - 1
        If oMenuItem.Name = parentMenu.Items(index).Name Then
          Return index
        End If
      Next
    End If
  End Function

This function basically checks the parent of the current menu, and gets the index of that menu item.  Basically I assume the menu item is part of some collection and I want to find out the index in the collection.

Below is how I use it.  Its pretty much the same, execept now my handles must specify all the menu items we are handling.

  Public Sub MenuBrushSize_Click(ByVal eventSender As System.Object, ByVal e As System.EventArgs) Handles _
    MenuBrushSize_0.Click, MenuBrushSize_1.Click, MenuBrushSize_2.Click, MenuBrushSize_3.Click

    Dim Index As Short = GetMenuItemIndex(eventSender)
    Select Case Index
      Case 0
        'do something
      Case 1
        'do something
      Case 2
        'do something
      Case Else
        'do something
    End Select
  End Sub


ContextMenuStrip is not modal like ContextMenu

I had an application that did something this

Menu.Show(Me.Image, e.x, e.y)
'Followed by a block of code
Block of code

The thing is my application didn't expect to execute the block of code until after the user selected the menu item.  So basically, when Menu.Show was called, we waited until the user clicked the menu item (at which point the code in the .Click) routine was called, and then we executed "Block of code"

Recently, I "updated" my application and decided to use the ContextMenuStrip for my menu.  Well, what happens now is after the Show method is called, the Block of code is executed immediately.  Which screws me up.

Haven't yet figured out a solution for this.

Wednesday, September 10, 2008

Saving global variables to an XML file

The block below is from the book Programming Microsoft Visual Basic 2005, which is an excellent book and should be on every VB Programmers shelf:

Here's a practical example of a technique that I used in several real applications. I gather all the global variables in a class named something like Globals or ProjectData that I can persist to an XML file stored on disk. To leverage the XML serialization mechanism offered by the .NET Framework, these global variables must be implemented as instance fields, but at the same time the class must be exposed as a singleton so that it can be accessed from anywhere in the application. Here's how you can build such a class:

' This code requires a reference to the System.Xml.dll assembly.

' (Classes must be public to use XML serialization.)
Public Class Globals
' This singleton instance is created when the application is created.
Private Shared m_Value As New Globals

' This static read-only property returns the singleton instance.
Public Shared ReadOnly Property Value() As Globals
Get
Return m_Value
End Get
End Property

' Load the singleton instance from file.
Public Shared Sub Load(ByVal fileName As String)
' Deserialize the content of this file into the singleton object.
Using fs As New FileStream(fileName, FileMode.Open)
Dim xser As New XmlSerializer(GetType(Globals))
m_Value = DirectCast(xser.Deserialize(fs), Globals)
End Using
End Sub

' Save the singleton instance to file.
Public Shared Sub Save(ByVal fileName As String)
' Serialize the singleton object to the file.
Using fs As New FileStream(fileName, FileMode.Create)
Dim xser As New XmlSerializer(GetType(Globals))
xser.Serialize(fs, m_Value)
End Using
End Sub

' Instance fields (the global variables)
Public UserName As String
Public Documents() As String
Public UseSimplifiedMenus As Boolean = True
Public UseSpellChecker As Boolean = True
End Class

Using the Globals class is straightforward because you must simply invoke the Load static method after the application starts and the Save static method before the application terminates. All the global variables can be accessed as properties of the Globals.Value object, as follows:

Globals.Value.UserName = "Francesco"
' Assign two items to the Documents array.
Globals.Value.Documents = New String(){"c:\doc1.txt", "c:\doc2.txt"}
' Save current global variables on disk.

Globals.Save("c:\myapp\globals.xml")

Interestingly, the very first time a given user runs the application, the XML file doesn't exist, thus the singleton Globals object will contain the default values for global variables you've defined by means of initializers.


Note 

Visual Basic 2005 comes with a very powerful mechanism for saving and retrieving user settings, which is conceptually similar to the one I illustrate in this section. You can read more about it in Chapter 16, "The My Namespace." However, the technique I just illustrated is more generic and flexible than the built-in Visual Basic mechanism is and can be used in many circumstances in which the built-in approach wouldn't work. For one, the built-in technique can store only one set of values for each user, and you can't manage multiple sets of preferences for a given user, merge current options with other users, move options to other computers, and so forth.


Wednesday, September 3, 2008

Official XML Comment Tags for .NET


The "official" XML comment tags

The following XML comment tags are officially supported in VB.NET: c,code, example, exception, include, list, para, param, paramref, permission, remarks, returns, see, seealso, summary and typeparam.

c: This tag is used to denote code, so it can be used to identify sample code associated with a particular entity within the source code. Note that the tag can only be used to represent a single line of code or to denote that some of the text on a line should be marked up as code. Example: <c>Dim MyObject As MyType</c>

code: This tag is used to denote more than one line of code, so it can be used to identify longer areas of sample code.

example: This tag may be used to describe an example of using a particular method or class. It is possible to include code samples within the example tag by making use of either the c or code tags.

exception: The exception tag allows a method's exception handling to be documented. The cref attribute allows the namespace of the exception handler to be included. Example: <exception cref="System.Exception" >Throws an exception if the customer is not found.</exception>

include: This tag allows documentation to be imported from an external XML file rather than being stored within the source code itself. As such it may be useful if the documentation is written by people other than the software developers (e.g. technical writers).

list: This tag allows bulleted or numbered lists or tables to be added to XML comments.

para: This tag indicates that the text within the tags should be formatted within a paragraph. As such it can be used to format text within other tags such as summary or returns. Example: <para>This is a paragraph</para>

param: The param tag is used to document a method's arguments. The tag's name attribute specifies the name of the argument to which the tag refers. The tag is also used by Visual Studio's Intellisense system to show a description of a method's arguments. It is also used by the Visual Studio Object Browser. Example: <param name="FileName" >The filename of the file to be loaded.</param>

paramref: This tag can be used to refer to a method's argument elsewhere within the method's XML comments. The tag's name attribute specifies the name of the argument to which the tag refers. Note that the param tag is actually used to describe the parameter. Example: Use the <paramref name="FileName"/> argument to specify which filename is loaded.

permission: The permission tag can be used to describe any special permissions a specific object needs. The object to which the permission refers is included as the cref attribute. Example: Class needs to write to the file system, ensure user has appropriate access.

summary: The summary tag describes a method or class, so it is the most important tag. As well as being used in a project's documentation, the tag is also used by Visual Studio's Intellisense system to show a description of the method or class being referenced. It is also used by the Visual Studio Object Browser.

remarks: The remarks tag can be used to supply additional information about a method or class, supplementing the details given in the summary tag. As with the summary tag, this tag is also used by Visual Studio's Intellisense system and the Visual Studio Object Browser.

returns: Describes the return value of a method. Example: <returns>True if user has permission to access the resource, otherwise False.</returns>

see: The see tag is used to reference other entities (such as classes) in the project. The see tag is intended for use within other tags such as the summary tag. Example: <seealso cref="System.Configuration"/>

seealso: The seealso tag resembles the see tag and has identical syntax, except that the text is intended to be used to create a separate seealso section for the entity's documentation.

typeparam: Typeparam is used in an identical way to param, except that it is used to document a type associated with a generic class or function.

value: Value is only used when documenting a property, and is used to describe the value assigned to that. The comment is not required for properties that are marked as read only. Example: <value>Sets the employee's salary</value>