none
A sorted combobox list with adding and removing items

    Question

  • I am trying to develop a combobox where the list is sorted, and items can be added, AND REMOVED, without screwing up the indexes of the previous selections.  In Visual Basic.

    (Also, there are many comboboxes where they all share the same list, and each combobox can add items to the list.  I have that part working well.  See my previous post on that topic.)  Now I'm trying to get it to also sort, and remove items, without messing up the prior selection indexes.  So I assume it would be best if each combobox does not do its own sorting.  Instead, the list is sorted once (when items are added or removed), and then shared with the many comboboxes.  Also I assume that once a given selection index is assigned, it is not re-assigned again after an item is removed from the list. That way, each list item permanently holds its own unique selection index, as other items are added or removed. 

    I have not idea where to begin on this one.  Thank you for any help you may give.

    -- Walter Snafu

    Wednesday, October 16, 2013 9:55 AM

All replies

  • When removig items from an array you must always start at the last index and move toward index 0.  If you move forward and delete index 3, when you go to the next index 4 you are looking at the data that was originally at index 5.  You in effect skip index 4 completely.

    jdweng

    Wednesday, October 16, 2013 11:05 AM
  • I am trying to develop a combobox where the list is sorted, and items can be added, AND REMOVED, without screwing up the indexes of the previous selections.  In Visual Basic.

    Do you want to maintain the index or the value when the list changes?

    For example, if the list is:

    0 = Apple
    1 = Banana
    2 = Orange

    And ComboBox1 = (2, Orange)

    Then you add "Lemon" and sort so now the list is:

    0 = Apple
    1 = Banana
    2 = Lemon
    3 = Orange

    What should ComboBox1 have?  Should it be (2, Lemon) maintaining the previous index, or should it be (3, Orange) maintaining the previous value?

    What happens if (2, Orange) is deleted from the original list?  Would the combo box become (1, Banana), nothing, or would the old value still be shown until the user made a new selection?

    This seems like a rather convoluted solution to whatever the problem is... perhaps you could explain the goal in a little more detail and get a better suggestion for a solution.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Wednesday, October 16, 2013 2:13 PM
    Moderator
  • Reed Kimble,

    Here is what I'm trying to create.  Once an item, say, "Orange" is added to the list, it is assigned the next available selection index, say "3".  Thereafter it ALWAYS has that same selection index, no matter what other items are added or removed from the list.  And the list is sorted (alphabetically).  (The selection index and the sort order are not the same thing.)  As items are added or removed from the list, a given item may appear at different locations on the list (due to the sorting). 

    As users make selections from the list, their selections are recorded -- by the selection index, which is unique, and permanent (for a given user session).  As items are added or removed from the list, the selection index should remain unchanged. (Otherwise it would mess-up the previous record of their selections. 

    Maintaining lists -- with adding, removing, and sorting -- is pretty standard stuff.  So I believe their exist elegant solutions for this (somewhere within the .NET Framework). I am seeking some clues on how to proceed.


    Thursday, October 17, 2013 1:24 PM
  • "As users make selections from the list, their selections are recorded"

    Instead of recording the listindex of the items, you should better record the items. For example, if Apple, Banana and Lemon are Enum values, you can record the enum values. If Apple, Banana and Lemon are class instances, you can record references to these instances. Don't rely on the listindex at all.


    Armin

    Thursday, October 17, 2013 1:50 PM
  • ...

    Maintaining lists -- with adding, removing, and sorting -- is pretty standard stuff.  So I believe their exist elegant solutions for this (somewhere within the .NET Framework). I am seeking some clues on how to proceed.


    Except that you are attempting to merge multiple concepts into a single grand scheme which does not exist...

    In responding to my previous question, you reiterated what you are trying to do (which was fairly clear already) but still have not explained exactly what problem you are trying to solve.  I was trying to get a better idea of what the actual list data represents, what the multiple combobox selections represent, and why the possible values can change mid-selection.  I think that with a better understanding of the problem, someone could offer you a better solution than what's currently being proposed.

    You have to understand that a ComboBox's SelectedIndex value must be either -1 to indicate no selection or a value within the range of entries in the control's Items collection, and also that indices of items in a list (enumerable collection) must be sequential. 

    You cannot have a list with non-sequential indices.  If you have four items in a list, then their indices are 0, 1, 2, and 3.  If you remove the second item, you do not get 0, 2, 3... you still have 0, 1, 2 but the item at index 1 is now the item previously at index 2 and the item now at index 2 was previously the item at index 3.

    A ComboBox also cannot have a SelectedIndex value that is beyond the length of its items collection.  If there are four items in a ComboBox and the last one is selected (SelectedIndex = 3), when you remove that last item from the list the SelectedIndex must become 2 as there is no longer an item located at index 3.

    So you cannot remove an item from a combobox's items collection and still have it selected.

    Similarly with sorting, if you change the order of the items in the list then the respective indices for each item change as well.

    If you want to be able to change the list contents on the fly like this (including removing and sorting items - just adding new ones to the end of the list would be far less problematic) then you'll probably need to do as Armin suggests and maintain references to the selected items; forget trying to use the selected index for anything beyond its intended purpose.

    When the user selects an item in a ComboBox, that selection needs to be captured into some other variable (and there should be some other control, perhaps a label, to show the item which was chosen).  Then the contents of the ComboBox used to select the item can change (even removing the chosen item from the list) without affecting the user's choice (though that choice may no longer be repeatable).

    By modifying the selection process like this you can then use simple data binding to display the list of items in multiple comboboxes and easily sort using the associated BindingSource component.

    All of that said, I still suspect that there may be a more elegant way to implement the UI functionality if we fully understood the purpose of that functionality.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Thursday, October 17, 2013 2:51 PM
    Moderator
  • Reed Kimble,

    The combobox mechanism works pretty good, and does a lot of what I need to do. That is, the combobox mechanism allows adding items and sorting, and more adding items and re-sorting -- without messing up the selected indexes.  It works good for that!  The problem is I need to REMOVE (or HIDE?) items from the list, without messing up the "selected index".  Okay, call it a "selected item" or "selected value", or "enum" -- I need some identifier (ID) and mechanism that doesn't get messed up by sorting or removing items from the list. 

    I suspect there exists such list managing/sorting mechanisms OUTSIDE of the combobox, and then I would "bind" that list to the combobox somehow.  I doubt the combobox will do all that I want it to do, but that it will work in combination with some other bit of magic -- a list manager of some kind.  (Database software could do this in a snap, but a database program seems like a lot of overhead for this rather trivial needing of it.)

    Again, I believe the .NET Framework has various mechanisms for managing and sorting lists (database Lite?).  I just don't know how to find or use them. 

    ===============

    Dingy Details:  In my application, the application suggests (in the combobox) a suitable distinct name.  This is done for the user's convenience, so the user can (with no typing) simply accept the suggested name, and move onward.  Or, the user may type-in any name of their choosing.  In either case, the name is added to the list.  (Or, the user can select a name off the combobox list.)  After a while, the list can grow inconveniently long, with names that are not actually being used, yet are listed in the combobox.  My application can automatically identify names that are not actually being "used", and remove those names from the list.  Again, this is for the user's convenience, to keep the combobox list from growing too long.  So again, my application needs a mechanism for adding, sorting, and removing items from the list, without messing up the identification (the ID #) of the user's previous selections. 


    Thursday, October 17, 2013 4:40 PM
  • I need some identifier (ID) and mechanism that doesn't get messed up by sorting or removing items from the list.

    Enum values are constant. Object references don't change either.

    Both, depending on how you've defined your fruits, identify each fruit unabmiguously during the lifetime of your application.

    If you want the user to be able to add new fruits, and if you want to maintain your list of fruits beyond the lifetime of the application, where do you intend to store them?

    Thursday, October 17, 2013 5:08 PM
  • OT: Something went wrong with my account. My=Armin Zingler. The message above is displayed as "MigrationUser 1" here, but it's from me.

    Directly before, all threads were displayed as new threads in the thread overview in the vbgeneral forum.

    I hope it fixes itself like so often....


    Armin


    Thursday, October 17, 2013 5:18 PM
  • Walter,

    I'm not sure what benefit this would give you but insofar as binding to a datasource, that can be done:

    Option Strict On Option Explicit On Public Class Form1 Private fruitList As New List(Of String) Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load fruitList.AddRange(New String() {"Orange", "Apple", "Grape", "Lemon"}) fruitList.Sort() ComboBox1.DataSource = fruitList End Sub End Class


    However what's gained in that? If an item is removed from the List(T) which is the datasource, the indices of the ComboBox will change - the same as what others here have been saying.


    Please call me Frank :)

    Thursday, October 17, 2013 5:31 PM
  • I think then that you will have to have two separate controls for each item of data that the user selects.

    The ComboBox will not do to serve as both the item-selection control and the current-item-view control.

    So it sounds like the "item" is just a string (you refer to it as a "name").  So what you need is something to encapsulate a string name and a unique identifier.  You could use a custom object and generic collection or a DataTable for this purpose.

    You'd then setup a BindingSource component for the chosen datasource (either the collection or the data table).  The BindingSource will take care of your sorting (be sure to implement IComparable on your custom object if you go the collection route) and you'll have code to handle adding and removing items (make the code modify the list via the BindingSource instance for the best results).

    So the only missing piece is to have the controls setup such that the user make a selection via a combo box, then clicks a button or hits enter and the selected value is saved somewhere (whatever you are doing with the selections) and is displayed in a corresponding label (or other control).  I suppose you could update the saved value when the associated combobox validates to avoid forcing the user to take an extra step.

    But the point is that you've separated the selection of the user from the selected item in the combo box - which is going to be necessary (however you do it) in order to make this work as desired (without spinning your own entire custom combo box, which I can just about promise you do not want to do).


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Thursday, October 17, 2013 8:37 PM
    Moderator
  • OT: Something went wrong with my account. My=Armin Zingler. The message above is displayed as "MigrationUser 1" here, but it's from me.

    Directly before, all threads were displayed as new threads in the thread overview in the vbgeneral forum.

    I hope it fixes itself like so often....


    Armin



    This happens sometimes... during the post back, your user ID was lost (migration user 1 is the default user id). It sounds like there was some kind of viewstate corruption in your browser, given the funky display you saw prior to this issue. Next time you see anything strange like that, restart your browser before proceeding and it should avoid the migration user issue.

    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Thursday, October 17, 2013 8:40 PM
    Moderator
  • I think then that you will have to have two separate controls for each item of data that the user selects.

    The ComboBox will not do to serve as both the item-selection control and the current-item-view control.

    So it sounds like the "item" is just a string (you refer to it as a "name").  So what you need is something to encapsulate a string name and a unique identifier.  You could use a custom object and generic collection or a DataTable for this purpose.

    You'd then setup a BindingSource component for the chosen datasource (either the collection or the data table).  The BindingSource will take care of your sorting (be sure to implement IComparable on your custom object if you go the collection route) and you'll have code to handle adding and removing items (make the code modify the list via the BindingSource instance for the best results).

    So the only missing piece is to have the controls setup such that the user make a selection via a combo box, then clicks a button or hits enter and the selected value is saved somewhere (whatever you are doing with the selections) and is displayed in a corresponding label (or other control).  I suppose you could update the saved value when the associated combobox validates to avoid forcing the user to take an extra step.

    But the point is that you've separated the selection of the user from the selected item in the combo box - which is going to be necessary (however you do it) in order to make this work as desired (without spinning your own entire custom combo box, which I can just about promise you do not want to do).


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Reed,

    I'm still not following what he actually wants/is attempting to do, are you?

    Is this another case of "using a control for data storage"?


    Please call me Frank :)

    Thursday, October 17, 2013 8:43 PM
  • ...

    Reed,

    I'm still not following what he actually wants/is attempting to do, are you?

    Is this another case of "using a control for data storage"?


    Please call me Frank :)

    Partially, yes, that may be much of the initial problem.

    My understanding is that there is an arbitrary list of names that the user can select from, or add to, when creating some kind of data entry (still no indication of what the data is or how it's being used/stored, making it difficult to suggest anything specific for a solution).

    Periodically the program will look through the list of accumulated names, compare that to the values in storage, and remove from the list any names not being utilized.  It is not clear when or how this process runs.  It may be quite feasible to simply update the stored values with the new indices as the compact operations runs, but there isn't enough information to say.

    It is not clear if this name value is part of a larger data construct or a complete item on its own.  So I'm not sure if the list control's items collection represents the only reference to the value or if it is also being stored somewhere else.  We have to assume the list is persisted to disk, but that could be occurring directly to and from the combobox... its just not clear.

    If the selection control is separated from the display control, then it should become a simple databinding scenario as you suspect, Frank.  I think at this point though we are still trying to make the distinction between a list control's selected index and the user selection of an item within a collection.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Thursday, October 17, 2013 8:55 PM
    Moderator
  • This happens sometimes...
    Thanks.

    Armin

    Thursday, October 17, 2013 8:56 PM
  • @Reed,

    Agreed.

    I'll go out on a limb here and say that one reason he [may not] want to "break the index number arrangement" is because he's not really famililar with encapulation; ergo, he's using that index number to refer to [something else] with that index number.

    We'll see as it develops. :)


    Please call me Frank :)

    Thursday, October 17, 2013 9:00 PM
  • More dingy details:  In my application there are THOUSANDS of diverse parameters the user may choose to deal with, and one of the main purposes of my application is to help aid the user in coping with all those parameters in useful ways.

    In my application, the user may often want to 'group' the parameters together in various intuitively obvious ways.  My way to create a group is to assign the same name to each member of the group.  In my application, a group has one or more members (ie. parameters) assigned to it.  It helps if the name means something, as that helps the user cope with all the various groups.

    Here's how I make that easy on the user.

    Say the user decides to 'enable' a given parameter as a group.  That is done by checking a checkbox.  A combobox (and a nearby textbox) then appears.  The combobox displays a "suggested name" for the group, as this reduces typing, and aids the user by automatically inventing a distinct, meaningful name.  (The name is created automatically to be descriptive of the parameter chosen by the user.)

    The user can accept the suggested name, by simply changing focus away from the combobox.  In that case the suggested name is added to the combobox list. (The combobox list starts empty, and grows as the user adds new groups, with new names.)

    Or, the user can reject the suggested name by simply typing-in a preferred name. If the name is distinct, it is added to the list, or the name might already exist on the list. In either case, that parameter is assigned to that group(via a selection index or some other ID number associated with the name).  Easy on the user. 

    Or, the user can simply select a name off the combobox dropdown list.  Again, easy on the user. 

    In all those three input options, the user should further specify a value for an associated parameter, think of it as a co-parameter. (The co-parameter works specifically and only with the original parameter.)  If the user does not specify a suitable value for the co-parameter, then the membership in that group is useless, and ought be eliminated. Which is a key problem discussed below.

    I like all that, because it is so easy on the user.  To review:  (1) The user checks a checkbox. (2) Then accepts, or selects, or types a name. (3) Then enters a suitable number into the nearby textbox.  If various parameters are assigned to the same name, then they are in the same group. Nice and easy. 

    However, there is a difficulty.  If the user takes step 1, but fails to complete step 3, then a useless group name has been added to the list, and the list can grow inconveniently long with these useless names.  Therefore, I need an automatic elimination of the useless names.  Fortunately, the useless names are easy to identify (as they lack a suitable co-parameter established in step 3).  The only remaining problem is eliminating names from the list, without screwing up the previous groups (which are signified by some ID number associated with the names).

    I'm sorry to put you through all those tedious details. Some people here expressed interest in it.

    Saturday, October 19, 2013 8:35 AM
  • You could use a custom object and generic collection or a DataTable for this purpose.

    You'd then setup a BindingSource component for the chosen datasource (either the collection or the data table).  The BindingSource will take care of your sorting (be sure to implement IComparable on your custom object if you go the collection route) and you'll have code to handle adding and removing items (make the code modify the list via the BindingSource instance for the best results).

    Reed Kimble,

    The above-quoted portion of your post is the kind of insight I was looking for.  I still don't know quite how to proceed, but it looks the most promising solution so far. 

    Also, you correctly suggested:  "It may be quite feasible to simply update the stored values with the new indices as the compact operations runs," That approach is feasible, but awkward to implement. It involves updating (changing) the previously stored ID numbers for thousands of parameters. Why?  Because many ID numbers would change each time one item is removed from the combobox dropdownlist, causing many ID numbers (i.e. selection indexes?)  to change.  The parameters assigned to those altered ID numbers would have to be searched, found, and updated. 

    That approach is "feasible" but not preferred.  I suspect your other suggestion will yield a more elegant , and simpler, solution, as it utilizes standardized list managing routines. 

    Thanks again for your help.

    -- Walter Snafu

    Saturday, October 19, 2013 9:00 AM
  • I'm here reporting on a bit of progress.  I still don't know how to do the filtering and sorting (I assume that's done with a bindingsource somehow?), and return the ItemValue from the combobox. But I have the basic workings of a dataTable attached to a combobox.  Here is my code.  This is cobbled together from various examples I found on the Internet:

     

    ' Declared Public, because I want to use this in comboboxes on various different forms
    ' Declare dataset, then add a table, and a column
    Public DS As DataSet = New DataSet("Stuff")   ' Must I declare a dataset if I only have ONE datatable?
    Public NameTable As DataTable = DS.Tables.Add("NameTable") ' Add a table
    Public ID As DataColumn = NameTable.Columns.Add("ID", Type.GetType("System.Int32")) 

    ' Add dataColumns and primary key
    NameTable.Columns.Add("Visible", Type.GetType("System.Boolean")) 
    NameTable.Columns.Add("Name", Type.GetType("System.String")) 
    NameTable.PrimaryKey = New DataColumn() {ID}   

    ' Load table with 10 items of sample data to see how it works
    Dim workRow As DataRow
    Dim i As Integer
    Dim Toggle As Boolean = True
    For i = 0 To 9
     workRow = NameTable.NewRow()
     workRow(0) = i
     workRow(1) = Toggle
     Toggle = Not Toggle
     workRow(2) = "Data" & i
     NameTable.Rows.Add(workRow)
    Next

    ' Attach to a ComboBox
    Me.ComboBox1.DisplayMember = "Name"
    Me.ComboBox1.ValueMember = "ID"
    Me.ComboBox1.DataSource = NameTable

     

    Wednesday, October 23, 2013 12:10 PM