The Assembly Spy Program

The Assembly Spy Program

In the following sections, we'll write a program we can use to spy on assemblies. In the process of creating this program, you'll solidify your understanding of what assemblies are all about and learn more about how to use Visual Basic .NET controls. Our program will have a menu, a list box at the left that displays namespaces, and a tree view control on the right that enables a hierarchical listing of the internals of each namespace. Because we can't be sure of the length of the names of the namespaces, properties, methods, and other internals, we'll add a splitter bar between the list box and the tree view control. The user can move the splitter in either direction for easier viewing. What's great about the splitter is that we simply set properties—no programming is involved. Once designed, our application window will look like Figure 8-4.

Figure 8-4

The Assembly Spy program.

The menu in our program permits a user to open a new assembly, examine summary assembly information, or quit the program. If no assembly is currently being viewed, the Assembly Information menu option is not available, as you can see in Figure 8-5.

Figure 8-5

When we're not spying on an assembly, the Assembly Information command is not available.

In creating the Assembly Spy program, you'll also learn how to use the new and improved Open File dialog box. When a user clicks Open Assembly on the menu, our customized dialog box appears. As you can see in Figure 8-6, the title bar shows users what they are supposed to do. We also configure the Files Of Type list to display only assembly files.

Figure 8-6

In the Assembly Spy program, we customize the Open File dialog box to show only assembly files.

As an example of how the Assembly Spy program works, if we open the C:\WINDOWS\Microsoft.NET\Framework\<version number> folder, select Windows.Forms.dll, and click Open, that assembly is loaded into our program. All the namespaces contained in this assembly are listed in alphabetical order on the left. On the right, we list the particular class we are interested in from the namespace at the root of the tree view control. All of the various components of the class, such as constructors, fields, properties and so on are listed. The plus sign (+) tells us whether any of the components have items in a collection. In Figure 8-7, the Instance Methods item is selected and expanded to reveal the signatures of the methods. The tree view control provides a tool tip that displays the entire contents of a partially hidden entry when the mouse hovers over it. As an added touch, we include the current location and name of the assembly in the title of the form.

Figure 8-7

The Assembly Spy program in action, showing namespaces on the left and class components on the right.

The signatures of methods in assemblies are written in C#, not in Visual Basic. To us, the signatures look a bit backward. For example, notice in Figure 8-7 that the method highlighted does not return a value (Void) but simply takes a menu as a parameter. Notice also that both static and instance fields, properties, events, methods, and constructors are shown. Simply substitute the Visual Basic keyword Shared for the C# identifier Static, and remember that a shared field or property means that only a single instance is shared among all classes. Instance fields or properties, of course, are unique to an instance of the object.

Because an assembly is currently loaded, selecting Assembly Information from our menu displays a message box with summary information, as shown in Figure 8-8.

Figure 8-8

Summary information about an assembly produced by the Assembly Spy program.

Building the Assembly Spy Program

Follow these steps to start building the Assembly Spy program yourself.

  1. Start a new Windows application project, and name it AssemblySpy.

  2. On the default form, add a list box control on the left and a tree view control on the right.

  3. Drag a MainMenu and an OpenFileDialog control from the toolbox to the form as well. The only control we still need to add is the splitter, but we have to dock our list box and tree view first, and we'll get to that in a moment. The form should now look something like Figure 8-9. (We'll change the control properties and add commands to the menu in a bit.)

    Figure 8-9

    The Assembly Spy program starts to take shape.

  4. Double-click the form to display the code window. Change the form's class name from the default, Form1, to AssemblySpyForm. Here's how the code should look when you're finished with this step.

    Public Class AssemblySpyForm Inherits System.Windows.Forms.Form

  5. Set the properties for the controls you've added as shown in Table 8-1.

    Table 8-1  Properties and Values for the AssemblySpyForm Controls

    Object

    Property

    Value

    MainMenu

    Name

    mnuMain

    OpenFileDialog

    Name

    ofdAssemblies

    Form1

    Name

    AssemblySpyForm

    Menu

    mnuMain

    Listbox1

    Name

    lbNamespaces

    Dock

    Left

    Sorted

    True

    You can dock controls at the edges of your form or have them fill the control's container (either a form or a container control). When a control is docked at an edge of its container, it will always be positioned flush against that edge when the container is resized. If more than one control is docked at an edge, the controls will not be placed on top of each other. Once the list box is docked (see Figure 8-10), we can add a splitter control.

    Figure 8-10

    Docking the list box control.

  6. Now drag a splitter control from the toolbox to the form. Set its Dock property to Left. This control will do the heavy lifting in resizing both the list box and the tree view. No code is required to make the splitter operational, but set its properties and the properties of the tree view control as shown in Table 8-2.

    Table 8-2  Properties and Values for the Splitter and TreeView Controls

    Object

    Property

    Value

    Splitter1

    Dock

    Left

    TreeView

    Name

    tvInternals

    Dock

    Fill (will fill the remainder of the form)

