Showing classes to user and instantiating selected type - vb.net

I'm starting to learn about Reflection in VB.NET, and I have a little example problem I'm working on to understand some concepts.
So I have one interface implemented by three classes:
Public Interface IVehicle
Sub SayType()
End Interface
Public Class Bike
Implements IVehicle
Public Sub SayType() Implements IVehicle.SayType
MsgBox("I'm a bike")
End Sub
End Class
Public Class Car
Implements IVehicle
Public Sub SayType() Implements IVehicle.SayType
MsgBox("I'm a car")
End Sub
End Class
Public Class Plane
Implements IVehicle
Public Sub SayType() Implements IVehicle.SayType
MsgBox("I'm a plane")
End Sub
End Class
I would like the user to select one type of vehicle of all the vehicles available, instantiate one object of this type and call its method "SayType".
So, with this situation, I have 2 questions
The 1st one: I have thought about filling one ComboBox control with all the classes which implement the interface IVehicle. I have searched how to do so with reflection, and I've came up with this solution:
Private Function ObtainVehicleTypes() As IEnumerable(Of Type)
Dim types As IEnumerable(Of Type) = _
Reflection.Assembly.GetExecutingAssembly.GetTypes.Where(Function(t) _
t.GetInterface("IVehicle") IsNot Nothing)
Return types
End Function
With those types, I fill the ComboBox like this, which also works fine:
Private Sub AddTypesOfVehicles()
Dim types As IEnumerable(Of Type) = ObtainVehicleTypes()
For Each t As Type In types
ComboBox1.Items.Add(t.Name)
Next
End Sub
The problem is that, when I try to retrieve the item selected by the user and obtain the type asociated like shown below, I get Nothing, since the String doesn't contain the AssemblyName, only the Class name:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim type As Type = TryCast(ComboBox1.SelectedItem, Type) 'Here I get Nothing
Dim v As IVehicle = TryCast(Activator.CreateInstance(type), IVehicle)
v.SayType()
End Sub
I have also tried to to add this to the combobox:
For Each t As Type In types
ComboBox1.Items.Add(t) 'Adding the type, not only its name.
Next
But then it displays the AssemblyName to the user, which I want to avoid.
So, the question is... how would you do to show the classes to the user and the retrieve them correctly to instantiate an object of the chosen class?
The 2nd question: Do you consider this as a good approach? Would you suggest something simpler?
Thanks!

I do not understand the need of the method SayType on the interface. All types implements the GetType method which will return the info you'll need.
Dim vehicles As IVehicle() = New IVehicle() {New Bike(), New Car(), New Plane()}
For Each vehicle As IVehicle In vehicles
MsgBox(String.Format("I'm a {0}", vehicle.GetType().Name.ToLower()))
Next
'This will produce:
'------------------
'I'm a bike
'I'm a car
'I'm a plane
'------------------
This is how you could populate the combobox:
Dim t = GetType(IVehicle)
Dim list As List(Of Type) = Assembly.GetExecutingAssembly().GetTypes().Where(Function(x As Type) ((x <> t) AndAlso t.IsAssignableFrom(x))).ToList()
Me.ComboBox1.DataSource = list
Me.ComboBox1.DisplayMember = "Name"
And to retrieve the type:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles saveButton.Click
If (Me.ComboBox1.SelectedIndex <> -1) Then
Dim t As Type = TryCast(Me.ComboBox1.SelectedItem, Type)
If (Not t Is Nothing) Then
MsgBox(t.FullName)
End If
End If
End Sub
Edit
A more real-world example of an IVehicle interface would be something like:
Public Interface IVehicle
ReadOnly Property Manufacturer() As String
ReadOnly Property Model() As String
Property Price() As Decimal
End Interface

combobx problem its Excellent answered by #Bjørn-Roger Kringsjå.
Here are additional improvements:
ObtainVehicleTypes:
Private Function ObtainVehicleTypes() As IEnumerable(Of Type)
Dim IVehicleType = GetType(IVehicle)
Dim types As IEnumerable(Of Type) = _
Reflection.Assembly.GetExecutingAssembly.GetTypes.Where(
Function(t) IVehicleType.IsAssignableFrom(t) AndAlso t.IsClass = True)
Return types
End Function
Private Sub AddTypesOfVehicles()
Dim types As IEnumerable(Of Type) = ObtainVehicleTypes().ToArray()
ComboBox1.DisplayMember = "Name"
ComboBox1.DataSource = types
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim type As Type = TryCast(ComboBox1.SelectedItem, Type)
Dim v As IVehicle = TryCast(Activator.CreateInstance(type), IVehicle)
v.SayType()
End Sub

