dcsimg
Results 1 to 3 of 3

Thread: Referencing controls, variables etc. in a form known only by its name as a string

  1. #1

    Thread Starter
    Member
    Join Date
    Sep 2017
    Location
    UK, France, Belgium, Ireland
    Posts
    34

    Referencing controls, variables etc. in a form known only by its name as a string

    I will have a large number of forms in my application. I want to store their names in a database, and when I want to open one of them, I simply want to use the name of the form. It's not straightforward, but I've come across solutions like this and this and this, where you use this sort of construction:
    Code:
    Dim frmNewForm_Type As Type = Type.GetType("your_assembly.type_name")
    frmNewForm = CType(Activator.CreateInstance(frmNewForm_Type), Form)
    .

    Although this works as far as simply opening a form, I want to open the form with parameters, such as an ID number for a particular record. However, it seems you cannot continue the above with
    Code:
    frmNewForm.intID
    because the compiler rightly complains that intID isn't a know property of frmNewForm.

    In short, I want to create a procedure something like
    Code:
    Public Sub OpenFormInstance(strFormName As String, intID As Integer)
    , calling it like this:
    Code:
    OpenFormInstance("KittenForm", DataGridView.CurrentRow.Cells(0).Value)
    that will open a form and load up the data for a particular record.

    I have created a function with a huge "case" statement containing a large number of clauses like
    Code:
    Case "KittenForm"
                    Dim FormInstance As New KittenForm
                    FormInstance.intID = intID
                    FormInstance.MdiParent = MDI
                    FormInstance.Show()
    The above case statement works, but with a huge number of form, is obviously a maintenance nightmare, and also offends me.

    Any suggestions for a better solution?
    Last edited by ktrammen; Dec 5th, 2017 at 11:17 AM.

  2. #2
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    32,505

    Re: Referencing controls, variables etc. in a form known only by its name as a string

    Normally, you'd use the constructor, but that seems excluded in this case. Would it be acceptable to have one more line? For example, you could have a method called something like Initialize, which took whatever parameters you needed, and simply call that immediately after you create the form.
    My usual boring signature: Nothing

  3. #3
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,580

    Re: Referencing controls, variables etc. in a form known only by its name as a string

    It is wrong to say you can't call a constructor with parameters via Activator.CreateInstance(). There are a lot of overloads, but for example this overload takes a list of arguments and will try to find a constructor that matches.

    The trick is, if all of your forms have different constructors, and you need to get different things from the database based on the constructor, you're going to have to write some extra code per-type to figure out what to do. And if you go that far, you may as well just start manually calling the constructor.

    Setting properties in a general manner is harder. Don't do it. For example, look at your KittenForm. It needs an "International National Tree" ID in order to be properly constructed (Or at least, that's what I think 'int' stands for, clearly if it were just an "ID" you'd have called it that, because there's no other sensible type for an ID.) What you need is something that us architecture types call a "Factory Method". Its job is to construct other types. We have a lot of time-tested techniques for handling how we deal with these, and our toolkit got a lot more broad in C# 7. Unfortunately, VB developers have historically attacked Microsoft when new features arrive, so the tools most relevant to your problem aren't even on the VB roadmap. Womp womp.

    So there's two scenarios.

    One is "I need to construct forms I didn't even write". That involves a lot more thinking and I'm not going to touch it unless you say that's precisely what you need.

    The easier case is, "I need to construct forms that I wrote and are in assemblies I'm directly referencing." We can work with this case. If you're used to working with Interfaces, you can do a lot more with it. If you're not, you have to work with slightly more primitive tools. I'm going to stick to "not comfortable with Interfaces" because it's less explanation and easier to describe if I already know some specific things.

    What you do in this case is go ahead and make a type for the things that the form needs. So you might make this:
    Code:
    Public Class KittenFormParameters
        Public ReadOnly Property Id As Integer
    
        Public Sub New(ByVal id As Integer)
            Me.Id = id
        End Sub
    End Class
    Now we need a type that can take a string name or some other token, and associate that name with a function that can create the appropriate type based on that input. Clear case for a Dictionary, but we need a way to say "a function that can create a Form given something that represents its parameters". That's a Dictionary(Of String, Func(Of Object, Form)) when we're using primitive tools. That is, "A class that associates a String with a Function that takes one Object parameter and returns a Form."

    If we give a function like that the KittenFormParameters, it can cast to the right type, create our KittenForm, pass it the right parameters, then return it. Or: if we make KittenForm have this:
    Code:
    Public Sub New(ByVal params As KittenFormParameters)
    We could do even less work. Let's do the "more work" way for illustration, as it's more flexible.

    So we could end up with a class like this:
    Code:
    Public Class FormFactory
    
        Private _creatorLookup As New Dictionary(Of String, Object)()
    
        Public Sub New()
            _creatorLookup("KittenForm") = AddressOf CreateKittenForm
        End Sub
    
        Public Function GetForm(ByVal formToken As String, ByVal params As Object) As Form
            Dim creator As Func(Of String, Object)
            If _creatorLookup.TryGetValue(formToken, creator) Then
                Return creator(params)
            Else
                ' Error: the string you got isn't a known form.
            End If
        End Function
    
        Private Function CreateKittenForm(ByVal params As Object) As Form
            Dim castParams = DirectCast(params, KittenFormParameters)
            Return New KittenForm(castParams)
        End Sub
    
    End Class
    Is it tedious if you have "many forms"? Yes. That's why interfaces are a good tool. We could also write this class, if you wrote the constructor (Sub New) that I discussed above:

    Code:
    Public Class FormFactory
    
        Private _formTypeLookup As New Dictionary(Of String, Type)()
    
        Public Sub New()
            _formTypeLookup("KittenForm") = GetType(KittenForm)
        End Sub
    
        Public Function GetForm(ByVal formToken As String, ByVal params As Object) As Form
            Dim formType As Func(Of String, Type)
            If _formTypeLookup.TryGetValue(formToken, formType) Then
                Dim result = Activator.CreateInstance(formType, params)
                Return CType(result, Form)            
            Else
                ' Error: the string you got isn't a known form.
            End If
        End Function
    
    End Class
    There are ways to make that fancier and still do specific things for each form, but they tend to be things that only make sense in the context of their specific problem. The gist is always:
    • If you can pick some "common ancestor" for the kinds of Forms you want to work with, that lets you treat many different Forms as "the same type".
    • Interfaces can be "a common ancestor" and are most convenient because any class can implement multiples.
    • Form is always a common ancestor for Forms.
    • You can cast from "a common ancestor" to any type you want, but should only do so within the context of a method that is specific for that form.

    The last point there is one I could bicker with people about, but I think when we work in code like this everything works better if the purpose of our code is to get more and more specific until there's no reason to expect a cast to fail as opposed to "a giant Select..Case". Pulling that off takes very careful thought.

    That's why it's generally "better" to not write code in such a generic manner. It's much harder to deal with this problem.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Featured


Click Here to Expand Forum to Full Width