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
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)
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"))
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)
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.