Link to home
Start Free TrialLog in
Avatar of sb8gq
sb8gq

asked on

Simple Listview Problem

I need a simple way to add a Date to a List view as a DATE rather than a string, so that I can sort by date, and not the string interpretation of the date... The help file says it will sort by date, but it doesn't say how to tell the list view that the List Item in question is a date
Avatar of ameba
ameba
Flag of Croatia image

You can add only strings. To sort dates, there is no simple way (except when your date format is "yy/mm/dd")
Avatar of sb8gq
sb8gq

ASKER

To me that sounds completely illogical.. Granted that The Explorers List View may not be the control that we are given for our own use, but This sorts dates with no problem whatsoever. Another example can be seen in Winzip 7. It just doesn't seem right, especially when the Help says that is can be done!
To me that sounds OK. I would like some ListView features *removed* - so it is faster, and spends less resources.

Try comparing control with a car. I like Porsche (small and fast). Now, add ability to carry 2 tons and 5 adults ...

I said: there is no simple way, but there are few non-simple ways.
This problem has come up before, and there really is no easy answer. There is one way of achieving it.

1 Make the width of the first column 0
2 Put the date in "yyyy/mm/dd" format into the first column
3 The second column will show the date in a format that the user will expect.
4 If you want to be able to click on items, you'll have to switch on full row select for the list view (In VB5 you'll have to use an API call to achieve this)

All the above are described in:
https://www.experts-exchange.com/jsp/qShow.jsp?ta=visualbasic&qid=10160608 
ASKER CERTIFIED SOLUTION
Avatar of sbmc
sbmc

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
sbmc,

Not a bad solution at all, but it still has a few drawbacks.

First of all, a minor typo - looks like this code was auto-generated through some skeleton code file with substitution parameters. Interesting, but MS missed one. The > at the end of the module should be replaced with >

What I really don't like about the code is the fact that after sorting the listview by date, you can no longer retrieve the list items by their position. For example, if you retrieve .listitems(1) you actually get the one displayed at position 3.

Microsoft obviously realised this, and have provided the module ListView_GetItemData, but this module is rather inadequate - you'd have to re-write it for every listview you design, because its parameters are tailored towards what is in the listview in question - in this case a name and date. To put this in a general module is sheer folly.

Just shows you Microsoft doesn't know it all.

There actually is another very easy way to achieve sorting by date, and it doesn't have the above drawbacks. I wouldn't recommend it for listviews with enormous numbers of list items though - it may be a bit slow - but I tried it on one with 1000 items and it was just fine.

See the next module for an example
Option Explicit
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Long) As Long
Private Sub Form_Load()

Me.ListView1.ColumnHeaders.Add , , "Test"

With Me.ListView1.ListItems
    .Add , , "01/02/97"
    .Add , , "03/01/96"
    .Add , , "01/04/97"
End With

End Sub

Private Sub ListView1_ColumnClick(ByVal ColumnHeader As ComctlLib.ColumnHeader)

'Turn redraw off for speed and to reduce irritating flicker
Call SendMessage(ListView1.hwnd, &HB, 0, 0)

Dim lngCount As Long
For lngCount = 1 To ListView1.ListItems.Count
    'Change the text in the date column to a yyyy/mm/dd format
    'In this case, it's the first column, if it is any other, use the appropriate subitem instead.
    ListView1.ListItems(lngCount).Text = Format(CDate(ListView1.ListItems(lngCount).Text), "yyyy/mm/dd")
Next

'Now sort the list view
ListView1.Sorted = True
'In this case, it's the first column, otherwise, set
'SortKey to the correct subitem number
ListView1.SortKey = 0
'That's all folks
ListView1.Sorted = False

For lngCount = 1 To ListView1.ListItems.Count
    'Revert the dates back to their original format, so _
     the user looking at the listview will be none the _
     wiser...
    ListView1.ListItems(lngCount).Text = Format(CDate(ListView1.ListItems(lngCount).Text), "dd/mm/yy")
Next

'Turn redraw back on again
Call SendMessage(hwnd, &HB, 1, 0)

End Sub

PS - my last code sample could work for anything, not just dates, with only minor changes.

For dates, to achieve proper sorting, always format as "yyyy/mm/dd" - the reason being that this can be converted back to a date by ANY locale, even, let's say, a German one where dots are used to separate the y, m and d elements.

