I'm looking to make a Listview sortable based on which header column the user clicks on. To do this, I'm using the LV_ITEMCHANGED event on the form and the pointer to lpNMV.iItem. However, whenever I click on the control the .iItem only tells me which row of the control BEYOND the header I've clicked.
Is there any way to know when a user a) clicks on the header and b) which column of the header the user clicked?
Thanks!
Hi Craig,
You need to deal with the LVN_COLUMNCLICK notification.
Quote
Pointer to an NM_LISTVIEW structure. The iItem member is - 1, and the iSubItem member identifies the column. All other members are zero.
Function FORM1_LISTVIEW1_LVN_COLUMNCLICK ( _
ControlIndex As Long, _ ' index in Control Array
hWndForm As Dword, _ ' handle of Form
hWndControl As Dword, _ ' handle of Control
ByVal lpNMV As NM_LISTVIEW Ptr _ ' pointer to NM_LISTVIEW
) As Long
' iSubItem contains the zero based column number that was clicked.
MsgBox "Column:" & Str$(@lpNMV.iSubItem + 1) & " clicked."
End Function
Here is some code that may help you in your sorting.....
Global gSortColumn As Long
'------------------------------------------------------------------------------------------------------------------------
Function FORM1_LISTVIEW1_LVN_COLUMNCLICK ( _
ControlIndex As Long, _ ' index in Control Array
hWndForm As Dword, _ ' handle of Form
hWndControl As Dword, _ ' handle of Control
ByVal lpNMV As NM_LISTVIEW Ptr _ ' pointer to NM_LISTVIEW
) As Long
' iSubItem contains the zero based column number that was clicked.
MsgBox "Column:" & Str$(@lpNMV.iSubItem + 1) & " clicked."
gSortColumn = @lpNMV.iSubItem
ListView_SortItemsEx hWndControl, CodePtr(ListView_CompareFunc), hWndControl
End Function
'------------------------------------------------------------------------------------------------------------------------
Function ListView_CompareFunc( ByVal index1 As Long, _
ByVal index2 As Long, _
ByVal hWndListView As Long _
) As Long
Local zItem1 As Asciiz * %MAX_PATH
Local zItem2 As Asciiz * %MAX_PATH
Local nCol As Long
Local nNumeric As Long
' Determine what type of sort (numeric or character) to do depending on the column clicked on.
Select Case gSortColumn
Case 0: nNumeric = %TRUE
Case 1: nNumeric = %FALSE
Case 2: nNumeric = %FALSE
End Select
ListView_GetItemText hWndListView, index1, nCol, zItem1, %MAX_PATH
ListView_GetItemText hWndListView, index2, nCol, zItem2, %MAX_PATH
If nNumeric Then
If Val(zItem1) < Val(zItem2) Then
Function = -1: Exit Function
End If
If Val(zItem1) > Val(zItem2) Then
Function = +1: Exit Function
End If
If Val(zItem1) = Val(zItem2) Then
Function = 0: Exit Function
End If
Else
zItem1 = LTrim$( zItem1 ): zItem2 = LTrim$( zItem2 )
Function = Lstrcmpi( zItem1, zItem2 )
End If
End Function
Is there any way to ensure that it's only the header that the user clicked?
Thanks much for the response!
I don't understand your question? The LVN_COLUMNCLICK notification is only fired when the user clicks on the column header so no other tests are required to determine where the user has clicked. It won't get executed if the user simply clicks on any of the rows in the ListView.
Sorry, didn't realize that. Looks like I have my solution then. Thank you!
Hi Paul,
I tried this code that you wrote last december; it doesn't seem to work for me; is it normal to pass twice the handle hWndControl ?
ListView_SortItemsEx hWndControl, CodePtr(ListView_CompareFunc), hWndControl
Jean-Pierre
The second hWndControl is passed as an application defined parameter. It is basically any 32-bit value that the user wants to send to the sorting callback procedure. In this case, I guess that I needed the control's window handle in the sorting callback. Here is the documentation: http://msdn.microsoft.com/en-us/library/bb775133(VS.85).aspx
Paul,
Thank you for pointing me to the right direction.
I updated your code:
- I removed the global variable gSortColumn which is no longer necessary; now the column is passed to the sorting callback procedure.
- I completed the ListView_CompareFunc() so the type of sort (date, numeric or character) is detected automatically; see the new functions IsDate() and IsNumeric().
Last edit: June 24
- I added a new global variable gListViewSort to define the sort order.
- I added a new sub ShowHeaderIcon() to draw a draw up-arrow or down-arrow in the ListView header depending on the sort order.
Here is the code:
'--------------------------------------------------------------------------------
Function FORM1_LISTVIEW1_LVN_COLUMNCLICK ( _
ControlIndex As Long, _ ' index in Control Array
hWndForm As Dword, _ ' handle of Form
hWndControl As Dword, _ ' handle of Control
ByVal lpNMV As NM_LISTVIEW Ptr _ ' pointer to NM_LISTVIEW
) As Long
' iSubItem contains the zero based column number that was clicked.
If gListViewSort = %SO_ASCENDING Then gListViewSort = %SO_DESCENDING Else gListViewSort = %SO_ASCENDING
' to suppress the arrow on the previous sorted column
If gListViewLastSortedColumn <> - 1 Then ShowHeaderIcon(HWND_FORM1_LISTVIEW1, gListViewLastSortedColumn, 0)
' sort the ListView on the selected column
ListView_SortItemsEx hWndControl, CodePtr(ListView_CompareFunc), @lpNMV.iSubItem
' Draws a down-arrow or up-arrow on this item.
ShowHeaderIcon(HWND_FORM1_LISTVIEW1, @lpNMV.iSubItem, gListViewSort)
' to save the column number that has been sorted
gListViewLastSortedColumn = @lpNMV.iSubItem
' to select the first line of the ListView
FF_ListView_SetSelectedItem (HWND_FORM1_LISTVIEW1, 0)
End Function
Function IsDate(pValue As Asciiz * %MAX_PATH) As Long
If Verify(pValue, "0123456789/") = 0 And Instr(pValue, "/") <> 0 Then Function = %TRUE Else Function = %FALSE
End Function
Function IsNumeric(pValue As Asciiz * %MAX_PATH) As Long
If Verify(pValue, "0123456789.,+- ") = 0 Then Function = %TRUE Else Function = %FALSE
End Function
'----------------------------------------------------------------------------------------------------
'* Dependence on global variables: gListViewSort (Sort Order, either %SO_ASCENDING or %SO_DESCENDING)
'----------------------------------------------------------------------------------------------------
Function ListView_CompareFunc( ByVal index1 As Long, _
ByVal index2 As Long, _
ByVal pColumn As Long _
) As Long
Local zItem1 As Asciiz * %MAX_PATH
Local zItem2 As Asciiz * %MAX_PATH
Local lDate1 As Asciiz * 10
Local lDate2 As Asciiz * 10
Local lNumeric1 As Double
Local lNumeric2 As Double
Local lReturn As Long
' get the value from the ListView
ListView_GetItemText HWND_FORM1_LISTVIEW1, index1, pColumn, zItem1, %MAX_PATH
ListView_GetItemText HWND_FORM1_LISTVIEW1, index2, pColumn, zItem2, %MAX_PATH
'--------------------------------------------------------------------------------------------
' Determine what type of sort (Date, Numeric or Character) depending on the column clicked on
'--------------------------------------------------------------------------------------------
' Date
If IsDate(zItem1) And IsDate(zItem2) Then
lDate1 = DateAff10ToFile(zItem1)
lDate2 = DateAff10ToFile(zItem2)
lReturn = Lstrcmpi(lDate1, lDate2)
Else
' Numeric
If IsNumeric(zItem1) And IsNumeric(zItem2) Then
lNumeric1 = Val(zItem1)
lNumeric2 = Val(zItem2)
If lNumeric1 < lNumeric2 Then lReturn = -1 Else If lNumeric1 > lNumeric2 Then lReturn = +1 Else lReturn = 0
Else
' Character
zItem1 = LTrim$(zItem1)
zItem2 = LTrim$( zItem2 )
lReturn = Lstrcmpi(zItem1, zItem2)
End If
End If ' If IsDate(zItem1) And IsDate(zItem2) Then
If gListViewSort = %SO_ASCENDING Then Function = lReturn Else Function = -lReturn
End Function
Sub ShowHeaderIcon(hWndControl As Dword, ByVal pColumn As Long, ByVal pSortOrder As Long)
Local hHeader As Dword
Local HDI As HD_ITEM
hHeader = SendMessage(hWndControl, %LVM_GetHeader, 0, ByVal 0)
HDI.mask = %HDI_FORMAT
Header_GetItem(hHeader, pColumn, HDI)
' depending on the SortOrder (%SO_NONE, %SO_ASCENDING, %SO_DESCENDING)
Select Case pSortOrder
Case 0:
' remove down-arrow or up-arrow
HDI.fmt = HDI.fmt And Not (%HDF_SORTDOWN Or %HDF_SORTUP)
Case %SO_ASCENDING:
' Draws an up-arrow on this item
HDI.fmt = HDI.fmt And Not %HDF_SORTDOWN
HDI.fmt = HDI.fmt Or %HDF_SORTUP
Case %SO_DESCENDING
'Draws a down-arrow On this item
HDI.fmt = HDI.fmt And Not %HDF_SORTUP
HDI.fmt = HDI.fmt Or %HDF_SORTDOWN
End Select
Header_SetItem(hHeader, pColumn, HDI)
End Sub
This code works pretty well; I hope it will be helpfull to other users.
Thanks
Jean-Pierre