How to input a procedure into another procedure - vb.net

I have a code like below. This is main logic function and I'd like to insert different procedures in that procedure. So is it a way or solution to do that. I marked with **.
Public Shared Sub CheckListSubstrs(ByVal Substrs As IScrNamedObjectList, **mySub(Substr As IScrSubstructure)**)
Dim Substr As IScrSubstructure = Nothing
Dim nSubstr As Integer = Nothing
nSubstr = Substrs.count
If nSubstr > 0 Then
For i As Integer = 0 To nSubstr - 1
Substr = CType(Substrs.item(i), IScrSubstructure)
**mySub(Substr As IScrSubstructure)**
Next
End If
End Sub
I have different types of sub/func procedures and all of them uses Substr As IScr as Substructure as their input so I'd like to insert them dynamically and call them for different classes, modules.
EDIT
I have to clarify my problem more specific to clear conversations.
This is my class with all values.
Option Explicit On
Option Strict On
Imports simpackcomslvLib
Public Class Substr
Public Shared Sub CheckListSubstrs(ByVal Substrs As IScrNamedObjectList, ByVal dgv As DataGridView, SourceType As ****)
Dim nSubstr As Integer = Nothing
nSubstr = Substrs.count
If nSubstr > 0 Then
For i As Integer = 0 To nSubstr - 1
Dim Substr As IScrSubstructure = CType(Substrs.item(i), IScrSubstructure)
'Procedure comes here according to element type for example listing bodies
' CheckListBodies(Substr.getBodyList(False), DataGridView2)
'or if i list forces
'CheckListForces(Substr.getForceList(False), DataGridView3)
'Recursive usage function to get lower substructures information you can think there's a cascaded structure of substructures
CheckListSubstrs(Substrs:=Substr.getSubstrList(False), ProcedureForElementType As ****)
Next
End If
End Sub
Private Shared Sub CheckListBodies(ByVal Bodies As IScrNamedObjectList, ByVal dgv As DataGridView)
Dim nBody As Integer
nBody = Bodies.count
For i As Integer = 0 To nBody - 1
Dim Body As IScrBody = CType(Bodies.item(i), IScrBody)
dgv.Rows.Add(Body.fullName)
Next
End Sub
Private Shared Sub CheckListForces(ByVal Forces As IScrNamedObjectList, ByVal dgv As DataGridView)
Dim nForce As Integer
nForce = Forces.count
For i As Integer = 0 To nForce - 1
Dim Force As IScrForce = CType(Forces.item(i), IScrForce)
dgv.Rows.Add(Force.fullName)
Next
End Sub
Public Shared Sub RunTheCodeforBodies()
CheckListSubstrs(Mdl.getSubstrList(False), DataGridView2, getBodyList)
End Sub
Public Shared Sub RunTheCodeforForces()
CheckListSubstrs(Mdl.getSubstrList(False), DataGridView3, getForceList)
End Sub
End Class
As I showed two examples here, I'm listing different types approx. 30 types. I'm using com-interface and this Iscr types of classes from 3rd part software which I'm connecting.
So all of properties belongs to substructures and I only want to change function element type and output datagridview.