Adding the Main Menu

The main menu, mnuMain, was associated with our AssemblySpyForm form by setting a property. With the Visual Studio .NET menu editor, you are only required to type in what you want the menu choices to display. Be sure to add the ampersand (&) before the first letter of each menu choice to create a keyboard shortcut for the menu, shown by the underscored characters in Figure 8-11.

Figure 8-11

The main menu in the Assembly Spy program.

Menu event handlers are not added by default. The easiest way to have Visual Basic .NET add the handler for you is to double-click the menu item. Double-clicking instructs the IDE to add the template for that particular menu item's Click event handler.

Let's Write Some Code

All right, let's have a look at the code for this program. I trust you're seeing patterns in writing .NET programs by now and are feeling more comfortable working with the .NET Framework. I'll review only the key concepts as we go through this code.

Imports System.Reflection Imports System Imports System.Text Imports CtrlChrs = Microsoft.VisualBasic.ControlChars Namespace AssemblyView  Public Class AssemblySpyForm Inherits System.Windows.Forms.Form  Dim tnNode As TreeNode Dim tnRootNode As TreeNode Const progName As String = "Assembly Spy" Dim sAssemblyLocation As String = "" Dim aAssembly As [Assembly]  '—Control declarations omitted... #Region " Windows Form Designer generated code " '—Omitted #End Region Private Sub loadAssembly(ByVal sAssemblyLocation As String) Dim tType As Type Try Me.Text = "Assembly: " & sAssemblyLocation aAssembly = [Assembly].LoadFrom(sAssemblyLocation) updateModuleTypes(aAssembly.GetTypes) Me.MenuItem3.Enabled = True Catch Me.MenuItem3.Enabled = False MessageBox.Show(sAssemblyLocation & _ " is not an assembly. ", progName, _ MessageBoxButtons.OK, _ MessageBoxIcon.Information) sAssemblyLocation = "" End Try End Sub Private Sub updateModuleTypes(ByRef tTypes() As Type) Dim tType As Type lbNamespaces.BeginUpdate() Cursor.Current = System.Windows.Forms.Cursors.WaitCursor For Each tType In tTypes If tType.IsPublic Then lbNamespaces.Items.Add(tType.FullName) End If Next lbNamespaces.EndUpdate() Cursor.Current = System.Windows.Forms.Cursors.Default End Sub Private Sub lbNamespaces_SelectedIndexChanged( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles lbNamespaces.SelectedIndexChanged Dim tType As Type = _ aAssembly.GetType(lbNamespaces.SelectedItem) tnRootNode = New TreeNode() tnRootNode.Text = "Class: " & tType.Name With tvInternals .Nodes.Clear() .Nodes.Add(tnRootNode) .SelectedNode = tnRootNode End With '-- Add Categories and Elements -- addCategory("Constructors") Dim ciConstructorInfo() As ConstructorInfo = _ tType.GetConstructors(BindingFlags.Static Or _ BindingFlags.NonPublic Or BindingFlags.Public) printElements(ciConstructorInfo) addCategory("Static Fields") Dim fFields() As FieldInfo = _ tType.GetFields(BindingFlags.Static Or _ BindingFlags.NonPublic Or BindingFlags.Public) printElements(fFields)  addCategory("Static Properties") Dim piStaticPropertyInfo() As PropertyInfo = _ tType.GetProperties(BindingFlags.Static Or _ BindingFlags.NonPublic Or BindingFlags.Public) printElements(piStaticPropertyInfo) addCategory("Static Events") Dim eiStaticEventInfo() As EventInfo = _ tType.GetEvents(BindingFlags.Static Or _ BindingFlags.NonPublic Or BindingFlags.Public) printElements(eiStaticEventInfo) addCategory("Static Methods") Dim miStaticMethodInfo() As MethodInfo = _ tType.GetMethods(BindingFlags.Static Or _ BindingFlags.NonPublic Or BindingFlags.Public) printElements(miStaticMethodInfo) addCategory("Static Constructors") Dim ciStaticConstructorInfo() As ConstructorInfo = _ tType.GetConstructors(BindingFlags.Static Or _ BindingFlags.NonPublic Or BindingFlags.Public) printElements(ciStaticConstructorInfo) addCategory("Instance Fields") Dim fiFieldInfo() As FieldInfo = _ tType.GetFields(BindingFlags.Instance _ Or BindingFlags.NonPublic Or BindingFlags.Public) printElements(fiFieldInfo) addCategory("Instance Properties") Dim piPropertyInfo() As PropertyInfo = _ tType.GetProperties(BindingFlags.Instance _ Or BindingFlags.NonPublic Or BindingFlags.Public) printElements(piPropertyInfo) addCategory("Instance Events") Dim eiEventInfo() As EventInfo = _ tType.GetEvents(BindingFlags.Instance _ Or BindingFlags.NonPublic Or BindingFlags.Public) printElements(eiEventInfo) addCategory("Instance Methods") Dim miMethodInfo() As MethodInfo = _ tType.GetMethods(BindingFlags.Instance Or _ BindingFlags.NonPublic Or BindingFlags.Public) printElements(miMethodInfo) End Sub Private Sub printElements(ByVal miElements() As MemberInfo) Dim miElement As MemberInfo For Each miElement In miElements tnNode = New TreeNode() tnNode.Text = Convert.ToString(miElement) tvInternals.SelectedNode.Nodes.Add(tnNode) Next End Sub Private Sub addCategory(ByVal sTitle As String) 'A string is passed in and a node is added to the tree tnNode = New TreeNode() tnNode.Text = sTitle With tvInternals .SelectedNode = tnRootNode .SelectedNode.Nodes.Add(tnNode) .SelectedNode = tnNode End With End Sub Private Sub MenuItem2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuItem2.Click  With ofdAssemblies 'The InitialDirectory property of the dialog box references 'the build on my machine. Your build number will be different. .InitialDirectory = _ "C:\WINNT\Microsoft.NET\Framework\v<your version number>\" .Filter = "Assemblies (*.dll *.exe)|*.dll; *.exe" .FilterIndex = 1 .RestoreDirectory = True If .ShowDialog = DialogResult.OK Then sAssemblyLocation = .FileName lbNamespaces.Items.Clear() tvInternals.Nodes.Clear() loadAssembly(sAssemblyLocation) End If End WithEnd Sub Private Sub MenuItem3_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuItem3.Click  Dim sAssemblyInfo As New StringBuilder() With sAssemblyInfo .Append("Assembly Information" & _ CtrlChrs.CrLf & CtrlChrs.CrLf) .Append("Full Name: " & _ aAssembly.FullName & CtrlChrs.CrLf) .Append("Location: " & _ aAssembly.Location & CtrlChrs.CrLf) End With MessageBox.Show(sAssemblyInfo.ToString, progName, _ MessageBoxButtons.OK, MessageBoxIcon.Information)End Sub Private Sub MenuItem5_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuItem5.Click Application.Exit() End Sub Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Me.Text = progName Me.MenuItem3.Enabled = False End Sub End Class End Namespace

