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
You can add only strings. To sort dates, there is no simple way (except when your date format is "yy/mm/dd")
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.
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
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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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
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(ByVa l 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(lngCou nt).Text = Format(CDate(ListView1.Lis tItems(lng Count).Tex t), "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(lngCou nt).Text = Format(CDate(ListView1.Lis tItems(lng Count).Tex t), "dd/mm/yy")
Next
'Turn redraw back on again
Call SendMessage(hwnd, &HB, 1, 0)
End Sub
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
With Me.ListView1.ListItems
.Add , , "01/02/97"
.Add , , "03/01/96"
.Add , , "01/04/97"
End With
End Sub
Private Sub ListView1_ColumnClick(ByVa
'Turn redraw off for speed and to reduce irritating flicker
Call SendMessage(ListView1.hwnd
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(lngCou
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(lngCou
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.
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(lngIn dex As Long, hWnd As Long) As ListItem
I'm offering 100 points for anyone who can provide that.
Public Function ListView_GetListItem(lngIn
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 !
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.Ind ex
End Sub
If refresh is in sbmc's position it reports wrong indices.
Private Sub ListView1_ItemClick(ByVal Item As ...)
Caption = ListView1.SelectedItem.Ind
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.
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.
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.Ad d(Text:="N ame")
Set clmAdd = ListView1.ColumnHeaders.Ad d(Text:="D ate")
'Invisible column to hold the correct index number.
Set clmAdd = ListView1.ColumnHeaders.Ad d(Text:="I ndex")
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(Te xt:="Joe")
itmAdd.SubItems(1) = Format(CDate("1997/05/07") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
Set itmAdd = ListView1.ListItems.Add(Te xt:="Sally ")
itmAdd.SubItems(1) = Format(CDate("1997/04/08") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
Set itmAdd = ListView1.ListItems.Add(Te xt:="Bill" )
itmAdd.SubItems(1) = Format(CDate("1997/05/29") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
Set itmAdd = ListView1.ListItems.Add(Te xt:="Fred" )
itmAdd.SubItems(1) = Format(CDate("1997/05/17") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
Set itmAdd = ListView1.ListItems.Add(Te xt:="Anne" )
itmAdd.SubItems(1) = Format(CDate("1997/04/01") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
End Sub
Private Sub ListView1_ColumnClick(ByVa l 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(lngIt em, ListView1, 3).Text
Next
End Sub
Private Sub Form_Load()
Dim clmAdd As ColumnHeader
Dim itmAdd As ListItem
'Add two Column Headers to the ListView control
Set clmAdd = ListView1.ColumnHeaders.Ad
Set clmAdd = ListView1.ColumnHeaders.Ad
'Invisible column to hold the correct index number.
Set clmAdd = ListView1.ColumnHeaders.Ad
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(Te
itmAdd.SubItems(1) = Format(CDate("1997/05/07")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
Set itmAdd = ListView1.ListItems.Add(Te
itmAdd.SubItems(1) = Format(CDate("1997/04/08")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
Set itmAdd = ListView1.ListItems.Add(Te
itmAdd.SubItems(1) = Format(CDate("1997/05/29")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
Set itmAdd = ListView1.ListItems.Add(Te
itmAdd.SubItems(1) = Format(CDate("1997/05/17")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
Set itmAdd = ListView1.ListItems.Add(Te
itmAdd.SubItems(1) = Format(CDate("1997/04/01")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
End Sub
Private Sub ListView1_ColumnClick(ByVa
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(lngIt
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(lngPa ram 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(baBuff er, 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(lngIn dex 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(baBuffe r, vbUnicode), lngLength))
Set ListView_GetListItem = lvIn.ListItems(lngRealInde x)
End Function
'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(lngPa
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(baBuff
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(lngIn
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(baBuffe
Set ListView_GetListItem = lvIn.ListItems(lngRealInde
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...
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.Ad d(Text:="N ame")
Set clmAdd = ListView1.ColumnHeaders.Ad d(Text:="D ate")
'Invisible column to hold the correct index number.
Set clmAdd = ListView1.ColumnHeaders.Ad d(Text:="I ndex")
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(Te xt:="Joe")
itmAdd.SubItems(1) = Format(CDate("1997/05/07") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
Set itmAdd = ListView1.ListItems.Add(Te xt:="Sally ")
itmAdd.SubItems(1) = Format(CDate("1997/04/08") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
Set itmAdd = ListView1.ListItems.Add(Te xt:="Bill" )
itmAdd.SubItems(1) = Format(CDate("1997/05/29") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
Set itmAdd = ListView1.ListItems.Add(Te xt:="Fred" )
itmAdd.SubItems(1) = Format(CDate("1997/05/17") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
Set itmAdd = ListView1.ListItems.Add(Te xt:="Anne" )
itmAdd.SubItems(1) = Format(CDate("1997/04/01") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
End Sub
Private Sub ListView1_ColumnClick(ByVa l 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(lngIte m).SubItem s(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(lngIt em, ListView1, 3).Text
Next
ListView1.Refresh
End Sub
Private Sub Form_Load()
Dim clmAdd As ColumnHeader
Dim itmAdd As ListItem
'Add two Column Headers to the ListView control
Set clmAdd = ListView1.ColumnHeaders.Ad
Set clmAdd = ListView1.ColumnHeaders.Ad
'Invisible column to hold the correct index number.
Set clmAdd = ListView1.ColumnHeaders.Ad
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(Te
itmAdd.SubItems(1) = Format(CDate("1997/05/07")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
Set itmAdd = ListView1.ListItems.Add(Te
itmAdd.SubItems(1) = Format(CDate("1997/04/08")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
Set itmAdd = ListView1.ListItems.Add(Te
itmAdd.SubItems(1) = Format(CDate("1997/05/29")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
Set itmAdd = ListView1.ListItems.Add(Te
itmAdd.SubItems(1) = Format(CDate("1997/05/17")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
Set itmAdd = ListView1.ListItems.Add(Te
itmAdd.SubItems(1) = Format(CDate("1997/04/01")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
End Sub
Private Sub ListView1_ColumnClick(ByVa
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(lngIte
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(lngIt
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.
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.Ad d(Text:="N ame")
Set clmAdd = ListView1.ColumnHeaders.Ad d(Text:="D ate")
'Invisible column to hold the correct index number.
Set clmAdd = ListView1.ColumnHeaders.Ad d(Text:="I ndex")
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.C ount + 1), "Joe")
itmAdd.SubItems(1) = Format(CDate("1997/05/07") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
Set itmAdd = ListView1.ListItems.Add(, "A" & CStr(ListView1.ListItems.C ount + 1), "Sally")
itmAdd.SubItems(1) = Format(CDate("1997/04/08") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
Set itmAdd = ListView1.ListItems.Add(, "A" & CStr(ListView1.ListItems.C ount + 1), "Bill")
itmAdd.SubItems(1) = Format(CDate("1997/05/29") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
Set itmAdd = ListView1.ListItems.Add(, "A" & CStr(ListView1.ListItems.C ount + 1), "Fred")
itmAdd.SubItems(1) = Format(CDate("1997/05/17") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
Set itmAdd = ListView1.ListItems.Add(, "A" & CStr(ListView1.ListItems.C ount + 1), "Anne")
itmAdd.SubItems(1) = Format(CDate("1997/04/01") , "Short Date")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C ount)
End Sub
Private Sub ListView1_ColumnClick(ByVa l 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(lngIt em, ListView1, 3).Text
Next
ListView1.Refresh
End Sub
Private Sub Form_Load()
Dim clmAdd As ColumnHeader
Dim itmAdd As ListItem
'Add two Column Headers to the ListView control
Set clmAdd = ListView1.ColumnHeaders.Ad
Set clmAdd = ListView1.ColumnHeaders.Ad
'Invisible column to hold the correct index number.
Set clmAdd = ListView1.ColumnHeaders.Ad
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.C
itmAdd.SubItems(1) = Format(CDate("1997/05/07")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
Set itmAdd = ListView1.ListItems.Add(, "A" & CStr(ListView1.ListItems.C
itmAdd.SubItems(1) = Format(CDate("1997/04/08")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
Set itmAdd = ListView1.ListItems.Add(, "A" & CStr(ListView1.ListItems.C
itmAdd.SubItems(1) = Format(CDate("1997/05/29")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
Set itmAdd = ListView1.ListItems.Add(, "A" & CStr(ListView1.ListItems.C
itmAdd.SubItems(1) = Format(CDate("1997/05/17")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
Set itmAdd = ListView1.ListItems.Add(, "A" & CStr(ListView1.ListItems.C
itmAdd.SubItems(1) = Format(CDate("1997/04/01")
itmAdd.SubItems(2) = CStr(ListView1.ListItems.C
End Sub
Private Sub ListView1_ColumnClick(ByVa
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(lngIt
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(lngPa ram 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(baBuff er, 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(lngIn dex 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(strRealInde x)
lngLength = SendMessage(lvIn.hWnd, LVM_FIRST, lngIndex, _
VarPtr(objItem))
End Function
'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(lngPa
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(baBuff
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(lngIn
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(strRealInde
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...
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.
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.Ind ex
Caption = Caption & " " & Item.Index
End Sub
2 diff. values!
It seems my comment about 'Refresh' was wrong.
Private Sub ListView1_ItemClick(ByVal Item As ListItem)
Caption = ListView1.SelectedItem.Ind
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.
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.