Since you already have existing methods that should elaborate a IScrSubstructure object and, as you say, all methods heve the same signature, you can use a method delegate with that same signature and use it as a parameter of the CheckListSubstrs sub.
A simulation, with some objects that can be used for testing:
Public Structure IScrSubstructure
Public value1 As String
Public value2 As Integer
End Structure
Public Class IScrNamedObjectList
Inherits List(Of IScrSubstructure)
End Class
Public Delegate Sub ScrSubstructureDelegate(ByVal Substr As IScrSubstructure)
Public Shared Sub CheckListSubstrs(ByVal Substrs As IScrNamedObjectList, MyDelegate As ScrSubstructureDelegate)
If Substrs?.Count > 0 Then
For Each item As IScrSubstructure In Substrs
MyDelegate(item)
Next
End If
End Sub
Now, your CheckListSubstrs method has a parameter:
MyDelegate As ScrSubstructureDelegate
you can pass any method that matches that signature:
ByVal Substr As IScrSubstructure
If you try to pass a method that doesn't match the delegate signature, the code will not compile.
So, lets build a couple of methods with these characteristics and call the CheckListSubstrs method using both methods as the MyDelegate parameter:
Public Sub MyIScrSub(ByVal Substr1 As IScrSubstructure)
'Do something with Substr1
Console.WriteLine("MyIScrSub Value1: {0}, MyIScrSub Value2: {1}", Substr1.value1, Substr1.value2)
End Sub
Public Sub MyOtherIScrSub(ByVal AnotherSubscr As IScrSubstructure)
'Do something with AnotherSubscr
Console.WriteLine("MyOtherIScrSub Value1: {0}, MyOtherIScrSub Value2: {1}", AnotherSubscr.value1, AnotherSubscr.value2)
End Sub
Now you can call CheckListSubstrs passing both MyIScrSub and MyOtherIScrSub methods as delegate:
Dim ScrList As IScrNamedObjectList = New IScrNamedObjectList()
ScrList.Add(New IScrSubstructure() With {.value1 = "Value1", .value2 = 1})
ScrList.Add(New IScrSubstructure() With {.value1 = "Value2", .value2 = 2})
ScrList.Add(New IScrSubstructure() With {.value1 = "Value3", .value2 = 3})
CheckListSubstrs(ScrList, AddressOf MyIScrSub)
CheckListSubstrs(ScrList, AddressOf MyOtherIScrSub)
As an note, in the CheckListSubstrs sub I wrote:
If Substrs?.Count > 0 Then
(...)
End If
so you can handle null values for the IScrNamedObjectList parameter:
(this syntax requires VB.Net 14 or newer)
CheckListSubstrs(nothing, AddressOf MyIScrSub)
but you could also write:
If Substrs IsNot Nothing Then
(...)
End If

Related

Is there a way to pass a given property identifier into a Sub as a parameter in VB?

I'm trying to create a generic BubbleSort subroutine to be used throughout my program. As such, I would like to be able to Sort a List by any given Property (e.g. Index, Health, Name etc.) . My issue is that I don't seem to be able to pass a property identifier into the sub. How do I go about implementing this?
I tried to pass the Property in as an object to no avail.
My attempt to call the sub:
AscendingBubbleSort(Party, Character.Health)
The Sub itself:
Sub AscendingBubbleSort(ByRef List As List(Of Object), ByRef ListProperty As Object)
Dim Swap As Boolean = False
Dim Temp As New Object
Do
Swap = False
For i = 0 To ((List).Count - 2)
If List(i).ListProperty < List(i + 1).ListProperty Then
Temp = List(i)
List(i) = List(i + 1)
List(i + 1) = Temp
Swap = True
End If
Next
Loop Until Swap = False
End Sub
I expected the "Party" List to be sorted by Ascending Health, but my program wouldn't compile, citing the following build error:
reference to a non-shared member requires an object reference
with "Character.Health" highlighted as the offending code.
Here's an example using Generics and a Lamba.
Note that you need to change your comparison from < to > if you want an ascending sort as indicated in the title of your subroutine!
Also note the use of ByVal instead of ByRef in the parameters. ByRef is only necessary when you want to point the variable passed in to a completely new instance that is created within the subroutine.
With this setup, you can specify what to sort on like this:
AscendingBubbleSort(objects, Function(x) x.SomeProperty)
' < or >
AscendingBubbleSort(objects, Function(x) x.SomeOtherProperty)
The code:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim objects As New List(Of SomeClass)
objects.Add(New SomeClass() With {.SomeProperty = 5, .SomeOtherProperty = "Bob"})
objects.Add(New SomeClass() With {.SomeProperty = 1, .SomeOtherProperty = "Jane"})
objects.Add(New SomeClass() With {.SomeProperty = 7, .SomeOtherProperty = "Cathy"})
AscendingBubbleSort(objects, Function(x) x.SomeOtherProperty)
For Each sc As SomeClass In objects
Debug.Print(sc.ToString)
Next
End Sub
Sub AscendingBubbleSort(Of T)(ByVal List As List(Of T), ByVal ListPropery As Func(Of T, Object))
Dim Swap As Boolean = False
Dim Temp As New Object
Do
Swap = False
For i = 0 To ((List).Count - 2)
If ListPropery(List(i)) > ListPropery(List(i + 1)) Then
Temp = List(i)
List(i) = List(i + 1)
List(i + 1) = Temp
Swap = True
End If
Next
Loop Until Swap = False
End Sub
End Class
Public Class SomeClass
Public Property SomeProperty As Integer
Public Property SomeOtherProperty As String
Public Overrides Function ToString() As String
Return SomeProperty & ": " & SomeOtherProperty
End Function
End Class

