Hello.

I need some advice.

Here is my scenareo.

I have a form displayed where a user can can select a 'function' from a list box. That 'function' has a corresponding ID number which I need to be able to take and use later in the code.

The List of functions is preset (about 150+ them and the IDs are already assigned (so I can't just use the index).

So if the user selects "Function A", I need the code to set a variable to, for example "42" (or whatever that Funtion's ID is).

I was going to do this using listbox.item and listbox.itemData, but I'm using VB.Net in VS2008 and I understand that function is no longer present (to add ItemData at design time).

Here's where it gets complicated. When the form loads it looks at the variable containing the FunctionID and if there is one present, I want it to populate the co-ordinating FunctionName. I can do that if I know the index number, but I don't know how to look at the FunctionID and return it's index (all FunctionIDs are unique).


I'm looking for some advice on the best way to:

A) store the info, either in the Listbox using ItemData, in an array (but I'd prefer to do it at design time), or some other way you might suggest.

B) cross-reference the ID and display the name in the listbox when the form loads.


Sorry this is so long, but I figured it would be easier if you had some background

Edited 6 Years Ago by napkinbob: Spelling

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
		Debug.WriteLine(CType(ListBox1.SelectedItem, myFunctions).ID)
	End Sub

	Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
		ListBox1.DisplayMember = "FunctionName" 'to show the function name
		'i don't know how you fill your listbox so i added some sample data here
		ListBox1.Items.Add(New myFunctions With {.FunctionName = "function1", .ID = 12})
		ListBox1.Items.Add(New myFunctions With {.FunctionName = "function2", .ID = 78})
		ListBox1.Items.Add(New myFunctions With {.FunctionName = "function3", .ID = 125})
		ListBox1.Items.Add(New myFunctions With {.FunctionName = "function4", .ID = 17})
		ListBox1.Items.Add(New myFunctions With {.FunctionName = "function5", .ID = 133})
		ListBox1.Items.Add(New myFunctions With {.FunctionName = "function6", .ID = 10})

	End Sub
End Class

Public Class myFunctions
	Property FunctionName As String
	Property ID As Integer
End Class

Since the listbox implements IList, make a class that implements IList and connected to the listbox's datasource.

For example:
Here is your basic class

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

Then you make a class to manage your collection of the above classes

Imports Microsoft.VisualBasic.FileIO

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

    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

Then create a listing.txt file and place it in the folder with the .exe
Example listing.txt:

44, Time Time Time is on my side
88, Pizza every morning
422, Love me or Leave me Baby
1223, Your sister is cute 
1299, Can I get her phone number
2345, While you are on your business trip
4444, Someone will need to check up on her
5543, I can help her with her 12 step program
7776, Sex addicts
8871, And their soriorty sisters
9811, who need guidence

And finally make a form with a listbox on it and place the following code in that form:

Dim _objFnXtef As New clsFunctionXref(My.Application.Info.DirectoryPath & "\listing.txt")

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ListBox1.DataSource = _objFnXtef
        ListBox1.DisplayMember = "StrFunction"

    End Sub

    Private Sub ListBox1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles ListBox1.SelectedIndexChanged
        Dim iReturnFuctionID As Integer = 0
        Dim tmpFnInfo As New clsFunctionInfo
        tmpFnInfo = _objFnXtef(ListBox1.SelectedIndex)
        iReturnFuctionID = tmpFnInfo.IFunctionNumber

        MessageBox.Show(iReturnFuctionID.ToString & " is the Function ID", "Lookup", MessageBoxButtons.OK, MessageBoxIcon.Information)
    End Sub

Wow... That's a lot to take in, but it looks very thorough and well explained.

I appreciate the help. I'll have to read through it and get back to you if I have any questions.


Thanks again!

Thanks again, this is great!

I really appreciate you taking the time.

One small follow-up.

How would you suggest I go about displaying a specific item on form load (I changed it to a combobox).

I know I can just do a

combobox1.selectedindex =

, but I need to know the how to look up the index (IdxFunction, i'm assuming) based on the function number (IFunctionNumber). I'm stuck there.

I want to be able to input a number in the text box, and clicking a button will change the selected index on the combobox or (listbox as it may be). I have all that down, I just need to lookup the index based on IFunctionNumber.


I appreciate your expertise on the matter!

easy...

You have the function number...

dim iStartUpFunctionNumber as integer = 422

combobox1.selectedindex =_objFnXtef.item(iStartUpFunctionNumber).IdxFunction

I think this should work. I didn't have VIsual Studio open to test it...

of course the above code will only work in the form because the object _objFnXtef is only declared in the form's context. (ooo... I just noticed a misspelling in the variable name but it doesn't matter)

I'm sure there is easier ways to do this same task... but the reason I would do it this way is so you can add in more complexity to the clsFunctionXref class.

Such as a SaveFile subroutine, or more logic to the iFunctionNumber such as verification of format or checks that it doesn't already exist, etc.

Hmm... I can't quite seem to get that working. when I add the .IdxFunction to the end, it tells me it's not a member. That's the part I couldn't figure out...

mmm... ok...
without visual studio here... I can't debug the code...

Let me see if I have the visual studio disk here... and I will install it and figure this out...

I think it has something to do with the...

Default Public Property Item(ByVal index As Integer) As Object Implements System.Collections.IList.Item

being declared as an object and not as a clsFunctionInfo

I'll get back to you.

I know I'm doing this wrong... but in my current state of mind I can't think of exactly where I'm getting my information crossed.

Anyway... I played around with the IndexOf function in the clsFunctionXref

and this is what I got replace the IndexOf function with this one...

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

So then I put a textbox and a button on the same form with the listbox

And then added this to the button_click

Dim iLookupNumber As Integer = 0

        Dim tmpFnInfo As New clsFunctionInfo
        Dim iReturnIdx As Integer = -1
        If TextBox1.Text.Length > 0 Then
            Try
                iLookupNumber = Convert.ToInt32(TextBox1.Text.ToString)

            Catch ex As Exception
                TextBox1.Text = ""
                MessageBox.Show("Enter a number, please.", "Come on... what are you doing here?", MessageBoxButtons.OK, MessageBoxIcon.Warning)

            End Try
            If iLookupNumber <> 0 Then
                tmpFnInfo.IFunctionNumber = iLookupNumber

                iReturnIdx = _objFnXtef.IndexOf(tmpFnInfo)
                If iReturnIdx <> -1 Then
                    ListBox1.SelectedIndex = iReturnIdx - 1
                End If


            End If
        End If

Completely insane, bassakwards and and screwed up but it works....

Hey Carpenter,

I got it working with that change. When I went to compare the index of function in the original code you posted, there was nothing there... maybe that was the problem.

Anyhow, it's working great and I can definitely work it into my application.

There's only one small detail I've been trying to figure out. I would like to distribute the app as a single EXE, so I don't want to have to distribute the txt file seperately, nor to I want it to remain in the directory after running.

I tried to adapt the line calling the file:

Dim _objFnXtef As New clsFunctionXref(My.Application.Info.DirectoryPath & "\listing.txt")

I've tried embedding it as a resource and accessing it via my.ressources and System.reflection.assembly but I just keep getting an "Illegal Characters in path" exception.

Is this possible what I'm trying to do?

Thanks again so much for your help!

if you don't want to read a text file you can get the data in one of ... ummm.... two ways that I can think of.

1. Do it in-line like the suggestion that GeekbyChoiCe showed you.
2. Do it by importing from a resource, like resource strings.

You know... if I wasn't so stupid... I would say that you could attach a database... but I'm stupid... so we'll ignore that comment.

Back to resource strings. The bogus thing about adding the strings... they want a name to identify the string and then the string itself. Almost too good to be true... you the FunctionNumber as the name and the String is the description of the function (or whatever).

Oh... so I was saying that they're bogus. Because "44" is not a valid name for string resource. However, "A44" is valid. So I took the listing.txt and put it in the resource strings of the project to look much like this:
A1223 Your sister is cute
A1299 Can I get he phone number
A2345 While you are on your business trip
A422 Love me or Leave me Baby
A44 Time Time Time is on my side
A4444 Someone will need to check up on here
A5543 I can help her with her 12 step program
A7776 Sex addicts
A88 Pizza every morning
A8871 And their soriorty sisters
A9811 who need guidence

But at you can see it sorted my list based on the name so you may want to enter the names in a fixed format like A000044, A000088, etc.

(... Dag nabit ... ) I have to go back into the station... something about an emergency. I will post the code when I come back. Sorry.

You know what... I think I've just decided to do it another way. Now that I've got it all working with the text file. I just have a function that stream writes the file to system.IO.Path.GetTempPath and then deletes it when the form closes.

That works for me!

The only thing i discovered is that if I set the combobox to sorted, it only sorts the names, the IDs and index no longer correspond, which is not what I expected, but it's not a big deal, i just threw the list in excel, sorted it and then pasted it back in.

Thanks SO much for your help on this one. I really learned a lot! That would have taken me years to come up with that on my own... maybe one day I'll read it and understand the whole thing... maybe.

Ok... no problem.

While I was at the station for their non-emergency... I worked on the a new sub for reading the resource string.

Add this to the clsFunctionXref class

Public Sub New()
        Dim thisExe As System.Reflection.Assembly = System.Reflection.Assembly.GetExecutingAssembly()
        Dim resources() As String = thisExe.GetManifestResourceNames()
        Dim fn As Integer
        Dim fname As String
        Dim _idx As Integer = 1
        For Each resource As String In resources
            thisExe.GetManifestResourceInfo(resource)
            Dim reader As New Resources.ResourceReader(thisExe.GetManifestResourceStream(resource))
            For Each entry As DictionaryEntry In reader
                fn = 0
                fname = ""

                fn = Convert.ToInt32(entry.Key.ToString().Remove(0, 1))
                fname = entry.Value().ToString()
                Dim objFunction As New clsFunctionInfo
                objFunction.IdxFunction = _idx
                objFunction.IFunctionNumber = fn
                objFunction.StrFunction = fname
                Me.Add(objFunction)
                _idx += 1


            Next
        Next
    End Sub

And then in the form change the line from this

Dim _objFnXtef As New clsFunctionXref(My.Application.Info.DirectoryPath & "\listing.txt")

To this:

Dim _objFnXtef As New clsFunctionXref()

Hey Carpenter.

I'm running into a odd error with this code. If I post the error would you mind taking a look to see what it is? On some PC's it works fine, on some it doesn't.

Hey Carpenter.

I'm running into a odd error with this code. If I post the error would you mind taking a look to see what it is? On some PC's it works fine, on some it doesn't.

Sorry was on a business trip... but I'm back now!!!! YEA! (Sometimes I can be a real dork.)

Anyway... post away... I'm always up for some debug and sleuth work.

This question has already been answered. Start a new discussion instead.