Many Windows programs use two adjacent list box controls to let the user select a number of items from a list of available values; such list boxes are mutually exclusive, in the sense that a given item always appear in the list box on the left (available items) or in the list box on the right (items that have been selected so far). While it is not difficult to implement a simple VB program that does the trick, a minor problem is that in the real world you have to preserve the same logical ordering of the items enforced in the original list box, and this has often nothing to do with the alphabetical order. For instance, you might offer a list of months for the user to select from, and wish that selected months appear in the rightmost list box in the same logical (i.e. chronological) order expected by the user. Another point you should deal with are list box controls that support multiple selections. Here is a routine that takes all these details into account:
Sub MoveListItem(sourceList As ListBox, destList As ListBox, _ Optional itemIndex As Variant)Dim index As LongDim thisItemData As Long If Not IsMissing(itemIndex) Then ' use itemIndex if not omitted GoSub MoveListItem_SubElseIf sourceList.MultiSelect = 0 Then ' else use current item itemIndex = sourceList.ListIndex GoSub MoveListItem_SubElse ' this is a multi-selection listbox ' loop over all selected items ' NOTE that we cannot use a simple For..Next loop ' because while we remove items their index changes itemIndex = 0 Do While itemIndex < sourceList.ListCount If sourceList.Selected(itemIndex) Then GoSub MoveListItem_Sub Else itemIndex = itemIndex + 1 End If LoopEnd IfExit Sub MoveListItem_Sub:If itemIndex < 0 Then Exit SubthisItemData = sourceList.ItemData(itemIndex)If thisItemData = 0 Or destList.Sorted = True Then ' if there is no associated ItemData append this ' element to the end or exploit curret Sort order destList.AddItem sourceList.List(itemIndex)Else ' else we must search the proper location ' in the destination listbox For index = 0 To destList.ListCount - 1 ' loop until we find an item with higher ItemData If destList.ItemData(index) > thisItemData Then ' add new item in that position destList.AddItem sourceList.List(itemIndex), index ' do not forget the associated data destList.ItemData(index) = thisItemData ' signal that we did the insert thisItemData = 0 Exit For End If Next If thisItemData Then ' this happens when the element goes to the end destList.AddItem sourceList.List(itemIndex) destList.ItemData(destList.ListCount - 1) = thisItemData End IfEnd If' remove from source listboxsourceList.RemoveItem itemIndexReturnEnd Sub
You can call this routine specifying the index of the item you with to move, or you can omit it. In this latter case the routine behaves differently if the source list box supports multiple selections (in which case all selected items are moved) or not (in which case only the current item is moved). The insertion of a new item in the destination list box is a bit tricky, because the routine also checks if the source item has any ItemData value associated to it, and if so it uses this value to search for the correct position in the list box.While this routine does all the actual work, you still have to build a reasonable user interface. In this code example I build a couple of list box controls – named List1(0) and List1(1) – plus two command buttons. You can move items by double clicking on any item on either list box, or use the Add and Remove buttons:
Private Sub cmdAdd_Click() MoveListItem List1(0), List1(1)End SubPrivate Sub cmdRemove_Click() MoveListItem List1(1), List1(0)End SubPrivate Sub List1_DblClick(index As Integer) MoveListItem List1(index), List1(1 - index)End Sub
At this point you only have to load some data in the leftmost list box control, which you usually do in the Form_Load event. For instance, you might offer the list of months
Private Sub Form_Load() With List1(0) .AddItem "January" .AddItem "February" .AddItem "March" .AddItem "April" .AddItem "May" .AddItem "June" .AddItem "July" .AddItem "August" .AddItem "September" .AddItem "October" .AddItem "November" .AddItem "December" End WithEnd Sub
Obviously, it makes no sense to use sorted list boxes in this cases. In order to enforce this same ordering even when items are moved to another list box (and possibly back to the original one), you should assign a progressive ItemData value to each element. I have prepared a short routine that does just that, and that you can easily reuse in all your projects:
Sub InitItemData(sourceList As ListBox) Dim i As Long For i = 0 To sourceList.ListCount - 1 sourceList.ItemData(i) = i + 1 NextEnd Sub