Examining the Code

We first import the assemblies needed in the program. The Reflection classes are the meat and potato classes for this program. We'll dynamically build a string that will contain the summary information about the assembly being viewed, so a StringBuilder object is just what's needed. These objects live in the System.Text namespace, so we include an Imports directive for it. Finally, we use our little imports alias trick to reference control characters, using the alias CtrlChrs instead of the fully qualified name of the namespace. We also want to add a namespace to our program even though it has only a single class. We add the namespace to illustrate how namespaces are displayed in our program.

Imports System.Reflection Imports System Imports System.Text Imports CtrlChrs = Microsoft.VisualBasic.ControlChars Namespace AssemblyView Public Class AssemblySpyForm Inherits System.Windows.Forms.Form

When you use a tree view control, you actually have to add a node to the tree for each item. Many beginning Visual Basic programmers find the tree view control complicated because of this extra piece. A node is simply an object that is instantiated from the TreeNode class. Once instantiated, the node can then contain various items such as the text to display in the tree view, the font to use for the text, an image to render, a background color, and so on. The Node object is also aware of its parents, siblings, and any child nodes it might have. It knows whether it's visible and other aspects of its state. Each node is really a class that has several properties and events, such as expanding and collapsing, that give it quite a bit of functionality. We add the node to the Nodes collection of the tree view control. A TreeNode object can't exist independently of a tree view control. Because we'll be displaying our program's name in the title bar of the form and in the message box with an assembly's summary information, dimensioning the title as a constant makes sense. When our program loads the assembly in question, its location on disk has to be known. We store that in the sAssemblyLocation string variable. Finally, we dimension a variable aAssembly of type Assembly. Be sure to add the square brackets ([ ]) around the keyword Assembly.