How to filter an object list based on an unknown property

I created a user control consisting of a text box and data grid.
(code not included on the sample below).
I need to filter the content of the data grid by a specific column.
The name of the column depends on the name of the object properties.
The object will vary, however each control will be bound to a specific object.
How I am using the control example:
Public Class Form1
Private ObjectList As List(Of Object)
Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
ObjectList = GenerateRandomData()
AddHandler Me.MyUserControl.SelectionStatusChanged, AddressOf UpdateForm
AddHandler Me.MyUserControl.TextChanged, AddressOf UpdateList
Me.MyUserControl.DataSource = ObjectList
End Sub
Private Sub UpdateList()
Dim FilteredList As IEnumerable(Of Object) = From obj As Item In ObjectList
Where obj.Prop_1.StartsWith(Me.MyUserControl.Text)
Select obj
Me.MyUserControl.DataSource = FilteredList.ToList
End Sub
Private Sub UpdateForm()
If Me.MyUserControl.HasItemLoaded Then
Dim NewItem As Item = CType(Me.MyUserControl.SelectedItem, Item)
TextBox1.Text = NewItem.Prop_1
TextBox2.Text = NewItem.Prop_2
TextBox3.Text = NewItem.Prop_3
Return
End If
TextBox1.Text = ""
TextBox2.Text = ""
TextBox3.Text = ""
End Sub
Private Function GenerateRandomData() As List(Of Object)
Randomize()
Dim lst As New List(Of Object)
For i = 0 To 10000
Dim itm As New Item
Dim value As Integer = CInt(Int((999999 * Rnd()) + 1))
Dim value2 As Integer = CInt(Int((999999 * Rnd()) + 1))
itm.Prop_1 = value
itm.Prop_2 = "abc " & value & value2
itm.Prop_3 = value2 & value & " abc"
lst.Add(itm)
Next
Return lst
End Function
End Class
I would like to handle the work of UpdateList() inside the User control (move the code or equivalent result) but the problem is that the control is unaware of the object type so I cannot directly call Prop_1 or whatever is the name of the property from inside the user control.
So far I am accomplishing this by listening for events outside the user control but I will like to remove as much responsibility from the programmer.
Any ideas are welcome.
The following is designed around its SetList method that is used to set the source list (IEnumerable(Of T)) and the name of the property to filter on in the UpDateList method. It use Reflection to retrieve the desired property.
Public Class UserControl1
Private list As IEnumerable
Private filterPropInfo As Reflection.PropertyInfo
Public Sub SetList(Of T)(list As IEnumerable(Of T), filterPropertyName As String)
filterPropInfo = GetType(T).GetProperty(filterPropertyName, GetType(String))
If filterPropInfo Is Nothing Then
Throw New ArgumentException(String.Format("{0} is not a Public String Property on Type: {1}", filterPropertyName, GetType(T).FullName))
End If
Me.list = list
End Sub
Public Sub UpdateList()
Dim FilteredList As IEnumerable(Of Object) = From obj In list
Where CStr(filterPropInfo.GetValue(obj)).StartsWith(Me.Text)
Select obj
Me.DataGridView1.DataSource = FilteredList.ToList
End Sub
End Class
Example usage by calling the test method.
Public Class Form1
Sub test()
Dim l As New List(Of Item)
l.Add(New Item("fred"))
l.Add(New Item("freddy"))
l.Add(New Item("fredrick"))
l.Add(New Item("joe"))
UserControl11.[Text] = "fred"
UserControl11.SetList(l, "Field1")
UserControl11.UpdateList()
End Sub
End Class
Class Item
Public Property Field1 As String
Public Property Field2 As Int32
Public Sub New(f1 As String)
Me.Field1 = f1
End Sub
End Class

Iterate through a dictionary of Subs/Functions and call them

