Update Object Properties Through Reflection Based On Property Name - vb.net

I have a custom class with approximately 30 properties. (I.E. headername, headersize...)
I would like to be able to update a property by the name of the property as a string.
I am looking at using reflection (not 100% sure if this is the best method) instead of a large case statement that would continuously need to be updated if I add to the main object.
Through things I have found I have created the following Sub. I have put this in another location than the class of the object.
Public Sub UpdateValue(ByRef MainData As FilesMainData, ByVal SearchForObjectPartName As String, ByVal NewValue As Object)
Dim ObjectPartTypes() As System.Reflection.PropertyInfo = MainData.GetType().GetProperties()
Dim ObjectPartNames() As System.Reflection.FieldInfo = MainData.GetType().GetFields()
For I As Integer = 0 To ObjectPartNames.Count - 1
If ObjectPartTypes(I).Name.Equals(SearchForObjectPartName) Then
ObjectPartTypes(I).SetValue(ObjectPartTypes(I).GetType, NewValue,CType(I, Object) )
End If
Next
End Sub
1) I am getting some errors and know the code isn't right, but am unsure how to fix
2) I am unsure it's in the correct location.
Any help would be appreciated, thank you.

There are a few problems with the code:
You want to update the properties, but when iterating through the loop you are using the wrong count
Also you are using SetValue incorrectly, the first parameter must be the instance of the object containing the property
Here's the update method:
Public Sub UpdateValue(ByRef MainData As FilesMainData, ByVal SearchForObjectPartName As String, ByVal NewValue As Object)
Dim ObjectPartTypes() As System.Reflection.PropertyInfo = MainData.GetType().GetProperties()
For I As Integer = 0 To ObjectPartTypes.Count - 1
If ObjectPartTypes(I).Name.Equals(SearchForObjectPartName) Then
ObjectPartTypes(I).SetValue(MainData, NewValue)
End If
Next
End Sub

Related

How to find the index of an object in a list of objects? VB.Net

I am creating a method FindPerson which searches for a given name in a list of objects and returns the index in the list of the object with this name if found, otherwise it returns -1.
Public Class TPerson
Private Name As String
Private Address As String
Private Age As Integer
Public Sub New()
Name = "x"
Address = "x"
Age = 0
End Sub
……
End Class
Public Class TGroup
Private Group As List(Of TPerson)
Private GroupSize As Integer
Public Sub New(size As Integer)
GroupSize = size
Group = New List(Of TPerson)
End Sub
Public Sub FindPerson(findname As String)
Dim index As Integer
index = Group.FindIndex(findname) 'error
End Sub
End Class
The output should be an index in the list, however when I run the program I get the error: BC30311 Value of type 'String' cannot be converted to 'Predicate(Of TPerson)'
I am not quite sure how to fix this any help will be appreciated
How exactly do you expect the FindIndex method to know what to do with that String that you are passing in? You seem to assume that it will know that it represents a name and that it needs to match an item by Name property but how do you think it's going to do that? Why do you think it would match on Name rather than Address?
As the error message says, you need to provide a Predicate which is a delegate that takes an object of type T and returns a Boolean. In your case, T is TPerson and the Boolean needs to indicate whether findname matches its Name property. The simplest way to do that is with a Lambda expression:
Dim index = Group.FindIndex(Function(person) person.Name = findname)
You could do it with a named method and a delegate if you wanted to but it would be more long-winded and it would mean getting the findname value in by some convoluted means. If you read the documentation for the FindIndex method (which you should have done before posting here) you can find an example of that sort of thing.

List of user made Object not updating values indivudally