Each of these variables (and the constant) is declared with class-level scope. Because they are declared at the top of the class, they can be seen and referenced anywhere within our class.

Dim tnNode As TreeNode Dim tnRootNode As TreeNode Const progName As String = "Assembly Spy" Dim sAssemblyLocation As String = "" Dim aAssembly As [Assembly]

Let's examine the rest of the code in the sequence in which it will be called.

When the form is loaded, we set the Text property of the form to the name of the program. Next we disable MenuItem3, which permits the user to select the Assembly Information command. Of course, there is no assembly loaded yet, so this is another example of defensive programming. If the command does not make sense in this context, don't give it to the user.

Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Me.Text = progName Me.MenuItem3.Enabled = False End Sub

When the form is displayed, the user can use the menu to bring up the Open File dialog box. Let's see how that's accomplished. The OpenFileDialog control has several properties that we can set to customize it for our application. For the sake of simplicity, we set the InitialDirectory property of the ofdAssemblies dialog box to the directory on my machine where the development assemblies are stored. Because we want only DLLs or EXEs to be displayed, we can set the Filter property to return these types of files. To add this detail, we simply add a text string. We first add the types of files we want displayed in the Files Of Type box. The user thus knows which types of files will be displayed. Then a bar character (|) is added as a delimiter to separate the types of files we want to display. Notice that a semicolon appears after each specific type of file. Next we set the FilterIndex property to 1. This property gets or sets the current filename filter string, which determines the choices that appear in the Files Of Type list in the dialog box. We have only one selection, but we could easily have added more specific types of files, such as text files, Microsoft Word files, or all files. Setting the RestoreDirectory property to True sets a value indicating whether the dialog box restores the current directory before closing. Because we will usually use this directory for assemblies, setting this property reduces some mouse clicks if we have to navigate back.

The ShowDialog method displays the dialog box modally. We check to see whether the return value is DialogResult.OK. This value tells us that a valid file has been returned. If the result is any other value, we bypass the rest of the If statement and the operation is canceled.