Related

Passing list to function as parameter

I have a function that will perform work on a list, but I cannot get it to accept more than one datatype. For example:
Public Sub PopulateListBox (objectList as List(of VariantType), ListboxToPopulate as Listbox)
listboxToPopulate.Items.Clear() 'clears the items in the listbox
For Each item In objectList
listboxToPopulate.Items.Add(item.ToString)
Next
End
The problem is that I have lists of different classes, like employee, building address, etc. I cannot pass a List(Of EmployeeClass) because it says it cannot be converted to List(Of VariantType). I have also tried List(Of Object) and the same result.
I will demonstrate the use by first showing you a sample class.
Public Class Coffee
Public Property ID As Integer
Public Property Name As String
Public Property Type As String
Public Sub New(iid As Integer, sname As String, stype As String)
ID = iid
Name = sname
Type = stype
End Sub
Public Overrides Function ToString() As String
Return Name
End Function
End Class
I added a parameterized constructor just to make it easy to get a fully populate Coffee. You need to add the .ToString override so the list box will know what to display.
Here is where my List(Of Coffee) comes from.
Private Function FillCoffeeList() As List(Of Coffee)
Dim CoffeeList As New List(Of Coffee)
Using cn As New SqlConnection(My.Settings.CoffeeConnection),
cmd As New SqlCommand("Select Top 10 ID, Name, Type From Coffees;", cn)
cn.Open()
Using reader = cmd.ExecuteReader
Do While reader.Read
Dim c As New Coffee(reader.GetInt32(0), reader.GetString(1), reader.GetString(2))
CoffeeList.Add(c)
Loop
End Using
End Using
Return CoffeeList
End Function
As commented by Hans Passant, change the datatype of objectList to IEnumerable(Of Object).
Public Sub PopulateListBox(objectList As IEnumerable(Of Object), ListboxToPopulate As ListBox)
ListboxToPopulate.Items.Clear() 'clears the items in the listbox
For Each item In objectList
ListboxToPopulate.Items.Add(item)
Next
End Sub
Now I can pass a List(Of Coffee) to the PopulateListBox method.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim CList = FillCoffeeList()
PopulateListBox(CList, ListBox1)
End Sub
I can access the properties of the underlying type be casting.
Private Sub ListBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ListBox1.SelectedIndexChanged
Dim t = ListBox1.SelectedItem.GetType
Select Case t.Name
Case "Coffee"
Dim c = DirectCast(ListBox1.SelectedItem, Coffee)
TextBox1.Text = c.ID.ToString
TextBox2.Text = c.Type
End Select
End Sub
You can add additionsl cases depending on what types you are expecting. There is probably a better way to do this.

EF6 - add objects to tables dynamicaly

