vb.netwinforms

String form name to form reference


Basically, I am rewriting some code working for years. Over the time I have many (60+) references to forms - there's a menuitem with OnClick event for each form, where a form reference was created:

Private Sub SomeForm_Click(sender As Object, e As EventArgs) Handles MenuItemForSomeForm.Click
    NewTab("Some Form", New SomeForm, 0)
End Sub

...where first parameter is a name to put in a tabPage.Text where the form is opened, second is a new instance of the (particular) form SomeForm and 0 is a default record to display (0 means no default record).

Now, I created a dynamic menu and stored the form names in a database (due to better access control over the access rights, etc). Now, because the menu is generated at runtime, I can't have the OnClick event with separate instance definition of the form and have to create it at runtime, after the MenuItems are created. The side-effect idea was to cut the code short by using only 1 OnClick event or such with MenuItem.Tag paremeter as FormName. Something like:

Private Sub clickeventhandler(sender As Object, e As EventArgs)
Dim tsmi As ToolStripMenuItem = CType(sender, ToolStripMenuItem)
Dim newForm As New >>>FormFrom(tsmi.Tag.ToString)<<<  ' only explanation, this won't work
MainW.OpenModuleInTab(new newForm, tsmi.Tag.ToString, 0)

However I am failing to find a way to create form (instances) from this string reference. Reference through collection (i.e. List(of) or Dictionary) would be fine too, I believe.

The structure is obviously:

Object → Form → Form1 (class) → MyForm1 (instance)

I know I can create an object like this:

' Note that you are getting a NEW instance of MyClassA
Dim MyInstance As Object = Activator.CreateInstance(Type.GetType(NameOfMyClass))

I can re-type it to a Form type:

Dim NewForm as Form = CType(MyInstance,Form)

... to acccess some of the form properties like Width, TopLevel, etc., but that's about it. I can't do:

Dim NewForm1 as Form1 = CType(NewForm,Form1)

...because obviously, Form1 comes as a string "Form1".

I don't know how to create a Form1 reference from a "Form1" text (then it would be easy to create an instance) or how to create an instance directly (MyForm1).

SOLUTION

As sugested, I used reflection to get the form. The only way working for me I found was this:

    Dim T As Type = System.Type.GetType(FormName, False) 
    If T Is Nothing Then 'if not found prepend default namespace
        Dim Fullname As String = Application.ProductName & "." & FormName
        T = System.Type.GetType(Fullname, True, True)
    End If

    Dim f2 As New Form   ' here I am creating a form and working with it
    f2 = CType(Activator.CreateInstance(T), Form)
    f2.TopLevel = False
    f2.Name = FormName.Replace(" ", "") & Now.ToString("yyyyMMddmmhh")
    f2.FormBorderStyle = FormBorderStyle.None
    f2.Dock = DockStyle.Fill

I am using VB.net CallByName to set public variable and same function to run a sub method (every form contains RecordID variable and LoadRecords sub):

    CallByName(f2, "RecordID", CallType.Set, 111)
    CallByName(f2, "LoadRecords", CallType.Method, Nothing)

For testing purposes, I put following into the testing form:

Public RecordID As Int32
Public Sub LoadRecords()
    MsgBox("Load records!!!!" & vbCrLf & "RecordID = " & RecordID)
End Sub

Solution

  • So, let's go with the idea that I have an assembly called "WindowsApp2" and in that assembly I've defined Form1 and Form2. I've also created this module in the same assembly:

    Public Module Module1
    
        Public Function GetDoStuffWiths() As Dictionary(Of Type, System.Delegate)
            Dim DoStuffWiths As New Dictionary(Of Type, System.Delegate)()
            DoStuffWiths.Add(GetType(WindowsApp2.Form1), CType(Sub(f) WindowsApp2.Module1.DoStuffWithForm1(f), Action(Of WindowsApp2.Form1)))
            DoStuffWiths.Add(GetType(WindowsApp2.Form2), CType(Sub(f) WindowsApp2.Module1.DoStuffWithForm2(f), Action(Of WindowsApp2.Form2)))
            Return DoStuffWiths
        End Function
    
        Public Sub DoStuffWithForm1(form1 As Form1)
            form1.Text = "This is Form 1"
        End Sub
    
        Public Sub DoStuffWithForm2(form2 As Form2)
            form2.Text = "This is Form 2"
        End Sub
    
    End Module
    

    Now, in another assembly "ConsoleApp1" I write this:

    Sub Main()
    
        Dim DoStuffWiths As Dictionary(Of Type, System.Delegate) = WindowsApp2.Module1.GetDoStuffWiths()
    
        Dim formAssembly = System.Reflection.Assembly.Load("WindowsApp2")
        Dim typeOfForm = formAssembly.GetType("WindowsApp2.Form1")
    
        Dim form As Form = CType(Activator.CreateInstance(typeOfForm), Form)
    
        DoStuffWiths(typeOfForm).DynamicInvoke(form)
    
        Application.Run(form)
    
    End Sub
    

    When I run my console app I get a form popping up with the message "This is Form 1".

    If I change the line formAssembly.GetType("WindowsApp2.Form1") to formAssembly.GetType("WindowsApp2.Form2") then I get the message "Wow this is cool".

    That's how you can work with strongly typed objects that you dynamically instantiate.