Reputation: 331
I'm working in a winform on .Net who generates controls automatically based on a model Class using the System.Reflection like this:
Imports System.Reflection
Public Class DynamicForm(Of Model As {IModelBase, Class, New})
Protected Sub InitializeComponent()
'A lot of not relevant code here...
For Each prop As PropertyInfo In GetType(Model).GetProperties
If Not HasControl(prop) Then Continue For
Dim control = CreateControl(prop)
Dim label = CreateLabel(prop)
SetLocation(label, control)
Me.Controls.Add(control)
Me.Controls.Add(label)
ResizeWindow(control.Height)
Next
End Sub
End Class
Of course a model can have a lot of properties who I don't want to be edited directly by the user so I go with the solution to use attributes to mark which properties you want to display like this:
Public Class HasControl
Inherits Attribute
Public Property Label As String
Public Sub New(label As String)
Me.Label = label
End Sub
End Class
And then my model look like:
Public Class Clients
Implements IModelBase
<HasControl("First name:")>
Public Property FirstName As String
Public Property CreatedTime As Date
End Class
Now, I was using the IModelBase interface just only for ensure no any class can be used in this windows but I recently start to use for something else. Many of my models are like Clients, Workers, Sellers, Users, etc. Those models has many similar properties so I make an interface with this properties:
Public Interface IHumanData
Inherits IModelBase
<HasControl("First name:")>
Public Property FirstName As String
<HasControl("Last name:")>
Public Property LastName As String
End Interface
Public Class Seller
Implements IHumanData
Public Property FirstName As String Implements IHumanData.FirstName
Public Property LastName As String Implements IHumanData.LastName
End Class
The problem with this is the "HasControl" attribute is not assigned to my Seller's properties. I know if I use inheritance instead of interface implementation this actually works, but inheritance has a limitation when I want to create something like this:
Public Class Worker
Implements IHumanData, IDateStorageData, ILocationData, ISortOfRandomData
' A bunch of properties auto generated by VisualStudio
End Class
So, there is actually an easy way to pass an Attribute through an interface implementation?
Upvotes: 0
Views: 456
Reputation: 23214
You should query the interface
definitions for the properties implementing the HasControl
attribute
.
To do so, you retrieve the interfaces
implemented by the object instance being used in the form.
The routine to build the form can look like below.
Sub buildForm(model As IModelBase)
Dim interfaceTypes As Type() = model.GetType().FindInterfaces(New TypeFilter(Function(t, c) True), Nothing)
For Each interfaceType As Type In interfaceTypes
Dim properties As PropertyInfo() = interfaceType.GetProperties()
For Each prop As PropertyInfo In properties
Dim attributes As IEnumerable(Of HasControlAttribute) = prop.GetCustomAttributes(Of HasControlAttribute)()
For Each attribute As HasControlAttribute In attributes
Console.Write(attribute.Name)
Console.Write(": ")
Console.WriteLine(CStr(prop.GetValue(model, Nothing)))
' Build label and control here ...
Next
Next
Next
End Sub
You can also retrieve these interfaces
from the generic type parameter (in your form Model
).
Dim interfaceTypes As Type() = GetType(Model).FindInterfaces(New TypeFilter(Function(t, c) True), Nothing)
Call it by passing it the object instance being used in your form, eg. a Worker
instance.
Dim worker As Worker = New Worker()
worker.FirstName = "John"
worker.LastName = "Doe"
worker.City = "Brussels"
worker.Age = 40
buildForm(worker)
Which results into only the marked properties being considered.
Notice that the property Age
(as example defined on Worker
doesn't appear.
First name: John
Last name: Doe
City: Brussels
IModelBase
Public Interface IModelBase
' ...
End Interface
HasControlAttribute
Public Class HasControlAttribute
Inherits Attribute
Public Sub New(ByVal name As String)
Me.Name = name
End Sub
Public Property Name As String
End Class
IHumanData
Public Interface IHumanData
Inherits IModelBase
<HasControl("First name")>
Property FirstName As String
<HasControl("Last name")>
Property LastName As String
'...
End Interface
ILocationData
Public Interface ILocationData
<HasControl("City")>
Property City As String
' ...
End Interface
ISortOfRandomData
Public Interface ISortOfRandomData
'...
End Interface
Worker
Public Class Worker
Implements IHumanData, ILocationData
Public Property FirstName As String Implements IHumanData.FirstName
Public Property LastName As String Implements IHumanData.LastName
Public Property City As String Implements ILocationData.City
Public Property Age As Integer
' ...
End Class
UPDATE
As follow up to your comment.
If you want to include the properties defined on the Worker
class itself (which are not defined in any of the implemented interfaces) as shown below,
then the interfaceTypes
array needs to include the Type
of the object instance itself.
Dim interfaceTypes As IList(Of Type) = model.GetType().FindInterfaces(New TypeFilter(Function(t, c) True), Nothing).ToList()
interfaceTypes.Add(model.GetType)
Worker with property Age
marked with HasControl
Public Class Worker
Implements IHumanData, ILocationData
Public Property FirstName As String Implements IHumanData.FirstName
Public Property LastName As String Implements IHumanData.LastName
Public Property City As String Implements ILocationData.City
<HasControl("Age")>
Public Property Age As Integer
' ...
End Class
Upvotes: 1