I'm trying to get the pointer to a function (like in C++) in VB.net and add it to a dictionary to be called later. I have no idea how to do this in VB but I know it's easily done in C++ using void*. I really want to avoid having 8000 global variables such as nextTime_RunSub1, nextTime_RunSub2, etc with a very big Select Case or If statement.
For example:
Public dictOfNewThinkFuncsToAdd As New Dictionary (Of ?, Single) 'Function/Sub, Time To Call
Private Sub Main()
Dim dictOfThinkFuncs As New Dictionary (Of ?, Single) 'Function/Sub, Time To Call
dictOfThinkFuncs.Add(AddressOf(Sub1), GetCurrentTime() + 5)
dictOfThinkFuncs.Add(AddressOf(Sub2), GetCurrentTime() + 6)
Dim removalQueue As New List(Of ?)
Do
Dim bRemoveFunc As Boolean = False
Dim bAddFunc As Boolean = False
For iter As Integer = 0 To dictOfThinkFuncs.Count - 1
If GetCurrentTime() >= dictOfThinkFuncs(iter).Value Then
CallFunction(dictOfThinkFuncs(iter).Key)
removalQueue.Add(dictOfThinkFuncs(iter).Key)
bRemoveFunc = True
End If
Next
If bRemoveFunc Then
For Each func In removalQueue
dictOfThinkFuncs.Remove(func)
Next
removalQueue.Clear()
End If
For Each func In dictOfNewThinkFuncsToAdd
dictOfThinkFuncs.Add(func.Key, func.Value)
bAddFunc = True
Next
If bAddFunc Then
dictOfNewThinkFuncsToAdd.Clear()
End If
Threading.Thread.Sleep(10)
Loop
End Sub
Private Sub Sub1()
DoStuff()
dictOfNewThinkFuncsToAdd.Add(AddressOf(Sub3), GetCurrentTime() + 15)
End Sub
Private Sub Sub2()
DoStuff()
dictOfNewThinkFuncsToAdd.Add(AddressOf(Sub2), GetCurrentTime() + 15)
End Sub
Private Sub Sub3()
DoStuff()
End Sub
This work (both formats). So you can either wrap your calls inside a uniform return or just do addressOf if they are all same type of function with no params.
It's not exactly your code (I know... I know). But gives you a quick example of a dictionary of Lambda functions. And it's vb.net.
Option Strict On
Module Module1
Sub Main()
Dim getters As New Dictionary(Of String, Func(Of Boolean))
getters.Add("First", New Func(Of Boolean)(Function()
Console.WriteLine("Test Run")
Return true
End Function))
getters.Add("Second", AddressOf TestMe)
For each el In getters.Values
el()
Next
End Sub
Public function TestMe() As boolean
Console.WriteLine("Test Run")
Return true
End function
End Module
Dictionary(of Action, Single) works too...
I guess you can do that without the return parameter, too (the vb.net equivalent of void function() is Sub() which is an object of type Action as seen below).
Module Module1
Sub Main()
Dim getters As New Dictionary(Of String, Action)
getters.Add("First", New Action(sub()
TestMe()
End sub))
getters.Add("Second", AddressOf TestMe2)
For each el In getters.Values
el()
Next
End Sub
Public Sub TestMe2()
Console.WriteLine("Test Run")
End Sub
Public function TestMe() As boolean
Console.WriteLine("Test Run")
Return true
End function
End Module

Populating a combo box with a list of functions - Need Advice

