user6439507
user6439507

Reputation:

Adding a Form as a property to a user control with Designer support

I have created a custom label. It has a property of type Form, which the user can select the form in the designer and then when the label is clicked that form is loaded. It works fine but the only form in the properties dropdown list is the one that the label is on.

Is there a way to show all possible forms or perhaps have the user pass the form as a string and then convert that to be a form.

Here is the code for my label:


Public Class BMLabel
    Private t As String
    Private id As String
    Private frm As New Form

    <Category("BM")>
    Public Property Type As String
        Get
            Return t
        End Get
        Set(value As String)
            t = value
        End Set
    End Property

    Public Property Identifier As String
        Get
            Return id
        End Get
        Set(value As String)
            id = value
        End Set
    End Property

    Public Property Form As Form
        Get
            Return frm
        End Get
        Set(value As Form)
            frm = value
        End Set
    End Property

    Private Sub BMLabel_Click(sender As Object, e As EventArgs) Handles Me.Click
        Dim t = frm.GetType()

        Dim form As Form = DirectCast(Activator.CreateInstance(t), Form)
        form.ShowDialog()
    End Sub
End Class

Upvotes: 2

Views: 356

Answers (2)

Reza Aghaei
Reza Aghaei

Reputation: 125197

There are two services that can help you at design-time to discover and resolve all types in the solution:

In the other hand, to show standard values in a dropdown in property editor, you can create a TypeConverter:

  • TypeConverter: Provides a unified way of converting types of values to other types, as well as for accessing standard values and subproperties.

Knowing about above options, you can create a custom type converter to discover all form types in the project and list in the dropdown.

Example

In the following example, I've created a custom button class which allows you to select a form type in design type and then at run-time, if you click on the button it shows the selected form as dialog:

enter image description here

To see a C# version for this answer see this post.

MyButton

Imports System.ComponentModel
Public Class MyButton
    Inherits Button
    <TypeConverter(GetType(FormTypeConverter))>
    Public Property Form As Type
    Protected Overrides Sub OnClick(ByVal e As EventArgs)
        MyBase.OnClick(e)
        If Form IsNot Nothing AndAlso GetType(Form).IsAssignableFrom(Form) Then

            Using f = CType(Activator.CreateInstance(Form), Form)
                f.ShowDialog()
            End Using
        End If
    End Sub
End Class

FormTypeConverter

Imports System.ComponentModel
Imports System.ComponentModel.Design
Imports System.Globalization

Public Class FormTypeConverter
    Inherits TypeConverter
    Public Overrides Function GetStandardValuesExclusive(ByVal context As ITypeDescriptorContext) As Boolean
        Return True
    End Function
    Public Overrides Function CanConvertTo(ByVal pContext As ITypeDescriptorContext, ByVal pDestinationType As Type) As Boolean
        Return MyBase.CanConvertTo(pContext, pDestinationType)
    End Function
    Public Overrides Function ConvertTo(ByVal pContext As ITypeDescriptorContext, ByVal pCulture As CultureInfo, ByVal pValue As Object, ByVal pDestinationType As Type) As Object
        Return MyBase.ConvertTo(pContext, pCulture, pValue, pDestinationType)
    End Function
    Public Overrides Function CanConvertFrom(ByVal pContext As ITypeDescriptorContext, ByVal pSourceType As Type) As Boolean
        If pSourceType = GetType(String) Then Return True
        Return MyBase.CanConvertFrom(pContext, pSourceType)
    End Function
    Public Overrides Function ConvertFrom(ByVal pContext As ITypeDescriptorContext, ByVal pCulture As CultureInfo, ByVal pValue As Object) As Object
        If TypeOf pValue Is String Then Return GetTypeFromName(pContext, CStr(pValue))
        Return MyBase.ConvertFrom(pContext, pCulture, pValue)
    End Function
    Public Overrides Function GetStandardValuesSupported(ByVal pContext As ITypeDescriptorContext) As Boolean
        Return True
    End Function
    Public Overrides Function GetStandardValues(ByVal pContext As ITypeDescriptorContext) As StandardValuesCollection
        Dim types As List(Of Type) = GetProjectTypes(pContext)
        Dim values As List(Of String) = New List(Of String)()
        For Each type As Type In types
            values.Add(type.FullName)
        Next
        values.Sort()
        Return New StandardValuesCollection(values)
    End Function
    Private Function GetProjectTypes(ByVal serviceProvider As IServiceProvider) As List(Of Type)
        Dim typeDiscoverySvc = CType(serviceProvider.GetService(GetType(ITypeDiscoveryService)), ITypeDiscoveryService)
        Dim types = typeDiscoverySvc.GetTypes(GetType(Object), True).Cast(Of Type)().Where(Function(item) item.IsPublic AndAlso GetType(Form).IsAssignableFrom(item) AndAlso Not item.FullName.StartsWith("System")).ToList()
        Return types
    End Function
    Private Function GetTypeFromName(ByVal serviceProvider As IServiceProvider, ByVal typeName As String) As Type
        Dim typeResolutionSvc As ITypeResolutionService = CType(serviceProvider.GetService(GetType(ITypeResolutionService)), ITypeResolutionService)
        Return typeResolutionSvc.[GetType](typeName)
    End Function