I'm pretty sure this problem is really obvious, but I can't seem to make due. I have a list of a user defined object (not by me, but I can look into editing if need be). I tried to declare it to have 14 blank objects. That way when I go to listname(5).setvalues(), it only edits that value. Instead it edits all of them (i.e. all 14) in the list or leaves them to be null.
Here's the code:
Dim currentProperties As New List(Of ExtendedCamObject)
'create a blank list
For i As Integer = 0 To 13
' Dim exp As New ExtendedCamObject
' currentProperties.Add(exp)
currentProperties.Add(New ExtendedCamObject)
Next
propVal = "4012"
currentProperties(8).SetValues(ExtendedCamObject.PropertyTypes.Max_Bitrate, propVal)
This leaves them to null. If I do the commented out version instead (removing the other line in the for loop), it sets them all to the same value. Here's the set value's definition in the class definition:
Private m_strValue As String
Private m_PropertyType As String
Public Sub SetValues(ByVal ExtendedProperty As PropertyTypes,
ByVal strValue As String)
m_PropertyType = CType(ExtendedProperty, PropertyTypes)
m_strValue = strValue
End Sub
I didn't write this user object, but I noticed that there aren't any 'get/set' property items from the original coder. Is that why my values are not being set correctly?
You could use some code clean up here:
Public Class ExtendedCamObject
Private _strValue As String
Private _PropertyType As ExtendedProperty
Public Sub SetValues(ByVal ExtendedProperty As PropertyTypes, ByVal strValue As String)
_PropertyType = ExtendedProperty
_strValue = strValue
End Sub
...
End Class

How Do I loop through this class once I have added items