I'm looking for some advice on the best way to handle this.
I have a list of about 200 "Functions" which are listed in a combo box. When the user selects a 'function' from the list, I need to return the functionID (integer).
I know this can be done easily by binding a dataset to the key and value of the combobox, I'm just not sure about the best way to populate the dataset.
I feel that the way I'm doing it currently is very convoluted:
I currently have a txt file as an embedded resource which I write to a temporary directory, then I use the following code to read in that text file and populate that box by setting the combobox's datasource and Display Member. It does this by way of a custom class which is implementing System.Collections.IList.
I have pasted the code below. The reason I want to simplify it is that I dislike writing the text file to the disk, because sometimes it fails.
I'm looking for a way to populate my combobox and return my ID, without writing anything to the user's temp folder.
I am open to changing the format of the embedded resource, and or the code.
The fnlist.txt is formatted currently as follows.
index, Function Name, ID
The index is only included for sorting (to keep NONE at the bottom, and unknown function at the top), and I suppose is not strictly required.
#Region "Function lookup"
Dim path As String = System.IO.Path.GetTempPath
Dim _objFnXtef As New clsFunctionXref(path & "fnList.txt")
Private Sub populate_list()
functionlist.DataSource = _objFnXtef
functionlist.DisplayMember = "StrFunction"
End Sub 'Populates the function list
Function get_index(ByVal fnid As Integer)
Dim iLookupNumber As Integer = fnid
Dim tmpFnInfo As New clsFunctionInfo
Dim iReturnIdx As Integer = -1
If iLookupNumber <> 0 Then
tmpFnInfo.IFunctionNumber = iLookupNumber
iReturnIdx = _objFnXtef.IndexOf(tmpFnInfo)
If iReturnIdx <> -1 Then
Return iReturnIdx - 1
Else
Return get_index(9999)
End If
End If
Return 0
End Function 'Returns index of specified function number
#End Region 'All function list functions
Here is the code when a user changes the drop down:
Private Sub functionlist_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles functionlist.SelectedIndexChanged
Dim iReturnFuctionID As Integer = 0
Dim tmpFnInfo As New clsFunctionInfo
tmpFnInfo = _objFnXtef(functionlist.SelectedIndex)
iReturnFuctionID = tmpFnInfo.IFunctionNumber
Func = (iReturnFuctionID)
End Sub
And here is the supporting class:
Imports Microsoft.VisualBasic.FileIO
Public Class clsFunctionInfo
Private _idxFunction As Integer
Public Property IdxFunction() As Integer
Get
Return _idxFunction
End Get
Set(ByVal value As Integer)
_idxFunction = value
End Set
End Property
Private _strFunction As String
Public Property StrFunction() As String
Get
Return _strFunction
End Get
Set(ByVal value As String)
_strFunction = value
End Set
End Property
Private _iFunctionNumber As Integer
Public Property IFunctionNumber() As Integer
Get
Return _iFunctionNumber
End Get
Set(ByVal value As Integer)
_iFunctionNumber = value
End Set
End Property
End Class
Public Class clsFunctionXref
Implements System.Collections.IList
Private _colFunctionInfo As New Collection
Private _filePath As String
Public Property FilePath() As String
Get
Return _filePath
End Get
Set(ByVal value As String)
_filePath = value
End Set
End Property
Public Sub New(ByVal filename As String)
_filePath = filename
Dim _idx As Integer = 1
Dim fields As String()
Dim delimiter As String = ","
Dim iFnx As Integer
Using parser As New TextFieldParser(filename)
parser.SetDelimiters(delimiter)
While Not parser.EndOfData
' Read in the fields for the current line
fields = parser.ReadFields()
Try
iFnx = Convert.ToInt16(fields(0).ToString)
Catch ex As Exception
MessageBox.Show("Error reading file. " & ex.ToString, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
Exit Sub
End Try
Dim objFunction As New clsFunctionInfo
objFunction.IdxFunction = _idx
objFunction.IFunctionNumber = iFnx
objFunction.StrFunction = fields(1).ToString
Me.Add(objFunction)
_idx += 1
End While
End Using
End Sub
Public Function Add(ByVal value As Object) As Integer Implements System.Collections.IList.Add
If _colFunctionInfo.Contains(value.IFunctionNumber.ToString) Then
SyncLock Me.SyncRoot
_colFunctionInfo.Remove(value.IFunctionNumber.ToString)
End SyncLock
ReIndex()
End If
SyncLock Me.SyncRoot
_colFunctionInfo.Add(value, value.IFunctionNumber.ToString)
End SyncLock
End Function
Public Sub Clear() Implements System.Collections.IList.Clear
SyncLock Me.SyncRoot
_colFunctionInfo.Clear()
End SyncLock
End Sub
Public Function Contains(ByVal value As Object) As Boolean Implements System.Collections.IList.Contains
If _colFunctionInfo.Contains(value.IFunctionNumber.ToString) Then
Return True
Else
Return False
End If
End Function
Public ReadOnly Property Count() As Integer Implements System.Collections.ICollection.Count
Get
Return _colFunctionInfo.Count
End Get
End Property
Public ReadOnly Property IsReadOnly() As Boolean Implements System.Collections.IList.IsReadOnly
Get
Return False
End Get
End Property
Public Sub Remove(ByVal value As Object) Implements System.Collections.IList.Remove
If _colFunctionInfo.Contains(value.IFunctionNumber.ToString) Then
SyncLock Me.SyncRoot
_colFunctionInfo.Remove(value.IFunctionNumber.ToString)
End SyncLock
ReIndex()
End If
End Sub
Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return _colFunctionInfo.GetEnumerator
End Function
Public Sub Insert(ByVal index As Integer, ByVal value As Object) Implements System.Collections.IList.Insert
SyncLock Me.SyncRoot
If _colFunctionInfo.Contains(value.IFunctionNumber.ToString) Then
_colFunctionInfo.Remove(value.IFunctionNumber.ToString)
End If
If index < _colFunctionInfo.Count Then
_colFunctionInfo.Add(value, value.IFunctionNumber.ToString, index - 1)
Else
_colFunctionInfo.Add(value, value.IFunctionNumber.ToString)
End If
End SyncLock
ReIndex()
End Sub
Public Sub RemoveAt(ByVal index As Integer) Implements System.Collections.IList.RemoveAt
SyncLock Me.SyncRoot
If _colFunctionInfo.Count <= index And index > 0 Then
_colFunctionInfo.Remove(index)
End If
End SyncLock
ReIndex()
End Sub
Private Sub ReIndex()
SyncLock Me.SyncRoot
Dim iReIndex As Integer = 1
Dim colTemp As New Collection
For Each obj As clsFunctionInfo In _colFunctionInfo
obj.IdxFunction = iReIndex
colTemp.Add(obj, obj.IFunctionNumber)
iReIndex += 1
Next
_colFunctionInfo.Clear()
For Each obj1 As clsFunctionInfo In colTemp
_colFunctionInfo.Add(obj1, obj1.IFunctionNumber.ToString)
Next
colTemp.Clear()
End SyncLock
End Sub
Public ReadOnly Property IsSynchronized() As Boolean Implements System.Collections.ICollection.IsSynchronized
Get
Return True
End Get
End Property
Public ReadOnly Property SyncRoot() As Object Implements System.Collections.ICollection.SyncRoot
Get
Dim _syncRoot As New Object
Return _syncRoot
End Get
End Property
Public ReadOnly Property IsFixedSize() As Boolean Implements System.Collections.IList.IsFixedSize
Get
Return False
End Get
End Property
Public Sub CopyTo(ByVal array As System.Array, ByVal index As Integer) Implements System.Collections.ICollection.CopyTo
For Each obj As clsFunctionInfo In _colFunctionInfo
array(index) = obj
index += 1
Next
End Sub
Public Function IndexOf(ByVal value As Object) As Integer Implements System.Collections.IList.IndexOf
SyncLock Me.SyncRoot
Dim tmpFnInfo As New clsFunctionInfo
Dim tmpFunctionNumber As Integer
Dim tmpidx As Integer = -1
tmpFnInfo = DirectCast(value, clsFunctionInfo)
tmpFunctionNumber = tmpFnInfo.IFunctionNumber
For Each obj In _colFunctionInfo
tmpFnInfo = DirectCast(obj, clsFunctionInfo)
If tmpFunctionNumber = tmpFnInfo.IFunctionNumber Then
tmpidx = tmpFnInfo.IdxFunction
Exit For
End If
Next
Return tmpidx
End SyncLock
End Function
Default Public Property Item(ByVal index As Integer) As Object Implements System.Collections.IList.Item
Get
index += 1
Return _colFunctionInfo(index)
End Get
Set(ByVal value As Object)
End Set
End Property
End Class
I'm sorry that this is so long, but I know that someone on here has some great suggestions on how to handle this because I'm having a little trouble wrapping my head around it. I think I've been starring at it too long.
since you have the text file as an embedded resource, you can open a stream to the file from there, without having to write it to disk. The ResourceReader class should help you.

how come in VB.NET a listcontrol has no Items collection but in ASP.NET it does?

I have a simple subroutine that loads a list from a database. I would like to be able to use the same code to load a ListBox and a ComboBox by defining the list type as the common abstract base class ListControl, and see no reason why I can't - except that VB.NET doesn't expose/implement/whatever the Items collection in ListControl. I note with frustration that this is not the case in ASP.NET. At moment my code is ugly because I have to check what type of list control I have passed in, in order to cast it to a type that has an Items collection. (My code may be ugly for numerous other reasons too, but it is beautiful to me). Is there a way to rewrite the code to avoid having to go through the testing and casting nonsense? (I've stripped it down somewhat so that all that remains is where the problem lies).
Sub loadList(ByVal db As SqlDatabase, ByVal strCommandText As String, lstHost As ListControl, Optional bClearList As Boolean = True, Optional bIsListBox As Boolean = True)
If bClearList Then
If bIsListBox Then
CType(lstHost, ListBox).Items.Clear()
Else
CType(lstHost, ComboBox).Items.Clear()
End If
End If
Dim dt As DataTable = db.ExecuteDataSet(db.GetSqlStringCommand(strCommandText)).Tables(0)
For i = 0 To dt.Rows.Count - 1
If bIsListBox Then
CType(lstHost, ListBox).Items.Add(dt.Rows(i)(0).ToString)
Else
CType(lstHost, ComboBox).Items.Add(dt.Rows(i)(0).ToString)
End If
Next
End Sub
This is because in winforms a ListBox object collection is different from a ComboBox object collection. The simplest way I can think of to tidy this is to make a helper class like
Public Class ListHelper
Public Shared Sub Clear(ByRef lst As ListControl)
If TypeOf lst Is ListBox Then
CType(lst, ListBox).Items.Clear()
Else
CType(lst, ComboBox).Items.Clear()
End If
End Sub
Public Shared Sub Add(ByRef lst As ListControl, ByVal itm As Object)
If TypeOf lst Is ListBox Then
CType(lst, ListBox).Items.Add(itm)
Else
CType(lst, ComboBox).Items.Add(itm)
End If
End Sub
End Class
Then in your code you can just do :
Sub loadList(ByVal db As SqlDatabase, ByVal strCommandText As String, _
ByVal lstHost As ListControl, Optional ByVal bClearList As Boolean = True)
If bClearList Then
ListHelper.Clear(lstHost)
End If
Dim dt As DataTable = _
db.ExecuteDataSet(db.GetSqlStringCommand(strCommandText)).Tables(0)
For i = 0 To dt.Rows.Count - 1
ListHelper.Add(lstHost, dt.Rows(i)(0).ToString)
Next
End Sub
EDIT :
Another (probably better) way to do this is using extension methods (add a new module and ) :
Imports System.Runtime.CompilerServices
Module ListExtensions
<Extension()> _
Public Sub AddToItems(ByRef lc As ListControl, ByVal itm As Object)
If TypeOf lc Is ListBox Then
CType(lc, ListBox).Items.Add(itm)
ElseIf TypeOf lc is ComboBox then
CType(lc, ComboBox).Items.Add(itm)
Else
'handle abuse
End If
End Sub
<Extension()> _
Public Sub ClearItems(ByRef lc As ListControl)
If TypeOf lc Is ListBox Then
CType(lc, ListBox).Items.Clear()
ElseIf TypeOf lc is ComboBox Then
CType(lc, ComboBox).Items.Clear()
Else
'handle abuse
End If
End Sub
End Module
Which ends up being even a bit neater in your code :
Sub loadList(ByVal db As SqlDatabase, ByVal strCommandText As String, _
ByVal lstHost As ListControl, Optional ByVal bClearList As Boolean = True)
If bClearList Then
lstHost.ClearItems()
End If
Dim dt As DataTable = _
db.ExecuteDataSet(db.GetSqlStringCommand(strCommandText)).Tables(0)
For i = 0 To dt.Rows.Count - 1
lstHost.AddToItems(dt.Rows(i)(0).ToString)
Next
End Sub
Here I've called these ClearItems and AddToItems to avoid ambiguity with instance methods. ListControl doesn't have .Clear() or .Add() itself but for the sake of being explicit it's probably best to have a unique nomenclature for extensions.