ogeiz Posted August 14, 2009 Share Posted August 14, 2009 (edited) Hi!Having a listview where the 1st column contains only checkboxes.When clicking on the header "Column1", I'm trying to sort the rows according their checkbox state, i.e. group the checked and group the unchecked ones.The code below contains the callback function "LVSort" that is registered via GUICtrlRegisterListViewSort() and shall compare two items for their ordering.The function LVSort gets the arguments $nItem1, $nItem2 that (somehow) refer the listview items.The UDFs _GUICtrlListView_GetItemText() and _GUICtrlListView_GetItemChecked() cannot be used here to get the content of the items to sort, as $nItem1 and $nItem2 are not the required item index. Hmpf.Fortunately as example the UDF help pages contain the definition of func GetSubItemText() to read the (sub)item strings in that case.Kudos to its author!With small improvements I use it for sorting of column 2 and 3.But I cannot think a way how to adapt this function for reading the checkbox state.Nevertheless I see 3 options to proceed:use a function to read the state (analogous to GetSubItemText)find a way to map argument $nItem1 to listview item index, then use _GUICtrlListView_GetItemXXXmark the listview items before inserting them in the listview, then use the marker for to reference the items. _GUICtrlListView_MapIDToIndex seems to be a good candidate, but how can I then access the state of the checkboxes? Using an external array to mirror the check state seems to be a waste of resources to me.I ask you for suggestions or solutions.Background info to the LVSort's arguments is welcome, too.Thanks,ogeiz(BTW: I'm running Environment = 3.3.0.0 under WIN_XP/Service Pack 2 X86 )EDIT: solved by workaround, see #5This implementation has problems in the lines marked with: ### Wrongexpandcollapse popup; ******************************************************* ; Example 1 - sorting different columns ; ******************************************************* #include <WindowsConstants.au3> #include <ListViewConstants.au3> #include <GuiListView.au3> #include <GUIConstants.au3> #include <GuiListView.au3> Opt("GUIOnEventMode", 1) Dim $nSortDir = 1 Dim $bSet = 0 Dim $nCol = -1 $hGUI = GUICreate("Test", 300, 200) GUISetOnEvent($GUI_EVENT_CLOSE, "_Terminate") $lv = GUICtrlCreateListView("Column1|Col2|Col3", 10, 10, 280, 180, -1, $LVS_EX_CHECKBOXES) GUICtrlSetOnEvent($lv, "_Setsort") GUICtrlRegisterListViewSort($lv, "LVSort"); Register the function "SortLV" for the sorting callback $lvi1 = GUICtrlCreateListViewItem("|AAA|10.05.2003", $lv) _GUICtrlListView_SetItemChecked($lv, 0, True) $lvi2 = GUICtrlCreateListViewItem("|BBB|11.05.2001", $lv) _GUICtrlListView_SetItemChecked($lv, 1, False) $lvi3 = GUICtrlCreateListViewItem("|CCC|12.05.2002", $lv) _GUICtrlListView_SetItemChecked($lv, 2, True) $lvi4 = GUICtrlCreateListViewItem("|DDD|13.05.2000", $lv) _GUICtrlListView_SetItemChecked($lv, 3, False) GUISetState() While 1 Sleep(10) WEnd Func _Terminate() Exit EndFunc ;==>_Terminate Func _Setsort() $bSet = 0 GUICtrlSendMsg($lv, $LVM_SETSELECTEDCOLUMN, GUICtrlGetState($lv), 0) DllCall("user32.dll", "int", "InvalidateRect", "hwnd", GUICtrlGetHandle($lv), "int", 0, "int", 1) Local $state ; print current checkbox state before sorting For $i = 0 To 3 $state &= ' #' & $i & ': Col2="' & _GUICtrlListView_GetItemText($lv, $i, 1) & _ '" checked=' & 0 + _GUICtrlListView_GetItemChecked($lv, $i) Next ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : State before sorting' & $state & @CRLF) EndFunc ;==>_Setsort ; sorting callback funtion Func LVSort($hWnd, $nItem1, $nItem2, $nColumn) Local $nSort ; Switch the sorting direction If Not $bSet Then If $nColumn = $nCol Then $nSortDir = $nSortDir * - 1 Else $nSortDir = 1 EndIf $bSet = 1 EndIf $nCol = $nColumn ; sort depends on content of column (column starts with 0) Switch $nColumn Case 0 ; checkboxes $val1 = 0 + _GUICtrlListView_GetItemChecked($lv, $nItem1) ; ### Wrong $val2 = 0 + _GUICtrlListView_GetItemChecked($lv, $nItem2) ; ### Wrong ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') :' & _ ' $nItem1=' & $nItem1 & ' Col2="' & GetSubItemText($lv, $nItem1, 1) & '" $val1=' & $val1 & ' ' & _ ' $nItem2=' & $nItem2 & ' Col2="' & GetSubItemText($lv, $nItem2, 1) & '" $val2=' & $val2 & @CRLF) Case 1 ; text $val1 = GetSubItemText($lv, $nItem1, $nColumn) $val2 = GetSubItemText($lv, $nItem2, $nColumn) Case 2 ; dates $val1 = GetSubItemText($lv, $nItem1, $nColumn) $val2 = GetSubItemText($lv, $nItem2, $nColumn) $val1 = StringRight($val1, 4) & StringMid($val1, 4, 2) & StringLeft($val1, 2) $val2 = StringRight($val2, 4) & StringMid($val2, 4, 2) & StringLeft($val2, 2) EndSwitch $nResult = 0 ; No change of item1 and item2 positions If $val1 < $val2 Then $nResult = -1 ; Put item2 before item1 ElseIf $val1 > $val2 Then $nResult = 1 ; Put item2 behind item1 EndIf $nResult = $nResult * $nSortDir Return $nResult EndFunc ;==>LVSort ; Retrieve the text of a listview item in a specified column Func GetSubItemText($nCtrlID, $nItemID, $nColumn) Local $stLvfi = DllStructCreate("uint;ptr;int;int[2];int") Local $nIndex, $stBuffer, $stLvi, $sItemText DllStructSetData($stLvfi, 1, $LVFI_PARAM) DllStructSetData($stLvfi, 3, $nItemID) $stBuffer = DllStructCreate("char[260]") $nIndex = GUICtrlSendMsg($nCtrlID, $LVM_FINDITEM, -1, DllStructGetPtr($stLvfi)); $stLvi = DllStructCreate("uint;int;int;uint;uint;ptr;int;int;int;int") DllStructSetData($stLvi, 1, $LVIF_TEXT) DllStructSetData($stLvi, 2, $nIndex) DllStructSetData($stLvi, 3, $nColumn) DllStructSetData($stLvi, 6, DllStructGetPtr($stBuffer)) DllStructSetData($stLvi, 7, 260) GUICtrlSendMsg($nCtrlID, $LVM_GETITEMA, 0, DllStructGetPtr($stLvi)); $sItemText = DllStructGetData($stBuffer, 1) $stLvi = 0 $stLvfi = 0 $stBuffer = 0 Return $sItemText EndFunc ;==>GetSubItemText Edited August 28, 2009 by ogeiz Link to comment Share on other sites More sharing options...
ogeiz Posted August 26, 2009 Author Share Posted August 26, 2009 Hello forum Nobody there, who has ever tried to sort checkboxes in a listview? Each hint would be appreciated... BR ogeiz Link to comment Share on other sites More sharing options...
BugFix Posted August 27, 2009 Share Posted August 27, 2009 I've solved it sometimes so (but i don't find my script). Thats the way: - add a new column with width=0 (following named as col_state) - add an function that checks the state of checkboxes if an mouseclick occured - write in col_state an '1' if is checked and an '0' or empty string otherwise - now you can sort by col_state Don't check the state in an loop before you want to sort. It needs a lot of time to read state and write '0' or '1'. Thats why use the mouse event. Best Regards BugFix Link to comment Share on other sites More sharing options...
ogeiz Posted August 27, 2009 Author Share Posted August 27, 2009 (edited) Hi BugFix Thanks for the hint. Storing the info twice, one version sortable but invisible, and the checkboxes as user interface is a clever workaround. But is it really impossible for the user to widen the 0-width column? ogeiz Edit: I've tried and run into two problems: - with function _GUICtrlListView_SetColumnWidth($hWnd, $iCol, $iWidth) I can't change the second's column width, the help says: "For list-view mode, this (=$iCol) parameter must be set to zero". With iCol=0, I will make the column of the checkboxes invisible. - the zeroed column an be opened by the user, when he badly puts the mouse pointer on the header column sepatators. Any idea? How to show checkboxes in the second column? Edited August 27, 2009 by ogeiz Link to comment Share on other sites More sharing options...
ogeiz Posted August 28, 2009 Author Share Posted August 28, 2009 Implemented the workaround suggested by BugFix, see source below.Use a hidden column with numeric values for sorting the column of checkboxes.The function LVSort contains a few improvements in relation to the GUICtrlRegisterListViewSort() function reference example:fewer compares with each call of the function, fewer statementseach sort will reverse the sequence of equal values, too. So the second click on the column header will now really revert the whole list, and not only the different items.some bells and whisles: show sort direction by arrow in list header, mark sorted column by grey backgroundMay the source be with you...ogeizHere is the corrected, working script for all people interested in it:expandcollapse popup#cs ---------------------------------------------------------------------------- AutoIt Version: 3.3.0.0 Author: ogeiz Script Function: Improved example for GUICtrlRegisterListViewSort - sort column with checkboxes. #ce ---------------------------------------------------------------------------- #include <WindowsConstants.au3> #include <ListViewConstants.au3> #include <GuiListView.au3> #include <GUIConstants.au3> #include <GuiListView.au3> Opt("GUIOnEventMode", 1) Dim $nSortDir = 1 ; toggle for sort up/down Dim $bSet = 0 ; detect the 1st call of LVSort() within a sorting sequence Dim $nCol = -1 ; preserve last column to detect 2nd click (for reverse direction) $hGUI = GUICreate("Test", 300, 200) GUISetOnEvent($GUI_EVENT_CLOSE, "_Terminate") ; create an zero-width column between 'Column1' and 'Col2' just for sorting the checkboxes $lv = GUICtrlCreateListView("Column1||Col2|Col3", 10, 10, 280, 180, -1, BitOR($LVS_EX_CHECKBOXES, $LVS_EX_HEADERDRAGDROP)) GUICtrlSetOnEvent($lv, "_Setsort") ; clicking the column header will start the sort GUICtrlRegisterListViewSort($lv, "LVSort") ; Register the function "SortLV" as the sorting callback ; Note: the hidden column #1 contains [x]<->'0', [ ]<->'1' to ease sorting active checkboxes at top with 1st click $lvi1 = GUICtrlCreateListViewItem("|0|AAA|10.05.2003", $lv) _GUICtrlListView_SetItemChecked($lv, 0, True) $lvi2 = GUICtrlCreateListViewItem("|1|BBB|11.05.2001", $lv) _GUICtrlListView_SetItemChecked($lv, 1, False) $lvi3 = GUICtrlCreateListViewItem("|0|CCC|12.05.2002", $lv) _GUICtrlListView_SetItemChecked($lv, 2, True) $lvi4 = GUICtrlCreateListViewItem("|1|DDD|13.05.2000", $lv) _GUICtrlListView_SetItemChecked($lv, 3, False) ; shrink the (hidden) sorting column #1 to 0 pt width _GUICtrlListView_SetColumnWidth($lv, 1, 0) ; use WM_NOTIFY to detect change of checkbox state and update hidden column GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY") GUISetState() While 1 Sleep(10) WEnd ;------------------------------------------------------------------------------------------------- Func _Terminate() Exit EndFunc ;==>_Terminate ; some initializations when starting the sort Func _Setsort() $bSet = 0 ; initialize for new sorting ; bells and whistles (I): ; mark the sorted column with a grey rectangle GUICtrlSendMsg($lv, $LVM_SETSELECTEDCOLUMN, GUICtrlGetState($lv), 0) DllCall("user32.dll", "int", "InvalidateRect", "hwnd", GUICtrlGetHandle($lv), "int", 0, "int", 1) ; bells and whistles (II): ; create an arrow in the listview header Local $iFormat Local Const $hHeader = _GUICtrlListView_GetHeader($lv) ; clear existing arrows For $x = 0 To _GUICtrlHeader_GetItemCount($hHeader) - 1 $iFormat = _GUICtrlHeader_GetItemFormat($hHeader, $x) If BitAND($iFormat, $HDF_SORTDOWN) Then _GUICtrlHeader_SetItemFormat($hHeader, $x, BitXOR($iFormat, $HDF_SORTDOWN)) ElseIf BitAND($iFormat, $HDF_SORTUP) Then _GUICtrlHeader_SetItemFormat($hHeader, $x, BitXOR($iFormat, $HDF_SORTUP)) EndIf Next ; set arrow in current column Local $nColumn = GUICtrlGetState($lv) $iFormat = _GUICtrlHeader_GetItemFormat($hHeader, $nColumn) If $nSortDir == 1 And $nCol == $nColumn Then ; ascending _GUICtrlHeader_SetItemFormat($hHeader, $nColumn, BitOR($iFormat, $HDF_SORTUP)) Else ; descending _GUICtrlHeader_SetItemFormat($hHeader, $nColumn, BitOR($iFormat, $HDF_SORTDOWN)) EndIf EndFunc ;==>_Setsort ;=============================================================================== ; ; Function Name..: LVSort ; Description....: Sort listview columns - checkboxes, text, dates. ; Parameters.....: $hWnd - The controlID of the listview control for which the callback function is used. ; $nItem1 - The lParam value of the first item (by default the item controlID). ; $nItem2 - The lParam value of the second item (by default the item controlID). ; $nColumn - The column that was clicked for sorting (the first column number is 0). ; Requirements...: use as callback function for GUICtrlRegisterListViewSort() ; Return Values..: -1 - 1st item should precede the 2nd. ; 0 - No Change. ; 1 - 1st item should follow the 2nd. ; Author.........: see example of function reference GUICtrlRegisterListViewSort() ; Modified.......: ogeiz: sort checkboxes, optimized direction checks, stable sort (revert sequence of equal values ; with direction change to permit sort sequences: least important column to most important column) ; ;=============================================================================== Func LVSort($hWnd, $nItem1, $nItem2, $nColumn) Local $val1, $val2 ; Switch the sorting direction If Not $bSet Then If $nColumn = $nCol Then $nSortDir = -$nSortDir Else $nSortDir = 1 EndIf $bSet = 1 EndIf $nCol = $nColumn ; sort depends on content of column (column starts with 0) Switch $nColumn Case 0, 1 ; checkboxes (column 0) => use always column 1 to sort $val1 = GetSubItemText($lv, $nItem1, 1) ; use '[x]'==0 to sort at top and '[ ]'==1 at bottom $val2 = GetSubItemText($lv, $nItem2, 1) Case 2 ; text $val1 = GetSubItemText($lv, $nItem1, $nColumn) $val2 = GetSubItemText($lv, $nItem2, $nColumn) Case 3 ; dates - reorder from dd.mm.yyyy -> yyyymmdd, then sort by value $val1 = GetSubItemText($lv, $nItem1, $nColumn) $val2 = GetSubItemText($lv, $nItem2, $nColumn) $val1 = StringRight($val1, 4) & StringMid($val1, 4, 2) & StringLeft($val1, 2) $val2 = StringRight($val2, 4) & StringMid($val2, 4, 2) & StringLeft($val2, 2) EndSwitch If $val1 < $val2 Or ($val1 == $val2 And $nItem1 < $nItem2) Then Return -$nSortDir ; Put item2 before item1 Else Return $nSortDir ; Put item2 behind item1 EndIf EndFunc ;==>LVSort ; Retrieve the text of a listview item in a specified column Func GetSubItemText($nCtrlID, $nItemID, $nColumn) Local $stLvfi = DllStructCreate("uint;ptr;int;int[2];int") Local $nIndex, $stBuffer, $stLvi, $sItemText DllStructSetData($stLvfi, 1, $LVFI_PARAM) DllStructSetData($stLvfi, 3, $nItemID) $stBuffer = DllStructCreate("char[260]") $nIndex = GUICtrlSendMsg($nCtrlID, $LVM_FINDITEM, -1, DllStructGetPtr($stLvfi)); $stLvi = DllStructCreate("uint;int;int;uint;uint;ptr;int;int;int;int") DllStructSetData($stLvi, 1, $LVIF_TEXT) DllStructSetData($stLvi, 2, $nIndex) DllStructSetData($stLvi, 3, $nColumn) DllStructSetData($stLvi, 6, DllStructGetPtr($stBuffer)) DllStructSetData($stLvi, 7, 260) GUICtrlSendMsg($nCtrlID, $LVM_GETITEMA, 0, DllStructGetPtr($stLvi)); $sItemText = DllStructGetData($stBuffer, 1) $stLvi = 0 $stLvfi = 0 $stBuffer = 0 Return $sItemText EndFunc ;==>GetSubItemText ; use WM_NOTIFY to detect the state change of a checkbox and adapt the value in hidden column accordingly Func WM_NOTIFY($hWnd, $iMsg, $iwParam, $ilParam) #forceref $hWnd, $iMsg, $iwParam Local $tNMHDR, $hWndFrom, $iCode Local Const $h_lv = GUICtrlGetHandle($lv) $tNMHDR = DllStructCreate($tagNMHDR, $ilParam) $hWndFrom = HWnd(DllStructGetData($tNMHDR, "hWndFrom")) $iCode = DllStructGetData($tNMHDR, "Code") Switch $hWndFrom Case $h_lv ; need Handle for detecting listview $h_lv = GUICtrlGetHandle($h_lv) Switch $iCode Case $LVN_ITEMCHANGED ; An listview item has changed Local $tInfo = DllStructCreate($tagNMLISTVIEW, $ilParam) Local $iItem = DllStructGetData($tInfo, "Item") _GUICtrlListView_SetItem($lv, 1 - _GUICtrlListView_GetItemChecked($h_lv, $iItem), $iItem, 1) ; No return value EndSwitch EndSwitch EndFunc ;==>WM_NOTIFY Link to comment Share on other sites More sharing options...
BugFix Posted August 29, 2009 Share Posted August 29, 2009 (edited) Hi, i've changed a little bit. Now the user can't change the column width of zero-width-column. Func WM_NOTIFY($hWnd, $iMsg, $iwParam, $ilParam) #forceref $hWnd, $iMsg, $iwParam Local $tNMHDR, $hWndFrom, $iCode Local Const $h_lv = GUICtrlGetHandle($lv) $tNMHDR = DllStructCreate($tagNMHDR, $ilParam) $hWndFrom = HWnd(DllStructGetData($tNMHDR, "hWndFrom")) $iCode = DllStructGetData($tNMHDR, "Code") Switch $hWndFrom Case $h_lv ; need Handle for detecting listview $h_lv = GUICtrlGetHandle($h_lv) Switch $iCode ; ####### build in this part ################################################################# Case -12 ; User has changed column width If _GUICtrlListView_GetColumnWidth($h_lv, 1) <> 0 Then _ _GUICtrlListView_SetColumnWidth($h_lv, 1, 0) ; width of column 1 reset to zero ; ############################################################################################ Case $LVN_ITEMCHANGED ; An listview item has changed Local $tInfo = DllStructCreate($tagNMLISTVIEW, $ilParam) Local $iItem = DllStructGetData($tInfo, "Item") _GUICtrlListView_SetItem($lv, 1 - _GUICtrlListView_GetItemChecked($h_lv, $iItem), $iItem, 1) ; No return value EndSwitch EndSwitch EndFunc ;==>WM_NOTIFY Edited August 29, 2009 by BugFix Skysnake 1 Best Regards BugFix Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now