The format displayed to the user is obviously dependent on your own locale. I've used dd/mm/yy in my example but it could be "Short Date" or anything else that is valid for that matter.
PPS - What I would be REALLY interested in is a modification to sbmc's original answer, where ListView_GetListItem is changed to a function as follows:

Public Function ListView_GetListItem(lngIndex As Long, hWnd As Long) As ListItem

I'm offering 100 points for anyone who can provide that.
(It should be a generic routine, not just tailored towards the example given)
caraf_g, see Q.10171182
sbmc, line
  ListView1.Refresh
is on wrong position - it should be the last line in ColumnClick event !
Actually, the .Refresh doesn't seem to do anything. The loop after that is just an illustration on how to use the ListView_GetListItem sub, so it doesn't belong there either.
It does. Add this line to see reported index
Private Sub ListView1_ItemClick(ByVal Item As ...)
    Caption = ListView1.SelectedItem.Index
End Sub

If refresh is in sbmc's position it reports wrong indices.
ameba, it reports "wrong" indices anyway, whether you put in the .refresh line or not.

in the smbc code, the only thing after the .refresh is a completely non-functional loop through the items. That loop doesn't do anything whatsoever. It is just an example of how you could loop through the listitems using the ListView_GetListItem sub, and process the items in the order you see them, rather than the order of the collection.
OK - worked it out myself. Here's another version of the smbc code.

It only requires that you add in an invisible column. A small sacrifice...

Here goes. Next comment is the code to go into the Form, the comment after that is the code to go into the module.

Good luck.
Option Explicit

Private Sub Form_Load()

Dim clmAdd As ColumnHeader
Dim itmAdd As ListItem

'Add two Column Headers to the ListView control
Set clmAdd = ListView1.ColumnHeaders.Add(Text:="Name")
Set clmAdd = ListView1.ColumnHeaders.Add(Text:="Date")

'Invisible column to hold the correct index number.
Set clmAdd = ListView1.ColumnHeaders.Add(Text:="Index")
clmAdd.Width = 0

'Set the view property of the Listview control to Report view
ListView1.View = lvwReport

