(Print this page)

Using Reflection to spy forms’ content
Published date: Thursday, September 1, 2005
On: Moer and Éric Moreau's web site

This month column is dedicated to a problem I had to solve lately. My client wanted me to extract some properties of all the forms of a solution that contains more then one assembly so he can see if there were not any typo errors in text of buttons and labels controls, menu items, and even tooltips. I discovered that I could not simply loop through controls of a form since everything on a form is not necessarily a control. Menus and tooltips are not!

What is Reflection?

The 'official' definition of reflection at MSDN (found in the .NET Framework Class Library documentation for the System.Reflection Namespace) is:
"The System.Reflection namespace contains classes and interfaces that provide a managed view of loaded types, methods, and fields, with the ability to dynamically create and invoke types."

A note about the demo code

Notice that I will not paste all the required code here. So please download the demo application to see it in action and also to see the remaining code.

Figure 1: The demo application

The demo application contains 2 assemblies (Demo and External). The demo assembly contains everything required to extract and manipulate controls’ properties. The external assembly only contains dummy forms and is only used to demonstrate that the extraction can also work for external assemblies.

All extracted properties have to be kept into a structure. I decided to use a datatable object. The first thing you need to when you start the application is to click the “Create datatable” button to create it.

The Extraction dialog

In order to insulate the extraction from the other features, I created a separate form for that specific purpose.

Figure 2: The extraction form

You have the choice of selecting the current assembly or the external dummy one. Once you made your selection, click the List button. The datatable will be populated and the listbox on the right of the dialog will be filled to show you the progress.

The main method of this dialog is called ProcessAssembly and it gets one argument: the assembly to spy. The method is just about a small loop that process each types found by reflection and is processing only objects that are of the form type (detected by the TypeIsAForm function):

For Each tType As Type In pAssembly.GetTypes()
    If TypeIsAForm(tType) Then
        DisplayStatus("Form " & tType.Name, 1)

        Dim oObject As Object = Activator.CreateInstance(tType)
        DisplayStatus("Text = " & CType(oObject, Form).Text, 3)
        UpdateDB(pAssembly.GetName.Name, tType.Name, "Me", _
              "Text", CType(oObject, Form).Text)

        Dim oForm As System.Windows.Forms.Form = _
              CType(oObject, System.Windows.Forms.Form)

        'Is there a tooltip control on the form?
        Dim strTooltipName As String
        Dim objToolTip As ToolTip = FindTooltip(oForm, strTooltipName)

        ListControls(pAssembly.GetName.Name, oForm.Name, _
             oForm.Controls, objToolTip, strTooltipName, 2)
    End If
Next tType
There are some methods that are called in this loop:
  • TypeIsAForm is used to detect if the type found by reflection is a form (because only forms are processed).
  • DisplayStatus displays progress into the listbox.
  • UpdateDB inserts form’s Text property into the datatable.
  • FindTooltip is used to detect if the form is hosting a tooltip component. If one is found, it will be used later when we will loop through controls.
  • ListControls is a recursive method (one that calls itself). It loops through controls collection received as an argument. The UpdateDB method is once again call to insert the control’s Text property into the datatable. If the control is found to be a container (using ocControl.Controls.Count property), ListControls is called recursively to loop through the contained controls. Finally, if the form has a tooltip component, the tooltip is extracted using the GetTooltip method.

You could modify the ListControls method to process properties other then the Text property into the datatable.

Processing menu items

I have soon discovered that menu items do not belong to the Form’s controls collection. I had to use Reflection once again to find everything having a MenuItem type. See the FindMenuItems method to see how it is done.

This method is called at the bottom of the loop in the ProcessAssembly method.

Processing an external assembly

The ProcessAssembly method is called like this for the current assembly:

ProcessAssembly(Reflection.Assembly.GetExecutingAssembly)
The ProcessAssembly method does not care of which assembly it is extracting information from (as long as it contains Windows Forms types).

If you want to process assemblies other then the current one, the only thing that is changing is that we have to load a different assembly like this:

ProcessAssembly(Reflection.Assembly.LoadFrom("External.dll"))
Because no path is specified, the same directory as the one from which you started the application (the bin folder when running from the IDE) is used.

Setting values back

I have also provided a way of viewing the global datatable content through a datagrid (using the View Datatable button). The grid is fully editable but you should be very cautious not to change anything but the Value column.

Take a minute and change some values of the fDemo form from the datagrid. Now click the “Affect new values” button to discover that your modifications are getting used.

The magic here occurs in the SetProperty method. A first method (AffectValues) is called on the button’s click event. It loops through all the data of a form of a specific assembly:

Dim strFilter As String = "Assembly = '" & pstrAssembly & _
           "' AND Form = '" & pForm.Name & "'"
For Each dr As DataRow In gdtControls.Select(strFilter)
For each row found into the table, the SetProperty method is called. This method is able to recreate a valid handle to the properties of the controls (or components) including tooltips and menu items and affect the new value to it.

Conclusion

There is a lot of customization you could do to this code. For example, you may want to extract a list of controls that do not have the tooltips set. You may also want to store the values in a repository somewhere (a database, a XML file, a flat file, …) in order to dynamically load some properties of your controls at runtime.

I hope you appreciated the topic and see you next month.


(Print this page)