I'm new around here (I know this site for long but it's my first time actually asking somehting).
Components that I'm using: - EF6, Devexpress XtraGrid
Ok... so, what I want is to kind of do this,
I have 1 form with multiple tables, and which I will have to be able to add and delete from each's NavigationBar.
I know how to do it, I just need a way to skip the select case.
Here's some code,
Private Sub ListChanged(sender As Object, e As System.ComponentModel.ListChangedEventArgs)
If e.ListChangedType = System.ComponentModel.ListChangedType.ItemAdded Then
Dim bList As BindingList(Of Object) = TryCast(sender, BindingList(Of Object))
Dim m As Object = bList.LastOrDefault()
If m IsNot Nothing Then
Select Case _curentPageIndex
Case 0 : db.GESTARM.Add(m)
Case 1 : 'Other table add
Case 2 : 'Other table add
End Select
End If
End If
End Sub
What I want to do with that is kind of this:
Private Sub ListChanged(sender As Object, e As System.ComponentModel.ListChangedEventArgs)
If e.ListChangedType = System.ComponentModel.ListChangedType.ItemAdded Then
Dim bList As BindingList(Of Object) = TryCast(sender, BindingList(Of Object))
Dim m As Object = bList.LastOrDefault()
'somehow get the table (type) of the entry through the m object
If m IsNot Nothing Then
db.<Table>.Add(m)
End If
End If
End Sub
So instead of writing every add for each case, I just had to do something like that.
Is it possible or am I going to stick with the select case?
Thanks in advance, and sorry if my english is bad (I'm not native).
EDIT 1:
as Mark mentioned in a comment we could use this in C#
but in VB it doesn't work...
Public Class GenericRepository(Of T)
Implements IDisposable
Friend context As GestProGest1Entities
Friend dbSet As Entity.DbSet(Of T) ' Gives error on T "Type argument 'T' does not satisfy the 'Class' constraint for type parameter 'TEntity'"
Public Sub Dispose() Implements IDisposable.Dispose
If context IsNot Nothing Then
context.Dispose()
context = Nothing
End If
End Sub
Public Sub New(context As GestProGest1Entities)
Me.context = context
Me.dbSet = context.Set(Of T)() ' Gives error on T "Type argument 'T' does not satisfy the 'Class' constraint for type parameter 'TEntity'"
End Sub
Public Overridable Sub Insert(entity As T)
dbSet.Add(entity)
context.SaveChanges()
End Sub
End Class
Any ideas how to do this in VB?
EDIT 2:
Ok, so I got it working like this
Public Class GenericRepository(Of T As Class)
now my problem is how to get the type from the object
Private Sub ListChanged(sender As Object, e As System.ComponentModel.ListChangedEventArgs)
If e.ListChangedType = System.ComponentModel.ListChangedType.ItemAdded Then
Dim bList As BindingList(Of Object) = TryCast(sender, BindingList(Of Object))
Dim m As Object = bList.LastOrDefault()
Dim myType As Type = m.GetType()
Dim table As New GenericRepository(Of myType)(db) 'Doesn't accept myType here...
table.Insert(m)
End If
End Sub
With Mark's help I finally got this working.
Private Sub ListChanged(sender As Object, e As System.ComponentModel.ListChangedEventArgs)
If e.ListChangedType = System.ComponentModel.ListChangedType.ItemAdded Then
Dim m As Object = sender(sender.count - 1)
db.Set(m.GetType()).Add(m)
End If
End Sub
Thanks for everyone's help!

Removing an object from List (Of T)

I'm not able to remove an object from my List (of contact)
Here are my fields:
Public Class Contact
'Things to remember
Private m_firstName As String = String.Empty
Private m_lastName As String = String.Empty
Private m_address As Address
My list:
Public Class ContactManager
Private m_contactRegistry As List(Of Contact)
Public Sub New()
m_contactRegistry = New List(Of Contact)()
End Sub
My method in ContactManger Class. Here I'm getting error "Value of type 'Integer' cannot be converted to Assignment.Contact" on the index
Public Function DeleteContact(index As Integer) As Boolean
m_contactRegistry.Remove(index)
Return True
End Function
My delete button method on my Main class:
Private Sub btnRemove_Click(sender As Object, e As EventArgs) Handles btnRemove.Click
'listResults is my listbox
Dim list = listResults.SelectedIndex
'm_contact is an object of the Contact class
m_contacts.DeleteContact(list)
UpdateGUI()
End Sub
The problem is that I don't know how to do the method DeleteContact(index As Integer) without getting an error. Do you guys have a suggestion?
When using an index, you need RemoveAt() rather than Remove()

VB.NET Class with no constructor (no new instance can be declared)

I'm making my own message box class (called MessageBoxC, whatever), and like System.Windows.Forms.MessageBox, I want to make my class with no constructors and no possibility to declare a new instance of it.
E.g.:
Public Class MessageBoxC
Public Overloads Sub Show(ByVal message As String)
Me.Message = message
ProcessData() '(*)
Me.ShowDialog()
End Sub
End Class
Public Class Form1
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
System.Windows.Forms.MessageBox.Show("Hello World!") 'works fine
MessageBoxC.Show("Hello World!") 'works fine
Dim msgBox As New System.Windows.Forms.MessageBox 'and you'll get an error message here (**)
Dim msgBoxC As New MessageBoxC 'no error message
End Sub
End Class
(*) Not important. It just calculates text size (width and height in pixels) to correct form size if needed and the corresponding label gets value of Me.Message property.
(**) This one is what I mean. You cannot make a new instance of a MessageBox class, you'll get following error-message: "Type System.Windows.Forms.MessageBox has no constructors."
Well, my class has also no constructors, but it's possible to declare an instance of it. What's the trick here?
Thanks a lot!
Solved. Thanks to OneFineDay.
Public Class MessageBoxC
Private Sub New()
'Empty
End Sub
Public Overloads Shared Function Show(ByVal message As String) As System.Windows.Forms.DialogResult
Return Show(message, Constants.MyAppName, Constants.messageTitle, MessageBoxCButtons.OK, MessageBoxCIcon.Undefined)
End Function
Public Overloads Shared Function Show(ByVal message As String, _
ByVal caption As String, _
ByVal title As String, _
ByVal buttons As Library.MessageBoxCButtons, _
ByVal icon As Library.MessageBoxCIcon) As System.Windows.Forms.DialogResult
Dim msgBoxC As New CBox(message, caption, title, buttons, icon)
msgBoxC.ShowDialog()
Return msgBoxC.DialogResult
End Function
Private Class CBox
Inherits System.Windows.Forms.Form
Sub New(ByVal message As String, _
ByVal caption As String, _
ByVal title As String, _
ByVal buttons As Library.MessageBoxCButtons, _
ByVal icon As Library.MessageBoxCIcon)
MyBase.New()
InitializeComponent()
Me.Message = message
Me.Text = caption
Me.Title = title
Me.Buttons = buttons
Me.Icon64 = icon
Me.OptimizeMe()
End Sub
End Class
End Class
Public Class Form1
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim dialogResult As New DialogResult
dialogResult = MessageBoxC.Show("This is a simple message.")
MessageBox.Show(dialogResult.ToString)
End Sub
End Class
If you don't declare any constructors, a default constructor is automatically created (this is a public constructor with no parameters).
To prevent anyone creating an instance of your class, you can create a private constructor, like so:
Public Class MessageBoxC
Private Sub New()
' Prevents anyone creating an instance of this class.
End Sub
End Class
Note that your Show method will need to be declared Shared, otherwise you won't be able to call it. In fact, it would need to be Shared, even with the code you provided.
Here is one way to hide the constructor - mainly because the class in question is not accessible.
Public Class Form1
Private Sub meLoad() Handles Me.Load
'Usage
FooBar.Show("Hi")
End Sub
'...
End Class
Public Class FooBar
Private Sub New()
End Sub
Public Shared Sub Show(message As String)
Dim mbc As New MessageBoxC(message)
mbc.ShowDialog()
End Sub
'MessageBoxC is not exposed outside of Foobar which is the entry point
Private Class MessageBoxC : Inherits Form
'define cTor's as needed
Public Sub New(message As String)
Me.Text = message
End Sub
'define content
End Class
End Class

VB.NET get type of derived generic list class from list item method

Public Class notifierMain
Public Class Contacts
Inherits List(Of row)
Public Sub New()
Dim r As New row()
Me.Add(r)
End Sub
Public Class row
Public Sub Validate()
Dim curType As String = Me.GetType().ToString
End Sub
End Class
End Class
Public Class MyContacts
Inherits contacts
End Class
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim c As MyContacts = New MyContacts()
c(0).Validate()
End Sub
End Class
When I debug this winforms application I get curType = "notifier.notifierMain+Contacts+row"
I want to the Validate function to know it is in MyContacts. How do I do this?
You're tostring()'ing gettype which returns a property called full name.
just check the .Name after get type and that'll have the result you want.
btw: this is a weird example, if you want validate() to return the name of the class you'll have to declare it as a function.
:)
The Me.GetType() is always going to return the type of the class it is enclosed in.
You will need to change Validate to a function and pass in the type of object being validated, but then you might as well call c(0).GetType() outside if the validation anyway!
See MSDN documentation for GetType
You can explore your generic type as shown in this MSDN article:
http://msdn.microsoft.com/en-us/library/b8ytshk6.aspx
Hope this helps.