Search code examples
.netvb.netwinformsreflectionsystem.reflection

List (top-level) declared variables in a Windows Form


I can easily list all controls in a Form, after creating an instance of it.

Is there any mechanism to list all declared variables or such objects?
Perhaps I shall call it declarations. Only top-level declarations are enough.

Let's assume we have MyForm Form with such top-level declarations:

Dim Town as String
Dim ZIP as String
Dim StreetName as String
Dim StreetNo as String
Public dtCountries as DataTable
Public LstCities as List(Of String)
...

Pseudo-code example:

Dim MyForm as New MyForm          ' create instance of the form
Dim dtVariables as New Datatable  ' create a datatable to store found objects
dtVariables.Columns.Add("ID", GetTy(Int32))
dtVariables.Columns.Add("VariableName", GetTy(String))
dtVariables.Columns.Add("VariableType", GetTy(String))

For Each Varbl In MyForm.***variables***   ' <<< (how) to read all variables
    Dim nr as Datarow = dtVariables.NewRow
    nr("ID") = dtVariables.Rows.Count + 1
    nr("VariableName") = Varbl.Name
    nr("VariableType") = Varbl.GetType.ToString.Replace("System.Windows.Forms.", "")
    dtVariables.Rows.Add(nr)       ' add found object/variable to our datatable
Next

The result I am looking for is something like:

 1   Town         String
 2   ZIP          String
 3   StreetName   String
 4   StreetNo     Int32
 5   dtCountries  DataTable
 6   LstCities    List(Of String)
 ... ...          ...

I know that I can read MyForm.designer.vb file and look there for declarations.
This question is about getting it from an object model of a Form / instance of a Form.


Solution

  • An example using a filtered collection of FieldInfo objects returned by Type.GetType().GetFields()

    Since you want this method to return both Public and non-Public Fields, the collection must be filtered because, since this is a Form class, it will include all the controls a Form contains.
    The collection of FieldInfo is then filtered using FieldType.Namespace, where the Namespace is not System.Windows.Forms.

    The BindingFlags are set to Instance | Public | NonPublic | DeclaredOnly.

    When the Field represents a Collection (List, Dictionary etc.), the Type.GenericTypeArguments property needs to be parsed to extract the arguments Collection.

    I'm using a couple of helper functions to clean up the Fields Name and to retrieve the collection of arguments as a formatted string.

    Using the sample Fields you posted (I added a Dictionary to test the output):

    Dim Town As String
    Dim ZIP As String
    Dim StreetName As String
    Dim StreetNo As String
    Public dtCountries As DataTable
    Public LstCities As List(Of String)
    Public DictOfControls As Dictionary(Of String, Control)
    

    this is the result:

    Class GetType GetFields

    Dim ClassFields As New DataTable
    ClassFields.Columns.Add("ID", GetType(Integer))
    ClassFields.Columns.Add("Name", GetType(String))
    ClassFields.Columns.Add("FieldType", GetType(String))
    
    Dim flags As BindingFlags = BindingFlags.Instance Or
                 BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.DeclaredOnly
    
    Dim allFields As List(Of FieldInfo) =
        Me.GetType().GetFields(flags).
            Where(Function(f) (Not (f.FieldType.Namespace.Equals("System.Windows.Forms"))) AndAlso f.Name <> "components").
            ToList()
    
    For Each field As FieldInfo In allFields
        Dim dr As DataRow = ClassFields.NewRow
        dr("ID") = ClassFields.Rows.Count + 1
        dr("Name") = field.Name
        dr("FieldType") = GetFieldTypeName(field.FieldType.Name) &
                          GetTypeArguments(field.FieldType.GenericTypeArguments)
        ClassFields.Rows.Add(dr)
    Next
    
    Private Function GetFieldTypeName(field As String) As String
        Dim EndPosition As Integer = field.IndexOf(ChrW(96))
        Return If(EndPosition > 0, field.Substring(0, EndPosition), field)
    End Function
    
    Private Function GetTypeArguments(args As Type()) As String
        If args.Length = 0 Then Return String.Empty
        Return $" ({String.Join(", ", args.Select(Function(arg) arg.Name))})"
    End Function
    

    If Interpolated String is not available (before VB.Net version 14), use a Composite Format string:

    Return $" ({String.Join(", ", args.Select(Function(arg) arg.Name))})"
    

    can be expressed as:

    Return String.Format(" ({0})", String.Join(", ", args.Select(Function(arg) arg.Name)))