If the user picks a valid file (which may or may not be an assembly), the class-level string variable sAssemblyLocation is set to the FileName property of the dialog box. To be sure neither the list box nor the tree view contains any information from a previous operation, we simply clear both controls. Finally, we call the procedure loadAssembly with the fully qualified name of the assembly we want to open.

Private Sub MenuItem2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuItem2.Click With ofdAssemblies .InitialDirectory = _ "C:\WINNT\Microsoft.NET\Framework\v<your version number>\" .Filter = "Assemblies (*.dll *.exe)|*.dll; *.exe" .FilterIndex = 1 .RestoreDirectory = True If .ShowDialog = DialogResult.OK Then sAssemblyLocation = .FileName lbNamespaces.Items.Clear() tvInternals.Nodes.Clear() loadAssembly(sAssemblyLocation) End If End With End Sub

We'll try to open the assembly here. I say try because the user could have easily selected an EXE or a DLL that happens not to be an assembly. The protected block of code in the Try construct will be executed and, if a file that's not an assembly is loaded, an exception will be thrown.

Setting the form's Text property to the name of the assembly ensures that the user knows which file is open. Then we try to load an assembly using the [Assembly].LoadFrom method in our class-level variable aAssembly, which is of type Assembly.

At this point, things could go south quickly if the user has selected an executable by mistake. If an exception is thrown, our code jumps to the unfiltered handler, Catch. Because we didn't use a filtered Catch construct, our handler will catch any and all errors. Here we simply catch the exception, ensure the user can't select the Assembly Information menu option, and display a message box stating that the selection was not an assembly. You can see an example of our friendly message in Figure 8-12.

Figure 8-12

If a user of Assembly Spy selects a file that isn't an assembly, the user will see a message like this one.

If a valid assembly was loaded, it is assigned to our reference aAssembly variable of type Assembly. Because we want to extract all the types from the assembly, we access the aAssembly.GetTypes method, which returns an array of Types. We pass that array of types as a parameter to the updateModuleTypes routine.

Private Sub loadAssembly(ByVal sAssemblyLocation As String) Dim tType As Type Try Me.Text = "Assembly: " & sAssemblyLocation aAssembly = [Assembly].LoadFrom(sAssemblyLocation) updateModuleTypes(aAssembly.GetTypes) Me.MenuItem3.Enabled = True Catch Me.MenuItem3.Enabled = False MessageBox.Show(sAssemblyLocation & _ " is not an assembly. ", progName, _ MessageBoxButtons.OK, MessageBoxIcon.Information) sAssemblyLocation = "" End Try End Sub

In this code, we pass an array of type Type to updateModules from the loaded assembly that contains objects for all the types defined in this assembly. As I mentioned, a type represents type declarations: class types, interface types, array types, value types, and enumeration types. Type is the root of all reflection operations and the object that represents a type inside the system. Type is an abstract base class that allows multiple implementations.

We now populate the left side of our form with the namespaces contained within the loaded assembly. Because some assemblies—System.dll is one of them—have quite a few namespaces, we set the Sorted property of the list box to True, so there can be some overhead when the list box is populated. If an assembly contains a lot of namespaces, updating the list box in a sorted order can be a bit costly in CPU cycles. To help speed things up, use the BeginUpdate and EndUpdate methods of the list box. These methods enable a programmer to add a large number of items without the control being repainted each time an item is added to the list. And because populating this control might take a second or two, don't forget to set the cursor to an hourglass to provide a visual clue that the program is busy. Again, it's attention to detail that gives comfort to the users.

Next we iterate through the tTypes collection of Types passed in to the subroutine. If the type is public, we add its full name to the list box. Otherwise, we will get many private types that we don't have access to. When all the namespaces have been added, we call EndUpdate to refresh the list box and reset the cursor.

Private Sub updateModuleTypes(ByRef tTypes() As Type) Dim tType As Type lbNamespaces.BeginUpdate() Cursor.Current = System.Windows.Forms.Cursors.WaitCursor For Each tType In tTypes If tType.IsPublic Then lbNamespaces.Items.Add(tType.FullName) End If Next lbNamespaces.EndUpdate() Cursor.Current = System.Windows.Forms.Cursors.Default End Sub

