1

I want to make a more universal "Progress Bar" which my API has some pre-defined methods. As the progress bar moves, I can update the Message which it displays. I have created a universal "For Each" sub-routine that I can send a Sub, and now I am linking the Progress Bar inside it that also contains a "Cancel" button which I can use to interrupt any loop. But in order to update the message, I want more feedback than just "Item [7/42]", I would rather be able to define "Object.Name" and other times it would need "Object.DisplayName" as each Object I send it will have a different Property that I want to show in the Message.

Is there a way to send an Identifier for which .Property or .Method to reference?

Sub FE(Of T)(c As System.Collections.Generic.IEnumerable(Of T), body As Action(Of T), Optional pb As ProgBar = Nothing, Optional _id As Identifier = Nothing)
    If pb IsNot Nothing Then pb.Start(c.Count)
    For Each i As T In c
        If pb IsNot Nothing AndAlso pb.Cancel Then Exit For
        body(i)
        If pb IsNot Nothing Then pb.Update(i._id)
    Next
    If pb IsNot Nothing Then pb.Close
End Sub

The final input to the function is where I want help, I don't know how to describe what it could be?

Optional _id As Identifier = Nothing

So I could invoke this

FE(Of Sketch)(collection(Of Sketch), _
    Sub(x As Sketch)
        x.Visible = Not x.Visible
    End Sub, _
    New ProgBar("Some Title"), _
    .Name )

Then it could use the .Name when it updates the ProgBar as the Message String. Obviously some error checking would have to be programmed, but I think this gets the point across?

deDevo
  • 11
  • 1
  • You can pass the object of your choice, and use a `Select-Case` inside your method to tell which object it is, and then choose the property appropriate to the object. This would work if you have a limited number of objects, and all objects of the same type use the same property the same way inside the method. – Michael Foster May 02 '23 at 16:37
  • You could create an [interface](https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/interfaces/) that all the objects you send would implement. – Étienne Laneville May 02 '23 at 16:38

2 Answers2

1

You could create an interface that all the objects you send would implement:

Interface IProgressBarItem
    Property DisplayText As String
End Interface

Your Sketch class would then implement this interface:

Public Class Sketch
    Implements IProgressBarItem

    Public Property Name As String Implements IProgressBarItem.DisplayText

End Class

Public Class OtherObject
    Implements IProgressBarItem

    Public Property DisplayName As String Implements IProgressBarItem.DisplayText

End Class

In your main loop, you can cast you i object to IProgressBarItem and use DisplayText:

If pb IsNot Nothing Then
    Dim pbi As IProgressBarItem = CType(i, IProgressBarItem)
    pb.Update(pbi.DisplayText)
End If

This offsets the decision-making of what property to display to the object itself.

You should add code to verify that i implements IProgressBarItem though.

Another approach is to use Reflection as described in this question. You would just pass the property name as a String parameter to FE (instead of _id) and use GetValue to get the property value (Miroslav Zadravec code here):

Public Function GetPropertyValue(ByVal obj As Object, ByVal PropName As String) As Object
    Dim objType As Type = obj.GetType()
    Dim pInfo As System.Reflection.PropertyInfo = objType.GetProperty(PropName)
    Dim PropValue As Object = pInfo.GetValue(obj, Reflection.BindingFlags.GetProperty, Nothing, Nothing, Nothing)
    Return PropValue
End Function
Étienne Laneville
  • 4,697
  • 5
  • 13
  • 29
  • Your link to Miroslav Zadravec's response led me to another: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.interaction.callbyname?view=net-7.0 `Interaction.CallByName` Which is a Microsoft.VisualBasic option that seems to work. For some reason, his code produces "Object reference not set to an instance of an object." During the "GetValue" operation. I will look further in to Interfaces as well, as I like that option. – deDevo May 02 '23 at 18:24
  • @deDevo Interfaces are probably the cleanest way if you are using your own objects. If not, Reflection is probably a better approach. – Étienne Laneville May 02 '23 at 20:17
0

Reflection was the solution:

Dim m As String = pbMsg
Dim n As Func(Of Object, String(), String) = _
    Function(j As Object, k As String())
        If k(0) = "" Then k = k.Skip(1).ToArray
        If k.Length > 1 Then
            Return n(Interaction.CallByName(j, k(0), CallType.Get), k.Skip(1).ToArray)
        Else
            Return Interaction.CallByName(j, k(0), CallType.Get)
        End If
    End Function

If pb IsNot Nothing AndAlso pb.Cancel Then cancel = True : Exit For
If pb IsNot Nothing Then
    If m.Contains(".") Then m = n(i, m.Split("."))
    pb.Update(m)
End If
deDevo
  • 11
  • 1