End Class

Upvotes: 1

41686d6564
41686d6564

Reputation: 19641

The problem is that when you declare a property of type Form, it allows you to select a form instance, not a type/class that inherits from Form. What you need to do instead is declare a property of type Type and prepare the editor which will allow you to select the desired type (limiting the options to the types that inherit from Form).

Disclaimer: The idea of creating a custom editor was inspired by this answer and the code was adapted for this particular situation.

So, here we go. The custom label class would look something like this:

Public Class BMLabel
    Inherits Label
    ' Don't forget to change the namespace.
    '        ↓↓↓↓↓↓↓↓↓↓↓
    <Editor("WindowsApp1.TypeSelector, System.Design", GetType(UITypeEditor)), Localizable(True)>
    Public Property FormType As Type

    Private Sub BMLabel_Click(sender As Object, e As EventArgs) Handles Me.Click
        Using frm As Form = DirectCast(Activator.CreateInstance(FormType), Form)
            frm.ShowDialog(Me)
        End Using
    End Sub
End Class

Now, we need to create the TypeSelector class:

Public Class TypeSelector
    Inherits UITypeEditor

    Public Overrides Function GetEditStyle(context As ITypeDescriptorContext) As UITypeEditorEditStyle
        If context Is Nothing OrElse context.Instance Is Nothing Then
            Return MyBase.GetEditStyle(context)
        End If

        Return UITypeEditorEditStyle.Modal
    End Function

    Public Overrides Function EditValue(context As ITypeDescriptorContext, provider As IServiceProvider, value As Object) As Object
        Dim editorService As IWindowsFormsEditorService

        If context Is Nothing OrElse context.Instance Is Nothing OrElse provider Is Nothing Then
            Return value
        End If

        editorService = DirectCast(provider.GetService(GetType(IWindowsFormsEditorService)),
                                   IWindowsFormsEditorService)

        Dim dlg As New FormTypeSelector()
        dlg.Value = DirectCast(value, Type)
        dlg.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen
        If editorService.ShowDialog(dlg) = System.Windows.Forms.DialogResult.OK Then
            Return dlg.Value
        End If
        Return value
    End Function
End Class

Then, we create a form called FormTypeSelector with a ComboBox or a ListBox to list the available options:

FormTypeSelector

Public Class FormTypeSelector
    Friend Property Value As Type

    Private Sub FormTypeSelector_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim availableFormTypes =
            System.Reflection.Assembly.GetExecutingAssembly().
                    GetTypes().
                    Where(Function(t) t.BaseType = GetType(Form) AndAlso t <> Me.GetType()).ToList()
        cboFormTypes.DisplayMember = "Name"
        cboFormTypes.DataSource = availableFormTypes
        cboFormTypes.SelectedItem = Value
    End Sub

    Private Sub BtnOK_Click(sender As Object, e As EventArgs) Handles btnOK.Click
        Value = DirectCast(cboFormTypes.SelectedItem, Type)
        DialogResult = DialogResult.OK
        Close()
    End Sub

    Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
        DialogResult = DialogResult.Cancel
        Close()
    End Sub
End Class

And that's it; it should be ready to go:

Demo

Note: You'll probably need to add an option in FormTypeSelector to allow clearing the selected value of the FormType property which should be easy enough to do.

Upvotes: 2

Related Questions