At this point, the namespaces in the assembly are loaded into the list box. Because they're sorted, finding a specific namespace is easy. You could easily extend this program by adding a Find dialog box. You could also easily add the built-in InPutBox and have the user enter a partial namespace, such as Clipboard. When the user clicks OK in the input box, simply use the FindString or FindStringExact method of the list box to search for a namespace in the list that contains a specific search string.

If we want to take a look at the namespaces located in System.Windows.Forms, we select that assembly from our common dialog box. Notice in Figure 8-13 that the name of the assembly is displayed in the form's caption area.

Figure 8-13

Examining the namespace System.Windows.Forms in the Assembly Spy program.

Now the user can either use the menu to view the summary information about the assembly or click a namespace to view its contents. Remember that when the assembly was successfully loaded, we enabled MenuItem3, which permits the user to view assembly information. Gathering this information is simply a matter of reading a few properties from our aAssembly variable. Because we build a string dynamically, now is a good time to use our friend the StringBuilder.

Private Sub MenuItem3_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuItem3.Click Dim sAssemblyInfo As New StringBuilder() With sAssemblyInfo .Append("Assembly Information" & _ CtrlChrs.CrLf & CtrlChrs.CrLf) .Append("Full Name: " & _ aAssembly.FullName & CtrlChrs.CrLf) .Append("Location: " & _ aAssembly.Location & CtrlChrs.CrLf) End With MessageBox.Show(sAssemblyInfo.ToString, progName, _ MessageBoxButtons.OK, MessageBoxIcon.Information) End Sub

A message box is a good device for displaying this sort of information, as you can see in Figure 8-14.

Figure 8-14

The summary information produced by Assembly Spy.

To display the internal types of any of the namespaces, a user can simply click the list box. Of course, the SelectedIndexChanged event fires when this occurs, and we can place our code in the event handler to add the components of the namespace to the tree view on the right.

Using the GetType method of our loaded assembly, we can pass in the name of the namespace the user selected and assign it to our local variable tType of Type. The System.Type class is central to reflection. The common language runtime creates the Type object for a loaded type when the Reflection class requests it. We can then use the Type object's methods, fields, properties, and nested classes to find out everything about that type.

The first item we want to add to the root of the tree view is the name of the namespace class currently being viewed. To make this addition, we create a new TreeNode object called tnRootNode so that we can distinguish it from the other child nodes. We can assign the name property of the tType variable (which is the namespace) to the Text property of the new node.

