You could do it like this, but its not very fast with listboxes that have 1000s of items. EDIT Updated, Thanks Ellis.
Code:
Private Sub SortListByItemData2(Lst As ListBox)
' // Sort ListBox By ItemData //
Dim lData() As Long
Dim sData() As String
Dim lTmp As Long
Dim sTmp As String
Dim i As Integer, j As Integer, Count As Integer
' Get list count
Count = Lst.ListCount - 1
If Count < 1 Then Exit Sub ' Nothing to sort
' Dimension arrays
ReDim lData(Count)
ReDim sData(Count)
' Put list and item data into arrays
For i = 0 To Count
lData(i) = Lst.ItemData(i)
sData(i) = Lst.List(i)
Next i
' Sort list by itemdata values
For i = 0 To Count
For j = Count To i + 1 Step -1
If lData(i) > lData(j) Then ' Sort Ascending
lTmp = lData(j) ' swap item data
lData(j) = lData(i)
lData(i) = lTmp
sTmp = sData(j) ' swap list data
sData(j) = sData(i)
sData(i) = sTmp
End If
Next j
Next i
' Copy sorted data back to listbox
Lst.Clear
For i = 0 To Count
Lst.AddItem sData(i)
Lst.ItemData(i) = lData(i)
Next i
End Sub
Last edited by Edgemeal; Oct 21st, 2008 at 02:12 PM.
Reason: Updated
Does that sorting algorithm have a name or did you make it up from scratch?
I got the basic code from someone named MrMac awhile back (not sure if he is on this forum or not),
Code:
For i = 0 To UBound(d) - 1
For j = i + 1 To UBound(d) - 1
If d(i) > d(j) Then
Dtmp = d(j)
d(j) = d(i)
d(i) = Dtmp
End If
Next j
Next i
was posted for sorting numbers in a listbox since VBs listbox sort falls short for sorting numbers, so I just expanded it for this sort by item data usage.
Ya it's slow, but OK if you only have like 1000 items max. I've made a couple vitural listbox classes that use moded versions of Ellis's QuickSort posted in the code bank, that code leaves smoke trails behind its so fast!
True, but that is just additional code that has to be maintained if application needs to be enhanced, e.g. GUI changes. I lean towards using a listview or something that doesn't tie sorting to the GUI too much, e.g. sorting before displaying using SQL if data comes from database.
For most business applications, normal speed is acceptable in order to gain RAD, maintainability, etc-ility.
Last edited by leinad31; Oct 20th, 2008 at 08:50 PM.
bubble sort... wow.... great for short lists.... sucks like a Hoover for larger items....
-tg
No, it isn't bubble sort. The first tipoff is that you can't write a bubblesort using a For...Next loop, because the number of passes needed is indeterminate. As a quick example, bubble sort is the fastest algorithm there is on an already ordered list, having only to make N comparisons before quitting. The above algorithm looks like it needs 1+2+3+...+N comparisons on an ordered list, which would be much slower.
It's some kind of selection sort hybrid. On a randomized list, it will blow bubblesort out of the water, much like selection sort. That doesn't mean it's fast, though; you have to work at it to come up with an algorithm as slow as bubble sort.
The defining characteristic of bubble sort is that it only swaps immediate neighbors. This is what makes it a stable algorithm. Edgemeal's algorithm swaps elements at any distance from each other, making it unstable.
The algorithm's control structure is identical to selection sort, it is conceptually very similar to gnome sort, and in practice it behaves similarly to heap sort. I have run across quite a few comparison sorts, and this is a new one to me. I threw it into my sorting program to see it in action. Here is is (labeled as Bubble sort) partway through. You can see how it stacks up all the larger elements toward the front of the list much like heap sort.
Note that while it seems to be keeping pace with selection sort, this means it is much slower. It starts off quickly and slows down as it progresses like gnome (insertion) sort, opposite of selection sort which starts off slow and speeds up as it progresses. You can tell it gets slower as it progresses because it's basically doing an inverse-insertion.
If you reverse the direction of the interior loop it would make it more efficient.
Code:
' sort list by itemdata values
For i = 0 To Count
For j = Count To i + 1 Step -1
If iData(i) > iData(j) Then ' sort acsending
dTmp = iData(j) ' swap item data
iData(j) = iData(i)
iData(i) = dTmp
sTmp = lData(j) ' swap list data
lData(j) = lData(i)
lData(i) = sTmp
End If
Next j
Next i
Note how much more order is being produced in the early stages of processing.
If you reverse the direction of the interior loop it would make it more efficient.
That crossed my mind but I didn't think it would make much of a difference if any (too lazy to test), but it it does!
EDIT: Changes made to original post.
Last edited by Edgemeal; Oct 21st, 2008 at 02:13 PM.
This thread intrigues me to the extent that you have to wonder what sorting routine that the standard list box uses when the Sort option is set to true. If anyone knows, please holler.
That being said, I wonder if OP might want to consider using an invisible Sorted list box to temporarily hold all the data with the item data switched to the left within the string. Then read that back out and drop the sorted contents into an unsorted, visible list box with the item data switched back to the right.
That procedure even eliminates the requirement of an array.
Last edited by Code Doc; Oct 21st, 2008 at 01:30 PM.
The sorting speed could be improved a lot if the strings weren't copied as the sorting progresses. I suggest a third array that just holds the information of the new indexes.
Alternatively you can also do evil hacks that are slightly lengthy for the purpose:
Code:
Option Explicit
' ProcedureReplace
Private Declare Function CloseHandle Lib "Kernel32" (ByVal hObject As Long) As Long
Private Declare Sub CopyMemory Lib "Kernel32" Alias "RtlMoveMemory" (ByRef lpvDest As Any, ByRef lpvSrc As Any, ByVal cbLen As Long)
Private Declare Function GetCurrentProcessId Lib "Kernel32" () As Long
Private Declare Function OpenProcess Lib "Kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function WriteProcessMemory Lib "Kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private Function InIDE(Optional IDE) As Boolean
If IsMissing(IDE) Then Debug.Assert Not InIDE(InIDE) Else IDE = True
End Function
Private Sub ProcedureReplace(ByVal AddressOfDest As Long, ByVal AddressOfSrc As Long)
Dim lngJMPASM(1) As Long, lngBytesWritten As Long, lngProcessHandle As Long
' get a handle for current process
lngProcessHandle = OpenProcess(&H1F0FFF, 0&, GetCurrentProcessId)
' if failed, we can't do anything
If lngProcessHandle = 0 Then Exit Sub
' check if we are in the IDE
If InIDE Then
' get the real locations of the procedures
CopyMemory AddressOfDest, ByVal AddressOfDest + &H16&, 4&
CopyMemory AddressOfSrc, ByVal AddressOfSrc + &H16&, 4&
End If
' set ASM JMP
lngJMPASM(0) = &HE9000000
' set JMP parameter (how many bytes to jump)
lngJMPASM(1) = AddressOfSrc - AddressOfDest - 5
' replace original procedure with the JMP
WriteProcessMemory lngProcessHandle, ByVal AddressOfDest, ByVal VarPtr(lngJMPASM(0)) + 3, 5, lngBytesWritten
' close handle for current process
CloseHandle lngProcessHandle
End Sub
Public Sub SwapLong(ByRef Value1 As Long, ByRef Value2 As Long)
Static lngTemp As Long
lngTemp = Value1
Value1 = Value2
Value2 = lngTemp
End Sub
Public Sub SwapString(ByRef String1 As String, ByRef String2 As String)
' make this procedure point to SwapLong
ProcedureReplace AddressOf SwapString, AddressOf SwapLong
' swap strings using SwapLong
SwapString String1, String2
End Sub
Or just use GetMem4 and PutMem4, although that would be slower I think.
Edit!
Or yet another evil hack would be to temporarily switch the memory location of iData's data to point to the sData's memory location. Or do that for another array so it isn't required to be done all the time, just before processing and reset after processing.
Edit #2
Oh and the obvious: you could use SendMessage with LB_INITSTORAGE to optimize inserting a lot of items. Also, by temporarily using WM_SETREDRAW to disable drawing and then restoring drawing once done, you can improve the listbox speed without it affecting rest of the application and without making the listbox hidden.
Well, note that OP never said where the data in the list box came from. Instead he just showed a table. So, I decided to assume that the data could have been read in from a file somewhere to build the unsorted list box.
This code solves the OP's problem without using any data arrays or sorting code. Just use an invisible sorted list box to do all the work and then read it back to the visible unsorted list box in correct presentation format:
Code:
Private Sub Command1_Click()
Dim Pointer As Integer
List2.Clear
For I = 0 To List1.ListCount - 1
Pointer = InStr(List1.List(I), "10")
If Pointer Then List2.AddItem Mid$(List1.List(I), Pointer) & "," & Left$(List1.List(I), Pointer - 1)
Next
List1.Clear
For I = 0 To List2.ListCount - 1
Pointer = InStr(List2.List(I), ",")
List1.AddItem Mid$(List2.List(I), Pointer + 1) & Left$(List2.List(I), Pointer - 1)
Next
End Sub
Private Sub Form_Load()
List1.AddItem "Medium Priced 1000"
List1.AddItem "Most Expensive 10000"
List1.AddItem "Cheapest Item 100"
List2.Visible = False
End Sub
Well, note that OP never said where the data in the list box came from. Instead he just showed a table. So, I decided to assume that the data could have been read in from a file somewhere to build the unsorted list box.
But the the title of this thread is, Sorting a ListBox by ItemData
I don't see how, List1.AddItem "Medium Priced 1000" is going to solve the OPs problem.
But the the title of this thread is, Sorting a ListBox by ItemData. I don't see how, List1.AddItem "Medium Priced 1000" is going to solve the OPs problem.
ItemData can be just part of a string. My code works. Please check it again. Simplicity and complexity are not synonymous.
OP's problem can be solved without using a ListView control and without using even just one array.