How do i loop through this class once I add items via this method. Just I am quite new to generic lists so was wonding if someone could point me in right direction in datatables im used to doing the following:
For Each thisentry In dt.rows
Next
What do I use in collections
Calling Code
Calling this in my delciarations of main class
Dim infoNoProductAvail As List(Of infoProductsNotFound) = New List(Of infoProductsNotFound)()
this is how i am adding the files but I have checked in the routine and the count for the list is at 2 products
If medProductInfo.SKU.SKUID = 0 Then
infoNoProductAvail.Add(New infoProductsNotFound(thisenty2.Item("EAN13").ToString(), True))
End If
this is the class itselfs
Public Class infoProductsNotFound
Public Sub New(tbcode As String, notfound As Boolean)
Me.tagbarcode = tbcode
Me.notfound = notfound
End Sub
Private tagbarcode As String = String.Empty
Private notfound As Boolean
Public Property tbcode() As String
Get
Return tagbarcode
End Get
Set(ByVal value As String)
tagbarcode = value
End Set
End Property
Public Property isNotFound() As Boolean
Get
Return notfound
End Get
Set(ByVal value As Boolean)
notfound = value
End Set
End Property
End Class
Tried
I tried using the following
Function BuildExceptionsForEmail()
Dim retval As String = ""
Dim cnt As Int32 = 0
retval = "The following products are not avialable" & vbCrLf
For Each info As infoProductsNotFound In infoNoProductAvail
retval &= info.tbcode
cnt &= 1
Next
Return retval
but for some reason at this point my info noproductAvail is blank even though in the routine above its sitting at count of 2 what gives?
First I'd shrink that declaration a bit:
Dim infoNoProductAvail As New List(Of infoProductsNotFound)
Next, to iterate there are several options. First (and what you're likely most used to):
For Each info as infoProductsNotFound in infoNoProductAvail
If info.tbCode = "xyz" Then
DoSomething(info)
End If
Next
Or you might want to use lambda expressions (if you're using .Net 3.5 and above I think - might be .Net 4):
infoNoProductAvail.ForEach (Function(item) DoSomething(item))
Remember that generics are strongly typed (unlike the old VB collections) so no need to cast whatever comes out: you can access properties and methods directly.
If infoNoProductAvail(3).isNotFound Then
'Do something
End If
(Not that that is a great example, but you get the idea).
The For Each syntax is the same. It works the same way for all IEnumerable objects. The only "trick" to it is to make sure that your iterator variable is of the correct type, and also to make sure that you are iterating through the correct object.
In the case of the DataTable, you are iterating over it's Rows property. That property is an IEnumerable object containing a list of DataRow objects. Therefore, to iterate through it with For Each, you must use an iterator variable of type DataRow (or one of its base classes, such as Object).
To iterate through a generic List(Of T), the IEnumerable object is the List object itself. You don't need to go to one of it's properties. The type of the iterator needs to match the type of the items in the list:
For Each i As infoProductsNotFound In infoNoProductAvail
' ...
Next
Or:
Dim i As infoProductsNotFound
For Each i In infoNoProductAvail
' ...
Next
Or:
For Each i As Object In infoNoProductAvail
' ...
Next
Etc.

How to assign a value to a variable of type Double, that has been passed as Object?

I am trying to assign a value to global variable, which has a Property of type Double. This Property is passed as Object and the assignment fails.
In the example code below, the value is never assigned to the actual object, but only locally:
Public Class Form1
Friend Home As New Building
Private Sub AssignValues() Handles Me.Load
'Objects of different types are added to a list
Dim listObjects As New List(Of Object)
listObjects.Add(Home.Surface)
'All the Objects in listObjects are assigned a value that
'is stored as String
For Each o As Object In listObjects
SetProperty(o, "45.6")
Debug.Print("Surface = " & Home.Surface.ToString)
Next
End Sub
Private Sub SetProperty(ByRef Variable As Object, ByVal Value As String)
Select Case Variable.GetType
Case GetType(Double)
Variable = CDbl(Value)
Case Else
'...
End Select
End Sub
End Class
Public Class Building
Dim _surface As Double = 0
Public Property Surface As Double
Get
Return _surface
End Get
Set(ByVal value As Double)
_surface = value
End Set
End Property
End Class
The program invariably outputs Surface = 0 instead of 45.6. What am I doing wrong?
I tried to pass the Variable as reference, as suggested here, but without success. I also read about using Reflection, but there ought to be something simpler than that...
When your adding home.surface to the list, your adding a copy of the double to the list and then adjusting that copy. Stick a watch on "o" and see how it changes whilst home.surface remains the same.
If you want to use reflection, try something along these lines.
Dim prop As Reflection.PropertyInfo = o.GetType().GetProperty("Surface")
prop.SetValue(o, 45.6)
With Variable.GetType you will get always Object, because this is the type of Variable. What you can do with an Object is converting/casting it into a different type (like Double).
The best way to determine the "original type" from where the Object comes would be including an additional variable telling it. Another option might be converting the given Object into the target Type and see if it is not nothing/does not trigger an error. But this second option is not too accurate, mainly when dealing with "equivalent types" like Doubles/Integers.

Using Delegates for handling an event

Sorry, that's the best subject I can come up with, if I understood the solution better, I could probably phrase a better subject line.
I am using a great grid control, Super List,l located here:
http://www.codeproject.com/KB/list/outlooklistcontrol.aspx?fid=449232&df=90&mpp=25&noise=3&sort=Position&view=Quick&fr=276
Before you read the problem, please note that you can download a very small VB.NET 2005 sample app which demos the problem:
http://dokmanovich.com/Documents/SuperListEvents.zip
Getting the answer to my question will, I hope, help me to understand dynamic events better in the context of what I am trying to accomplish.
The grid works like this: When you add a column to the grid, you specify the address of an event handler which will return the value at run time. In this case, the CC_ItemValueAccessor function. The latter function will be called with an input parameter which, in this case, is a "ToDo" object. Each ToDo object will be rendered as one row in the grid. The job of the CC_ItemValueAccessor function is to return the column value to be displayed by the grid for the row that corresponds to the passed-in ToDo object.
This works fine till I take it to the next step:
I want to dynamically create columns at run time. For example, I want to display the output of a datatable returned as a result of executing a user-specified SQL.
Using the earlier described static approach, I have one columnItemValueAccessor function responsible for returning the value of each column in the grid for the passed in row object. Now, since the columns are determined at run time based on the SQL returned results, I believe I need to write a generic handler that handles all columns, determines the name of the column that triggered this event and then returns the value for that column within the row object that is passed in as the sole parameter.
The problem is that the ItemValueAccessor function has a signature that only includes the row object and I do not know of a way to determine which column name is needed since all of the columns were hooked up to the same ItemValueAccessor function as the event handler.
I suspect that this is just a limitation of the control and that to overcome this problem I would have to enhance the underlying custom control, but that is likely beyond my current skills as it is an advanced control written in C# and I am a VB guy.
Here's the code:
Private Sub AddCcColumn()
Dim NewColumn As New BinaryComponents.SuperList.Column("CC", "CC", 110, AddressOf Cc_ItemValueAccessor)
_SuperList.Columns.Add(NewColumn)
End Sub
Private Function Cc_ItemValueAccessor(ByVal rowItem As Object) As Object
Dim ToDo As ToDo = CType(rowItem, SrToDoAndException).ToDo
Return ToDo.CCs.ToString
End Function
'---------------------------
And here are the signatures of the Column's instantiator method and the definition of the last parameter which is responsible for specifying the procedure that handles identifies the event handler responsible for returning the value of the column.
Public Sub New(ByVal name As String, ByVal caption As String, ByVal width As Integer, ByVal columnItemValueAccessor As BinaryComponents.SuperList.ColumnItemValueAccessor)
Member of BinaryComponents.SuperList.Column
Public Sub New(ByVal object As Object, ByVal method As System.IntPtr)
Member of BinaryComponents.SuperList.ColumnItemValueAccessor
Does anyone have any suggestions or am I stuck? I would really love to utilize the fantasic grouping capabilities of this control so I can display dynamic output that allows the user to group the dynamic output of a SQL by any column that they want.
I addressed the question to the author at the above site but it has gone unanswered. This is a desperate attempt to find a way to do this.
Thanks for bearing with me. I hope this question isn't rejected based on the fact that I refer to a third party control. My hope is that the answer lies in a better understanding of delegates, a more universal topic.
I used a lambda function, as Matthew suggested. Here is the code from the dynamic approach:
Private Sub btnDynamic_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDynamic.Click
ListControl1.Columns.Clear()
For Each DataCol As DataColumn In _ds.dtbPerson.Columns
' Get the column name in a loop variable - it needs to be in loop scope or this won\'t work properly'
Dim colName = DataCol.ColumnName
' Create the function that will be called by the grid'
Dim colLambda As ColumnItemValueAccessor = Function(rowItem As Object) General_ItemValueAccessor(rowItem, colName)
' Setup each column in the grid'
Dim NewColumn As New BinaryComponents.SuperList.Column(DataCol.ColumnName, DataCol.ColumnName, 220, colLambda)
ListControl1.Columns.Add(NewColumn)
Next
End Sub
Private Function General_ItemValueAccessor(ByVal rowItem As Object, ByVal colName As Object) As Object
Dim rowPerson As DataRow = CType(rowItem, DataRow)
Return rowPerson.Item(colName).ToString
End Function
Here's a quick primer on how it works:
Each time through the loop the lambda function is creating a new callback function for each column that looks something like this:
Class Func1
Dim colName1 As String = "PersonId"
Private Function General_ItemValueAccessor1(ByVal rowItem As Object) As Object
Dim rowPerson As DataRow = CType(rowItem, DataRow)
Return rowPerson.Item(Me.colName1).ToString
End Function
End Class
Class Func2
Dim colName2 As String = "LastName"
Private Function General_ItemValueAccessor2(ByVal rowItem As Object) As Object
Dim rowPerson As DataRow = CType(rowItem, DataRow)
Return rowPerson.Item(Me.colName2).ToString
End Function
End Class
... for however many columns you have - 3 in this case.
You need the colName variable within the loop and not use just DataCol.ColumnName directly in the lambda. Otherwise, when the grid gets around to calling the callback functions, that DataCol variable would be equal to the last value from the collection (or Nothing) for all the callback functions.
Basically, it would do this and you wouldn't get what you expected:
Class Func
Dim DataCol1 = DataCol
Private Function General_ItemValueAccessor1(ByVal rowItem As Object) As Object
Dim rowPerson As DataRow = CType(rowItem, DataRow)
Return rowPerson.Item(Me.DataCol1.ColumnName).ToString
End Function
Private Function General_ItemValueAccessor2(ByVal rowItem As Object) As Object
Dim rowPerson As DataRow = CType(rowItem, DataRow)
Return rowPerson.Item(Me.DataCol1.ColumnName).ToString
End Function
...
End Class
Hope that helps. Good luck.
The problem is that the ItemValueAccessor function has a signature that only includes the row object and I do not know of a way to determine which column name is needed since all of the columns were hooked up to the same ItemValueAccessor function as the event handler.
Okay, I haven't used that control in the past, and I'm really a C# person. But I think you may be able to accomplish this by creating a new lambda function for each column. Something like:
Private Sub AddCcColumn(ByVal sender As System.Object As System.String)
colLambda = (Function(rowItem As Object) Cc_InternalItemValueAccessor(columnName, rowItem))
Dim NewColumn As New BinaryComponents.SuperList.Column("CC", "CC", 110, colLambda)
_SuperList.Columns.Add(NewColumn)
End Sub
Then, colLambda will fit the signature, while your internal Cc_InternalItemValueAccessor gets the info it needs. Totally untested, but I think the basic idea works.