Private Sub lbNamespaces_SelectedIndexChanged( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles lbNamespaces.SelectedIndexChanged Dim tType As Type = _ aAssembly.GetType(lbNamespaces.SelectedItem) tnRootNode = New TreeNode() tnRootNode.Text = "Class: " & tType.Name

Because this is a new namespace, let's clear out any nodes that might have been in the tree previously. Then we add the root node we just created, tnRootNode, to the Nodes collection of the tree view. Finally, we make that node the selected node. If you have used the tree view control in previous versions of Visual Basic, you need to know that the way nodes are added in Visual Basic .NET is fundamentally different. In classic Visual Basic, you pass in several parameters to designate where the node should be placed, whether it is a child or sibling, the parent node, and so forth. In Visual Basic .NET, when you want to add a child node, you simply make the parent the selected node. You can then add child nodes to the SelectedNode.Nodes collection.

With tvInternals .Nodes.Clear() .Nodes.Add(tnRootNode) .SelectedNode = tnRootNode End With

Adding the Categories to the Tree View

We want to add all of the constructors, fields, properties, events, and methods of the loaded assembly to the tree view in an ordered and structured way. To streamline the process, we add two helper functions, addCategory and printElements, that can be called for each category. Let's examine the first items we'll add—the constructors in the namespace.

After we add the title "Constructors," the constructor information is retrieved. ConstructorInfo is used to discover the attributes of a constructor by returning an array of ConstructorInfo information. Notice that we dimension our ciConstructorInfo variable as an array of ConstructorInfo objects. Using the GetConstructors method on our variable tType, which contains a reference to the namespace that we're examining, we retrieve the constructor information. Our code uses the information gathering methods of the Reflection class along with BindingFlags. BindingFlags permits us to list the members (constructors, fields, properties, events, and methods) of the specified class, dividing the members into static and instance categories. By putting them together with an Or operation, we can retrieve the static constructors that are both nonpublic and public.

'-- Add Categories and Elements -- addCategory("Constructors") Dim ciConstructorInfo() As ConstructorInfo = _ tType.GetConstructors(BindingFlags.Static Or _ BindingFlags.NonPublic Or BindingFlags.Public) printElements(ciConstructorInfo)

Our program repeats this approach for each category by retrieving the FieldInfo, PropertyInfo, EventInfo, and MethodInfo properties. Notice the BindingFlags.Static flag. This flag ensures that we retrieve only the static members for this call. After each of the static members is added, we add the instance members. The BindingFlags flags for these are Instance.

addCategory("Instance Fields") Dim fiFieldInfo() As FieldInfo = _ tType.GetFields(BindingFlags.Instance _ Or BindingFlags.NonPublic Or BindingFlags.Public) printElements(fiFieldInfo)

The Helper Procedures

For each category we want to add to the tree view, we pass in a string with a value of something like "Constructors". In the addCategory subroutine, a new TreeNode object is created and its Text property is set to the sTitle string variable passed in as a parameter. Remember that when we added the namespace to the tree view, we created a special node called tnRootNode that has class scope. Because we want to add each category as a child under the root node, we assign tnRootNode as the value of SelectedNode for the tree view. We can now simply add our new tnNode to the SelectedNode.Nodes collection. I like this much simpler approach to adding a child node.

Of course, the next step is to add the various elements, such as any constructors, under that category. Therefore, we set the newly added node as the SelectedNode. We can now add the constructors to the SelectedNode.Nodes collection, which will place the actual constructor information under the Constructor category we just added.

Private Sub addCategory(ByVal sTitle As String) ' A string is passed in and a node is added to the tree tnNode = New TreeNode() tnNode.Text = sTitle With tvInternals .SelectedNode = tnRootNode .SelectedNode.Nodes.Add(tnNode) .SelectedNode = tnNode End With End Sub

If we add a category for static constructors, for example, the next line retrieves the constructors and places them in the ciConstructorInfo array of ConstructorInfo types.

Dim ciConstructorInfo() As ConstructorInfo = _     tType.GetConstructors(BindingFlags.Static Or _     BindingFlags.NonPublic Or BindingFlags.Public)

Then the array is passed to the helper subroutine printElements as a parameter:

printElements(ciConstructorInfo)

The array is one of the generic MemberInfo objects describing each of the members of the current type. We can set the parameter of the signature of printElements to accept an array of MemberInfo objects. The MemberInfo class is the abstract base class of the classes used to obtain information about all members of a class (constructors, events, fields, methods, and properties). We can use this class to generically accept arrays of constructors, events, fields, methods, and properties.

Conceptually this subroutine is similar in structure to the addCategory procedure. However, this time we want to iterate through each of the items in the array, add its description to a node, and insert it under the appropriate category. We dimension a variable of type MemberInfo so that we can hold each item in turn and display it.

For each miElement in the miElements array, a new TreeNode object is instantiated. Because we want to display the entire contents of the description, we must use the Convert.ToString method on each miElement so that it can be displayed. And remember that in the addCategory subroutine, we made the current category (in this case "Constructors") the currently selected node. So now it's just a matter of adding each of the elements to the SelectedNode.Nodes collection, and they become children of the current category.

Private Sub printElements(ByVal miElements() As MemberInfo) Dim miElement As MemberInfo For Each miElement In miElements tnNode = New TreeNode() tnNode.Text = Convert.ToString(miElement) tvInternals.SelectedNode.Nodes.Add(tnNode) Next End Sub



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