Is there a way to keep the index order of a listbox after sorting items?

Viewed 50

For my coursework I am making a booking system in Visual Basic that also stores data regarding outstanding payments, I'm using a list box to display names of registered customers saved in an array using the index of the Listbox item in conjunction with the primary key of the array, e.g:

Array:

User ID Amount Owed
0 100
1 0
2 200

This is then added into a Listbox, the customer data such as name and contact information is stored in a separate array to the invoice data, linked via the User ID as the primary key.

Listbox:

Index Name
0 John Doe
1 Jane Doe
2 Joe Bloggs

This system all works fine until I want to add a check box that filters out people who do not have an outstanding payment Filtered Listbox:

Index Name User ID
0 John Doe 0
1 Joe Bloggs 2

As you can see the Index of the Listbox is now no longer the same as the User ID, which in turn breaks my other operations that involve the use of the .SelectedIndex function.

Here is the code involved in this process

Private Sub SortOpenInvoice_CheckedChanged(sender as Object, e as EventArgs) _
    Handles CheckBox1.CheckedChanged

    Dim HasOpenInvoice As Boolean

    HasOpenInvoice = False
    If SortOpenInvoice.Checked = True Then
        For i = 0 To numofCustomers - 1
            For j = 0 To numofInvoices - 1
                If invoices(j).AmountOwed > 0 And invoices(j).CustomerID = customers(i).CustomerID Then 'Searches for an open invoice (outstanding payment) linked to a customers ID
                    HasOpenInvoice = True  'Flag for if there is an open invoice (outstanding payment)
                End If
            Next
            If HasOpenInvoice = False Then
                lbxMemberList.Items.RemoveAt(Convert.ToInt32(customers(i).CustomerID)) 'Removes index if no open invoice is found
            Else
                HasOpenInvoice = False
            End If
        Next
    ...
End Sub

What can I do to preserve the Index value after particular fields have been filtered out? Or are there any other alternative solutions that you could suggest?

Thanks!

1 Answers

I suggest using another approach.

Set the Sorted property of the ListBox to True and assign the list of customers to the DataSource property of the ListBox.

Now, in the ListBox, the customers appear as sorted, but they remain unsorted in the list. I also suggest wrapping the list in a BindingList. It has the advantage of updating the ListBox automatically when you add or remove customers.

You also will have to override the ToString method of the Customer class to have the ListBox display them in an appropriate way:

Class Customer
    Public Property UserID As Integer
    Public Property AmountOwed As Decimal
    Public Property Name As String

    Public Overrides Function ToString() As String
        Return $"{Name} {UserID}, $={AmountOwed:n2}"
    End Function
End Class

In the form (as an example):

Private customerBindingList As BindingList(Of Customer)

Protected Overrides Sub OnLoad(e As EventArgs)
    MyBase.OnLoad(e)

    ' Set up example list of customers
    Dim customers = New List(Of Customer) From {
        New Customer With {.UserID = 0, .Name = "John Doe", .AmountOwed = 100},
        New Customer With {.UserID = 1, .Name = "Jane Doe", .AmountOwed = 0},
        New Customer With {.UserID = 2, .Name = "Joe Bloggs", .AmountOwed = 200}
    }

    ' Use the customers as data source of the ListBox via a BindingList.
    customerBindingList = New BindingList(Of Customer)(customers)
    ListBox1.DataSource = customerBindingList
End Sub

The idea is to work with the customerBindingList instead of working with the ListBox. From the ListBox, you can get the selected customer through the SelectedItem property. This is much more robust than using the index. You cannot keep the UserIDs in sync with any indexes anyway. The UserIDs must stay the same, so that you can identify customers reliably, whereas indexes will change when adding or removing (or sorting) customers.

Example of a delete function not using the index:

Private Sub DeleteButton_Click(sender As Object, e As EventArgs) Handles DeleteButton.Click
    Dim customer = DirectCast(ListBox1.SelectedItem, Customer)

    customerBindingList.Remove(customer)
End Sub

You can also set the DisplayMember property of the ListBox to "Name" instead of overriding ToString.

You can set the ValueMember property of the ListBox to "UserID". This enables you to access the UserID with

Dim id As Integer = DirectCast(ListBox1.SelectedValue, Integer)
Related