Reputation:
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
Reputation: 125197
There are two services that can help you at design-time to discover and resolve all types in the solution:
ITypeDiscoveryService
: Discovers available types at design time.
ITypeResolutionService
: Provides an interface to retrieve an assembly or type by name.
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:
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
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:
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:
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