'Add data to the ListView control - adapted it to work in all locales.
Set itmAdd = ListView1.ListItems.Add(Text:="Joe")
itmAdd.SubItems(1) = Format(CDate("1997/05/07"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)
 
Set itmAdd = ListView1.ListItems.Add(Text:="Sally")
itmAdd.SubItems(1) = Format(CDate("1997/04/08"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)

Set itmAdd = ListView1.ListItems.Add(Text:="Bill")
itmAdd.SubItems(1) = Format(CDate("1997/05/29"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)
 
Set itmAdd = ListView1.ListItems.Add(Text:="Fred")
itmAdd.SubItems(1) = Format(CDate("1997/05/17"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)

Set itmAdd = ListView1.ListItems.Add(Text:="Anne")
itmAdd.SubItems(1) = Format(CDate("1997/04/01"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)

End Sub

Private Sub ListView1_ColumnClick(ByVal ColumnHeader As ComctlLib.ColumnHeader)
 
Dim strName As String
Dim dDate As Date
Dim lngItem As Long

'Handle User click on column header
If ColumnHeader.Text = "Name" Then  'User clicked on Name header
    ListView1.Sorted = True        'Use default sorting to sort the
    ListView1.SortKey = 0          'items in the list
Else
    ListView1.Sorted = False       'User clicked on the Date header
                                   'Use our sort routine to sort
                                   'by date
    SendMessage ListView1.hWnd, _
                LVM_SORTITEMS, _
                ListView1.hWnd, _
                AddressOf CompareDates
End If

'Refresh the ListView before writing the data - no need for this, honest!
'ListView1.Refresh
 
'Now, check that it worked correctly. Loop through all the list items, using _
 ListView_GetListItem, and check that it loops through the names in correct order.
For lngItem = 0 To ListView1.ListItems.Count - 1
    'Our listview has two visible columns, and the index is held in column _
     number 3, so the last parameter of ListView_GetListItem will be 3.
    MsgBox ListView_GetListItem(lngItem, ListView1, 3).Text
Next

End Sub
Option Explicit

'Structures

Public Type POINT
  x As Long
  y As Long
End Type

Public Type LV_FINDINFO
  flags As Long
  psz As String
  lParam As Long
  pt As POINT
  vkDirection As Long
End Type

Public Type LV_ITEM
  mask As Long
  iItem As Long
  iSubItem As Long
  State As Long
  stateMask As Long
  pszText As Long
  cchTextMax As Long
  iImage As Long
  lParam As Long
  iIndent As Long
End Type

'Constants
Private Const LVFI_PARAM = 1
Private Const LVIF_TEXT = &H1

Private Const LVM_FIRST = &H1000
Private Const LVM_FINDITEM = LVM_FIRST + 13
Private Const LVM_GETITEMTEXT = LVM_FIRST + 45
Public Const LVM_SORTITEMS = LVM_FIRST + 48

'API declarations

Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _
  ByVal hWnd As Long, _
  ByVal wMsg As Long, _
  ByVal wParam As Long, _
  ByVal lParam As Long) As Long

'Module Functions and Procedures

'CompareDates: This is the sorting routine that gets passed to the
'ListView control to provide the comparison test for date values.

Public Function CompareDates(ByVal lngParam1 As Long, _
                             ByVal lngParam2 As Long, _
                             ByVal hWnd As Long) As Long

  Dim strName1 As String
  Dim strName2 As String
  Dim dDate1 As Date
  Dim dDate2 As Date

  'Obtain the item names and dates corresponding to the
  'input parameters

  ListView_GetItemData lngParam1, hWnd, strName1, dDate1
  ListView_GetItemData lngParam2, hWnd, strName2, dDate2

  'Compare the dates
  'Return 0 ==> Less Than
  '       1 ==> Equal
  '       2 ==> Greater Than

  If dDate1 < dDate2 Then
    CompareDates = 0
  ElseIf dDate1 = dDate2 Then
    CompareDates = 1
  Else
    CompareDates = 2
  End If

End Function

'GetItemData - Given Retrieves

Public Sub ListView_GetItemData(lngParam As Long, _
                                hWnd As Long, _
                                strName As String, _
                                dDate As Date)
  Dim objFind As LV_FINDINFO
  Dim lngIndex As Long
  Dim objItem As LV_ITEM
  Dim baBuffer(32) As Byte
  Dim lngLength As Long

  '
  ' Convert the input parameter to an index in the list view
  '
  objFind.flags = LVFI_PARAM
  objFind.lParam = lngParam
  lngIndex = SendMessage(hWnd, LVM_FINDITEM, -1, VarPtr(objFind))

  '
  ' Obtain the name of the specified list view item
  '
  objItem.mask = LVIF_TEXT
  objItem.iSubItem = 0
  objItem.pszText = VarPtr(baBuffer(0))
  objItem.cchTextMax = UBound(baBuffer)
  lngLength = SendMessage(hWnd, LVM_GETITEMTEXT, lngIndex, _
                          VarPtr(objItem))
  strName = Left$(StrConv(baBuffer, vbUnicode), lngLength)

  '
  ' Obtain the modification date of the specified list view item
  '
  objItem.mask = LVIF_TEXT
  objItem.iSubItem = 1
  objItem.pszText = VarPtr(baBuffer(0))
  objItem.cchTextMax = UBound(baBuffer)
  lngLength = SendMessage(hWnd, LVM_GETITEMTEXT, lngIndex, _
                          VarPtr(objItem))
  If lngLength > 0 Then
    dDate = CDate(Left$(StrConv(baBuffer, vbUnicode), lngLength))
  End If

End Sub

'GetListItem - This is a modified version of ListView_GetItemData
' It takes an index into the list as a parameter and returns
' the appropriate values in the strName and dDate parameters.

Public Function ListView_GetListItem(lngIndex As Long, _
                                     lvIn As ListView, _
                                     lngIndexColumn As Long) As ListItem
    Dim objItem As LV_ITEM
    Dim baBuffer(32) As Byte
    Dim lngLength As Long
    Dim lngRealIndex As Long

    '
    ' Obtain the correct list item
    '
    objItem.mask = LVIF_TEXT
    objItem.iSubItem = lngIndexColumn - 1
    objItem.pszText = VarPtr(baBuffer(0))
    objItem.cchTextMax = UBound(baBuffer)
    lngLength = SendMessage(lvIn.hWnd, LVM_GETITEMTEXT, lngIndex, _
                            VarPtr(objItem))
    lngRealIndex = CStr(Left$(StrConv(baBuffer, vbUnicode), lngLength))
    Set ListView_GetListItem = lvIn.ListItems(lngRealIndex)

End Function
BUG!!! - I just realised... when you sort the listview properly (.sorted = true), you'll mangle all the index numbers. To make sure the program continues to work properly, you have to re-set the contents of the invisible column after the sorting.
Improved code for Form follows in next comment...
Option Explicit

Private Sub Form_Load()

Dim clmAdd As ColumnHeader
Dim itmAdd As ListItem

'Add two Column Headers to the ListView control
Set clmAdd = ListView1.ColumnHeaders.Add(Text:="Name")
Set clmAdd = ListView1.ColumnHeaders.Add(Text:="Date")

'Invisible column to hold the correct index number.
Set clmAdd = ListView1.ColumnHeaders.Add(Text:="Index")
clmAdd.Width = 0

'Set the view property of the Listview control to Report view
ListView1.View = lvwReport

'Add data to the ListView control - adapted it to work in all locales.
Set itmAdd = ListView1.ListItems.Add(Text:="Joe")
itmAdd.SubItems(1) = Format(CDate("1997/05/07"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)
 
Set itmAdd = ListView1.ListItems.Add(Text:="Sally")
itmAdd.SubItems(1) = Format(CDate("1997/04/08"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)

Set itmAdd = ListView1.ListItems.Add(Text:="Bill")
itmAdd.SubItems(1) = Format(CDate("1997/05/29"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)
 
Set itmAdd = ListView1.ListItems.Add(Text:="Fred")
itmAdd.SubItems(1) = Format(CDate("1997/05/17"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)

Set itmAdd = ListView1.ListItems.Add(Text:="Anne")
itmAdd.SubItems(1) = Format(CDate("1997/04/01"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)

End Sub

Private Sub ListView1_ColumnClick(ByVal ColumnHeader As ComctlLib.ColumnHeader)
 
Dim strName As String
Dim dDate As Date
Dim lngItem As Long

'Handle User click on column header
If ColumnHeader.Text = "Name" Then  'User clicked on Name header
    ListView1.Sorted = True         'Use default sorting to sort the
    ListView1.SortKey = 0           'items in the list
    For lngItem = 1 To ListView1.ListItems.Count
        'Our listview has two visible columns, and the index is held in column _
         number 3, so subitem(2) must now be updated.
         ListView1.ListItems(lngItem).SubItems(2) = CStr(lngItem)
    Next
Else
    ListView1.Sorted = False       'User clicked on the Date header
                                   'Use our sort routine to sort
                                   'by date
    SendMessage ListView1.hWnd, _
                LVM_SORTITEMS, _
                ListView1.hWnd, _
                AddressOf CompareDates
End If

'Refresh the ListView before writing the data - no need for this, honest!
'ListView1.Refresh
 
'Now, check that it worked correctly. Loop through all the list items, using _
 ListView_GetListItem, and check that it loops through the names in correct order.
For lngItem = 0 To ListView1.ListItems.Count - 1
    'Our listview has two visible columns, and the index is held in column _
     number 3, so the last parameter of ListView_GetListItem will be 3.
    MsgBox ListView_GetListItem(lngItem, ListView1, 3).Text
Next

ListView1.Refresh

End Sub

In the mean time, sb8gq has accepted the answer. If you don't mind we'll just keep on working on it.. :-)
OK... there was one thing I really didn't like about my last solution. The fact that after sorting the proper way (with .sorted = true), you'll have to loop through all the list items to re-set the indices in the invisible column. Not nice.

OK here's another solution that uses the "Key" property.

Again, Form in first comment, module in second.
Option Explicit

Private Sub Form_Load()

Dim clmAdd As ColumnHeader
Dim itmAdd As ListItem

'Add two Column Headers to the ListView control
Set clmAdd = ListView1.ColumnHeaders.Add(Text:="Name")
Set clmAdd = ListView1.ColumnHeaders.Add(Text:="Date")

'Invisible column to hold the correct index number.
Set clmAdd = ListView1.ColumnHeaders.Add(Text:="Index")
clmAdd.Width = 0

'Set the view property of the Listview control to Report view
ListView1.View = lvwReport

'Add data to the ListView control - adapted it to work in all locales.
Set itmAdd = ListView1.ListItems.Add(, "A" & CStr(ListView1.ListItems.Count + 1), "Joe")
itmAdd.SubItems(1) = Format(CDate("1997/05/07"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)
 
Set itmAdd = ListView1.ListItems.Add(, "A" & CStr(ListView1.ListItems.Count + 1), "Sally")
itmAdd.SubItems(1) = Format(CDate("1997/04/08"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)

Set itmAdd = ListView1.ListItems.Add(, "A" & CStr(ListView1.ListItems.Count + 1), "Bill")
itmAdd.SubItems(1) = Format(CDate("1997/05/29"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)
 
Set itmAdd = ListView1.ListItems.Add(, "A" & CStr(ListView1.ListItems.Count + 1), "Fred")
itmAdd.SubItems(1) = Format(CDate("1997/05/17"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)

Set itmAdd = ListView1.ListItems.Add(, "A" & CStr(ListView1.ListItems.Count + 1), "Anne")
itmAdd.SubItems(1) = Format(CDate("1997/04/01"), "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.Count)

End Sub

Private Sub ListView1_ColumnClick(ByVal ColumnHeader As ComctlLib.ColumnHeader)
 
Dim strName As String
Dim dDate As Date
Dim lngItem As Long

'Handle User click on column header
If ColumnHeader.Text = "Name" Then  'User clicked on Name header
    ListView1.Sorted = True         'Use default sorting to sort the
    ListView1.SortKey = 0           'items in the list
Else
    ListView1.Sorted = False       'User clicked on the Date header
                                   'Use our sort routine to sort
                                   'by date
    SendMessage ListView1.hWnd, _
                LVM_SORTITEMS, _
                ListView1.hWnd, _
                AddressOf CompareDates
End If

'Refresh the ListView before writing the data - no need for this, honest!
'ListView1.Refresh
 
'Now, check that it worked correctly. Loop through all the list items, using _
 ListView_GetListItem, and check that it loops through the names in correct order.
For lngItem = 0 To ListView1.ListItems.Count - 1
    'Our listview has two visible columns, and the index is held in column _
     number 3, so the last parameter of ListView_GetListItem will be 3.
    MsgBox ListView_GetListItem(lngItem, ListView1, 3).Text
Next

ListView1.Refresh

End Sub

Option Explicit

'Structures

Public Type POINT
  x As Long
  y As Long
End Type

Public Type LV_FINDINFO
  flags As Long
  psz As String
  lParam As Long
  pt As POINT
  vkDirection As Long
End Type

Public Type LV_ITEM
  mask As Long
  iItem As Long
  iSubItem As Long
  State As Long
  stateMask As Long
  pszText As Long
  cchTextMax As Long
  iImage As Long
  lParam As Long
  iIndent As Long
End Type

'Constants
Private Const LVFI_PARAM = 1
Private Const LVIF_TEXT = &H1

Private Const LVM_FIRST = &H1000
Private Const LVM_FINDITEM = LVM_FIRST + 13
Private Const LVM_GETITEMTEXT = LVM_FIRST + 45
Public Const LVM_SORTITEMS = LVM_FIRST + 48

'API declarations

Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _
  ByVal hWnd As Long, _
  ByVal wMsg As Long, _
  ByVal wParam As Long, _
  ByVal lParam As Long) As Long

'Module Functions and Procedures

'CompareDates: This is the sorting routine that gets passed to the
'ListView control to provide the comparison test for date values.

Public Function CompareDates(ByVal lngParam1 As Long, _
                             ByVal lngParam2 As Long, _
                             ByVal hWnd As Long) As Long

  Dim strName1 As String
  Dim strName2 As String
  Dim dDate1 As Date
  Dim dDate2 As Date

  'Obtain the item names and dates corresponding to the
  'input parameters

  ListView_GetItemData lngParam1, hWnd, strName1, dDate1
  ListView_GetItemData lngParam2, hWnd, strName2, dDate2

  'Compare the dates
  'Return 0 ==> Less Than
  '       1 ==> Equal
  '       2 ==> Greater Than

  If dDate1 < dDate2 Then
    CompareDates = 0
  ElseIf dDate1 = dDate2 Then
    CompareDates = 1
  Else
    CompareDates = 2
  End If

End Function

'GetItemData - Given Retrieves

Public Sub ListView_GetItemData(lngParam As Long, _
                                hWnd As Long, _
                                strName As String, _
                                dDate As Date)
  Dim objFind As LV_FINDINFO
  Dim lngIndex As Long
  Dim objItem As LV_ITEM
  Dim baBuffer(32) As Byte
  Dim lngLength As Long

  '
  ' Convert the input parameter to an index in the list view
  '
  objFind.flags = LVFI_PARAM
  objFind.lParam = lngParam
  lngIndex = SendMessage(hWnd, LVM_FINDITEM, -1, VarPtr(objFind))

  '
  ' Obtain the name of the specified list view item
  '
  objItem.mask = LVIF_TEXT
  objItem.iSubItem = 0
  objItem.pszText = VarPtr(baBuffer(0))
  objItem.cchTextMax = UBound(baBuffer)
  lngLength = SendMessage(hWnd, LVM_GETITEMTEXT, lngIndex, _
                          VarPtr(objItem))
  strName = Left$(StrConv(baBuffer, vbUnicode), lngLength)

  '
  ' Obtain the modification date of the specified list view item
  '
  objItem.mask = LVIF_TEXT
  objItem.iSubItem = 1
  objItem.pszText = VarPtr(baBuffer(0))
  objItem.cchTextMax = UBound(baBuffer)
  lngLength = SendMessage(hWnd, LVM_GETITEMTEXT, lngIndex, _
                          VarPtr(objItem))
  If lngLength > 0 Then
    dDate = CDate(Left$(StrConv(baBuffer, vbUnicode), lngLength))
  End If

End Sub

'GetListItem - This is a modified version of ListView_GetItemData
' It takes an index into the list as a parameter and returns
' the appropriate values in the strName and dDate parameters.

Public Function ListView_GetListItem(lngIndex As Long, _
                                     lvIn As ListView, _
                                     lngIndexColumn As Long) As ListItem
    Dim objItem As LV_ITEM
    Dim baBuffer(32) As Byte
    Dim lngLength As Long
    Dim strRealIndex As String

    '
    ' Obtain the correct list item
    '
    objItem.mask = LVIF_TEXT
    objItem.iSubItem = lngIndexColumn - 1
    objItem.pszText = VarPtr(baBuffer(0))
    objItem.cchTextMax = UBound(baBuffer)
    lngLength = SendMessage(lvIn.hWnd, LVM_GETITEMTEXT, lngIndex, _
                            VarPtr(objItem))
    strRealIndex = "A" & Left$(StrConv(baBuffer, vbUnicode), lngLength)
    Set ListView_GetListItem = lvIn.ListItems(strRealIndex)
    lngLength = SendMessage(lvIn.hWnd, LVM_FIRST, lngIndex, _
                            VarPtr(objItem))

End Function


Alright. This works, but not to my satisfaction.

What still galls me is that I cannot simply, with the index of a list view item (the way you see it), establish what the corresponding index in the listview's collection is, so I can retrieve the list item. Surely there must be a way to do this?

I'm going to post that as a new question because I don't thing many people are monitoring this question anymore...
Erm... the last line
lngLength = SendMessage(lvIn.hWnd, LVM_FIRST, lngIndex, _
                            VarPtr(objItem))
in the last version of ListView_GetListItem is unnecessary, and can be removed.
I am monitoring. :)
It seems my comment about 'Refresh' was wrong.

Private Sub ListView1_ItemClick(ByVal Item As ListItem)
    Caption = ListView1.SelectedItem.Index
    Caption = Caption & "  " & Item.Index
End Sub

2 diff. values!
I'd be more and more inclined to revert back to my earlier suggestion of Sunday, June 13 1999 - 03:29AM PDT
OK. I investigated the matter, and here's my conclusion

When you use the "Callback" method of sorting mentioned in the answer to this question, the problem is that the order in which items are displayed in the list view no longer corresponds with the order in which they are held in the ListView's collection.

Any solution based on just looping through the collection is doomed to failure.

The VB listview is actually a windows control wrapped in a VB layer. The API calls used in the "Callback" sorting method work directly on the windows control, bypassing the VB layer. That is the cause of the misplacement.

Because the ListItems collection is part of the VB layer, there is NO way you can relate the actual index numbers back to the listitems index number; API calls only work on windows controls, there are no API calls that take the VB layer into account. The only workaround is to resort to coding tricks like adding an extra column for the purpose.

I don't think that is satisfactory.

I've therefore come to the conclusion that the "Callback" method should only be used if you're dealing with large amounts of data and performance becomes a real issue. Otherwise, my own method of formatting the column, sorting and formatting back is much better as it keeps the listview and the listitems collection in sync.

sb8gq, sorry for hijacking your question like this, but thank you, it's been very instructive. I've certainly learnt a lot.