The sort is not bad; a listview sort is based on ASCII codes of the
characters, the same as a ListBox. Unlike a listbox, however, you can use
some API to replace this default sort with your own custom sorting
procedure. See the following Knowledge Base article for some background
information on how to do this:
HOWTO: Sort a ListView Control by Date
Article ID: Q170884
http://support.microsoft.com/support/kb/articles/Q170/8/84.asp
However, this article contains some blatant errors in it. For one, it
mentions the example code will not work with the VB6 version of the ListView
control. Not true. Second, the custom sorting function in the example
returns 0, 1 or 2. This is incorrect. If you look up the LVM_SORTITEMS
message in MSDN Library, you'll see it states "The comparison function must
return a negative value if the first item should precede the second, a
positive value if the first item should follow the second, or zero if the
two items are equivalent".
Here's my own procedure for sorting numbers:
Public Function CompareNumbers(ByVal lParam1 As Long, ByVal lParam2 As Long,
ByVal hWnd As Long) As Long
'This is the callback procedure that gets passed to the
'ListView control to provide the comparison function for
'integer values (Longs).
Dim sData As String
Dim lNumber1 As Long
Dim lNumber2 As Long
ListView_GetItemData lParam1, hWnd, sData
If IsNumeric(sData) Then
lNumber1 = CLng(sData)
Else
lNumber1 = 0
End If
sData = ""
ListView_GetItemData lParam2, hWnd, sData
If IsNumeric(sData) Then
lNumber2 = CLng(sData)
Else
lNumber2 = 0
End If
If m_lSortOrder = lvwAscending Then
CompareNumbers = LngComp(lNumber1, lNumber2)
Else
CompareNumbers = LngComp(lNumber2, lNumber1)
End If
End Function
Private Function LngComp(ByVal Number1 As Long, ByVal Number2 As Long) As
Long
Select Case Number1
Case Number2
LngComp = 0
Case Is < Number2
LngComp = -1
Case Is > Number2
LngComp = 1
End Select
End Function
And here's a slightly modified version of ListView_GetItemData that will get
the data for whatever column is being sorted.
Public Sub ListView_GetItemData(lParam As Long, hWnd As Long, sData As
String)
Dim objFind As LV_FINDINFO
Dim lIndex As Long
Dim objItem As LV_ITEM
Dim baBuffer(32) As Byte
Dim lLength As Long
'Convert the input parameter to an index in the list view
With objFind
.Flags = LVFI_PARAM
.lParam = lParam
End With
lIndex = SendMessageLong(hWnd, LVM_FINDITEM, -1, VarPtr(objFind))
'Get the data for this list item.
With objItem
.mask = LVIF_TEXT
'SubItems are 0-based in the API
.isubitem = m_lSortKey - 1
.pszText = VarPtr(baBuffer(0))
.cchTextMax = UBound(baBuffer)
End With
lLength = SendMessageLong(hWnd, LVM_GETITEMTEXT, lIndex,
VarPtr(objItem))
If lLength > 0 Then
'On Error Resume Next
sData = Left$(StrConv(baBuffer, vbUnicode), lLength)
End If
End Sub
Note that m_lSortKey and m_lSortOrder are module-level variables in the .bas
file in which this code exists. m_lSortKey is ColumnHeader.Index and
m_lSortOrder is either lvwAscending or lvwDescending. I used Public
Property Let procedures to pass these two values from the listview control's
ColumnClick event. You may or may not want to do it the same way.
Also note that when you custom sort items in a ListView in this way, the
ListItems collection does not get sorted, so refering to an item by its
index is useless (always use a key). This also means that certain methods
won't work at all or won't work correctly. One example is the EnsureVisible
method. Here's a replacement procedure you can call.
Public Function EnsureVisible(ByVal hListView As Long, ByVal ItemText As
String) As Boolean
'Replacement procedure for a ListItem's EnsureVisible method.
'This is necessary because the EnsureVisible method will
'not work correctly if we've done our own sorting because
'the indexes of items in the ListItems collection are not
'correct. Return value is True if successful; False otherwise.
Dim lvfi As LV_FINDINFO
Dim lRet As Long
Dim Index As Long
'We first need to find the item in order to get its
'correct index
With lvfi
.psz = ItemText & vbNullChar
.Flags = LVFI_STRING
End With
'Specify -1 for wParam to start search from beginning
Index = SendMessage(hListView, LVM_FINDITEM, -1, lvfi)
If Index = -1 Then
'not found or unsuccessful
Exit Function
End If
'Now we can send the LVM_ENSUREVISIBLE message
'wParam is the index of the item
'lParam is either 0 or 1.
' 0 - scroll if item is not entirely visible
' 1 - no scrolling occurs if the item is at least
' partially visible
lRet = SendMessage(hListView, LVM_ENSUREVISIBLE, Index, ByVal 0&)
EnsureVisible = CBool(lRet)
End Function
Example of calling this procedure:
With ListView1
If Not .SelectedItem Is Nothing Then
EnsureVisible .hWnd, .SelectedItem.Text
End If
End With
Furthermore, in the ItemClick event, the Item object passed to this event
won't be the correct item. To work around this, immediately in this event,
Set Item = ListView1.SelectedItem.
Finally, let me just say that while custom sorting a ListView is entirely
possible, you need to work-around quite a few things and I did not cover
them all in this post. You might determine it's not really worth it,
particularly if you're not too familiar with the API. If there are a lot of
items to be sorted, you will also notice a severe performance hit.
Mike
Quote:
> Hi,
> I sort columns in a list view control like this :
> Private Sub lsvDetail_ColumnClick(ByVal ColumnHeader As
> MSComctlLib.ColumnHeader)
> With lsvDetail
> .SortKey = ColumnHeader.Index - 1
> If .SortOrder = lvwAscending Then
> .SortOrder = lvwDescending
> Else
> .SortOrder = lvwAscending
> End If
> End With
> End Sub
> it works when I have text in my columns.
> But , if i have number or date, the sort is bad. ("3" is greater than
"12")
> How can I sort number, or date like explorer does ?