Jump to content

Sorting a ListView where columns have different sort sequences


Recommended Posts

My script lists all files in a path, showing filename, extension, size, and path. While filename, extension and path should use a string-sort, size should sort numerically. (Size is comma-separated). Looking at the examples in the Help, I see how to sort all columns using the same sort sequence, but how can I have one sort sequence for one and column, and a different one for the others?

I have searched the forum, but I still have no clear idea to how to do it. In the Help, I have been particularly looking at the examples for _GuiListView_SortItems().

My script follows.

#include <GUIConstantsEx.au3>
#include <GuiListView.au3>
#include "MsgBoxUDF\MsgBoxUDF.au3"  ; Thanks to Melba23
Opt('MustDeclareVars',1)

Global Enum $kFnam,$kExt,$kSize,$kDateTime,$kPath

If UBound($cmdline)=1 Then
    MsgBox($MB_ICONINFORMATION,'Information','One parameter, a path, is required')
    Exit 1
EndIf

If Not FileExists($cmdline[1]) Then
    _MsgBox($MB_ICONERROR,'Error','Parameter 1, "'&$cmdline[1]&'", must be a drive or directory')
    Exit 1
EndIf

Local $sPath = $cmdline[1]

If StringRight($sPath,1)='\' Then
    $sPath = StringTrimRight($sPath,1)
EndIf

main($sPath)

Func main($sPath)
    Local $FormFF = GUICreate($sPath,@DesktopWidth/2,@DesktopHeight-57,1,-1)
    Local $idListview = _GUICtrlListView_Create($FormFF,"",8,8,@DesktopWidth/2-16,@DesktopHeight-150)
    _GUICtrlListView_InsertColumn($idListview, 0, "Filename", 400)
    _GUICtrlListView_InsertColumn($idListview, 1, "Ext", 50)
    _GUICtrlListView_InsertColumn($idListview, 2, "Size",70)
    _GUICtrlListView_InsertColumn($idListview, 3, 'Date time',100)
    _GUICtrlListView_InsertColumn($idListview, 4, "Path", 385)

    _GUICtrlListView_JustifyColumn($idListview, $kSize,1)   ; right align


    Local $idLbl = GUICtrlCreateLabel('',20,@DesktopHeight-135,@DesktopWidth/2-20,20)
    Local $btnRemove = GUICtrlCreateButton("Remove", 20,@DesktopHeight-110,60,30)
    Local $btnClose = GUICtrlCreateButton("Close", @DesktopWidth/2-100,@DesktopHeight-110,50,25)

    GUISetState(@SW_SHOW)

    _GUICtrlListView_SetExtendedListViewStyle($idListview,BitOR($LVS_EX_CHECKBOXES,$LVS_EX_GRIDLINES))

    Local $nTotBytes=0
    PopulateListView($idListview,$idLbl,$cmdLine[1],  $nTotBytes)

    For $i = $kFnam To $kPath
        _GUICtrlListView_SetColumnWidth($idListview,$i,$LVSCW_AUTOSIZE)
    Next

    Local $t = AddThousandsSeparator(_GUICtrlListView_GetItemCount($idListview))&' files ('&  _
        AddThousandsSeparator($nTotBytes)&' bytes)'
    GUICtrlSetData($idLbl,$t)

    Local $nMsg

    While 1
        $nMsg = GUIGetMsg()
        Switch $nMsg
            Case $GUI_EVENT_CLOSE,$btnClose
                ExitLoop
            Case $btnRemove
                Local $bDel=False,$filspc
                If _MsgBoxEx($MB_YESNO + $MB_DEFBUTTON2 + $MB_ICONQUESTION,"","Remove checked files from backup?", _
                0,WinGetHandle('')) Then
                    For $i = _GUICtrlListView_GetItemCount($idListview)-1 To 0 Step -1
                        If _GUICtrlListView_GetItemChecked($idListview,$i) Then
                            $nTotBytes -= StringReplace(_GUICtrlListView_GetItemText($idListview,$i,$kSize),',','')
                            $filspc = _GUICtrlListView_GetItemText($idListview,$i,$kPath)
                            $filspc &= '\'&_GUICtrlListView_GetItemText($idListview,$i,$kFnam)
                            $t = _GUICtrlListView_GetItemText($idListview,$i,$kExt)
                            If $t<>'' Then
                                $filspc &= '.'&$t
                            EndIf
;~                          FileDelete($filspc) ; for now!!
                            $bDel = True
                            _GUICtrlListView_DeleteItem($idListview,$i)
                        EndIf
                    Next
                    If $bDel Then
                        Local $q = _GUICtrlListView_GetItemCount($idListview)
                        GUICtrlSetData($idLbl,AddThousandsSeparator($q)&' files ('&AddThousandsSeparator($nTotBytes)&' bytes)')

                        If $q=0 Then
                            GUICtrlSetState($btnRemove,$GUI_DISABLE)
                        EndIf
                    EndIf
                EndIf
        EndSwitch
    WEnd
    GUIDelete($FormFF)
EndFunc

Func PopulateListView($idListview,$idLbl,$sPath, ByRef $nTotBytes)
    Local $sFnamExt,$sAtts,$sDirs='',$p,$sFnam,$sExt,$a1,$nSize,$t,$nItem
    Local $iSrch = FileFindFirstFile($sPath&'\*.*')
    If $iSrch=-1 Then
        Return
    EndIf
    While True
        $sFnamExt = FileFindNextFile($iSrch)
        If @error Then
            ExitLoop
        EndIf
        $sAtts = FileGetAttrib($sPath&'\'&$sFnamExt)
        If StringInStr($sAtts,'D') Then
            $sDirs &= '?'&$sPath&'\'&$sFnamExt
        Else
            $p = StringInStr($sFnamExt,'.',0,-1)    ; last
            If $p=0 Then
                $sFnam = $sFnamExt
                $sExt = ''
            Else
                $sFnam = StringLeft($sFnamExt,$p-1)
                $sExt = StringTrimLeft($sFnamExt,$p)
            EndIf
            $nItem = _GUICtrlListView_GetItemCount($idListview)
            _GUICtrlListView_AddItem($idListview,$sFnam)
            _GUICtrlListView_AddSubItem($idListview,$nItem,$sExt,$kExt)
            $nSize = FileGetSize($sPath&'\'&$sFnamExt)
            _GUICtrlListView_AddSubItem($idListview,$nItem,AddThousandsSeparator($nsize),$kSize)
            $nTotBytes += $nSize
            $a1 = FileGetTime($sPath&'\'&$sFnamExt,$FT_MODIFIED,$FT_ARRAY)
            $t = $a1[0]&'-'&$a1[1]&'-'&$a1[2]&' '&$a1[3]&':'&$a1[4]
            _GUICtrlListView_AddSubItem($idListview,$nItem,$t,$kDateTime)
            _GUICtrlListView_AddSubItem($idListview,$nItem,$sPath,$kPath)
        EndIf
    WEnd
    FileClose($iSrch)
    GUICtrlSetData($idLbl,AddThousandsSeparator($nTotBytes)&' bytes')
    If $sDirs<>'' Then
        $sDirs = StringTrimLeft($sDirs,1)
        $a1 = StringSplit($sDirs,'?',0+2)   ; char,0-based
        For $i = 0 To UBound($a1)-1
            PopulateListView($idListview,$idLbl,$a1[$i], $nTotBytes)
        Next
    EndIf
EndFunc

Func AddThousandsSeparator($n)
    Local $s = String($n)
    Local $sRet=''
    For $i = StringLen($s) To 1 Step -3
        $sRet = StringMid($s,$i-2,3)&','&$sRet
    Next
    $sRet = StringTrimRight($sRet,1)
    If $i<>0 Then
        $sRet = StringLeft($s,3+$i)&$sRet
    EndIf
    Return $sRet
EndFunc

 

Edited by c.haslam
Spoiler

CDebug Dumps values of variables including arrays and DLL structs, to a GUI, to the Console, and to the Clipboard

 

Link to comment
Share on other sites

Try this one from the help:

#include <GUIConstantsEx.au3>
#include <GuiImageList.au3>
#include <GuiListView.au3>
#include <WindowsConstants.au3>

Global $g_idListView

Example()

Func Example()
    Local $hImage, $aIcons[3] = [0, 3, 6]
    Local $iExWindowStyle = BitOR($WS_EX_DLGMODALFRAME, $WS_EX_CLIENTEDGE)
    Local $iExListViewStyle = BitOR($LVS_EX_FULLROWSELECT, $LVS_EX_SUBITEMIMAGES, $LVS_EX_GRIDLINES, $LVS_EX_CHECKBOXES, $LVS_EX_DOUBLEBUFFER)

    GUICreate("ListView Sort", 300, 200)

    $g_idListView = GUICtrlCreateListView("Column1|Col2|Col3", 10, 10, 280, 180, -1, $iExWindowStyle)
    _GUICtrlListView_SetExtendedListViewStyle($g_idListView, $iExListViewStyle)

    ; Load images
    $hImage = _GUIImageList_Create(18, 18, 5, 3)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -7)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -12)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -3)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -4)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -5)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -6)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -9)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -10)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -11)
    _GUICtrlListView_SetImageList($g_idListView, $hImage, 1)

    _AddRow($g_idListView, "ABC|000666|10.05.2004", $aIcons)
    _AddRow($g_idListView, "DEF|444|11.05.2005", $aIcons, 1)
    _AddRow($g_idListView, "CDE|555|12.05.2004", $aIcons, 2)

    GUISetState(@SW_SHOW)

    _GUICtrlListView_RegisterSortCallBack($g_idListView)

    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop
            Case $g_idListView
                ; Kick off the sort callback
                _GUICtrlListView_SortItems($g_idListView, GUICtrlGetState($g_idListView))
        EndSwitch
    WEnd

    _GUICtrlListView_UnRegisterSortCallBack($g_idListView)
    GUIDelete()
EndFunc   ;==>Example

Func _AddRow($hWnd, $sItem, $aIcons, $iPlus = 0)
    Local $aItem = StringSplit($sItem, "|")
    Local $iIndex = _GUICtrlListView_AddItem($hWnd, $aItem[1], $aIcons[0] + $iPlus, _GUICtrlListView_GetItemCount($hWnd) + 9999)
    _GUICtrlListView_SetColumnWidth($hWnd, 0, $LVSCW_AUTOSIZE_USEHEADER)

    For $x = 2 To $aItem[0]
        _GUICtrlListView_AddSubItem($hWnd, $iIndex, $aItem[$x], $x - 1, $aIcons[$x - 1] + $iPlus)
        _GUICtrlListView_SetColumnWidth($hWnd, $x - 1, $LVSCW_AUTOSIZE)
    Next
EndFunc   ;==>_AddRow

 

Link to comment
Share on other sites

Adding a thousands separator makes sorting difficult
The easiest solution is to create a 0-width fifth column to put sizes non thousand separated, so when you click the col 2 header the sorting is done on col 5 using _GUICtrlListView_SimpleSort

#include <GUIConstantsEx.au3>
#include <GuiListView.au3>
#include <FileConstants.au3>
;#include "MsgBoxUDF\MsgBoxUDF.au3"  ; Thanks to Melba23
;Opt('MustDeclareVars',1)

Global Enum $kFnam,$kExt,$kSize,$kDateTime,$kPath,$kSize2
Global $listview

Local $sPath = @desktopdir

If StringRight($sPath,1)='\' Then
    $sPath = StringTrimRight($sPath,1)
EndIf

main($sPath)

Func main($sPath)
    Local $FormFF = GUICreate($sPath,@DesktopWidth/2,@DesktopHeight-57,1,-1)
    $listview = GUICtrlCreateListView("",8,8,@DesktopWidth/2-16,@DesktopHeight-150)
$idListview = GUICtrlGetHandle($listview)
;    Local $idListview = _GUICtrlListView_Create($FormFF,"",8,8,@DesktopWidth/2-16,@DesktopHeight-150)
    _GUICtrlListView_InsertColumn($idListview, 0, "Filename", 400)
    _GUICtrlListView_InsertColumn($idListview, 1, "Ext", 50)
    _GUICtrlListView_InsertColumn($idListview, 2, "Size",70)
    _GUICtrlListView_InsertColumn($idListview, 3, 'Date time',100)
    _GUICtrlListView_InsertColumn($idListview, 4, "Path", 385)

    _GUICtrlListView_InsertColumn($idListview, 5, "size2", 0)
    _GUICtrlListView_JustifyColumn($idListview, $kSize,1)   ; right align



    Local $idLbl = GUICtrlCreateLabel('',20,@DesktopHeight-135,@DesktopWidth/2-20,20)
    Local $btnRemove = GUICtrlCreateButton("Remove", 20,@DesktopHeight-110,60,30)
    Local $btnClose = GUICtrlCreateButton("Close", @DesktopWidth/2-100,@DesktopHeight-110,50,25)

    GUISetState(@SW_SHOW)

    _GUICtrlListView_SetExtendedListViewStyle($idListview,BitOR($LVS_EX_CHECKBOXES,$LVS_EX_GRIDLINES))

    Local $nTotBytes=0
    PopulateListView($idListview,$idLbl,@desktopdir,  $nTotBytes)

    For $i = $kFnam To $kPath
        _GUICtrlListView_SetColumnWidth($idListview,$i,$LVSCW_AUTOSIZE)
    Next

    Local $t = AddThousandsSeparator(_GUICtrlListView_GetItemCount($idListview))&' files ('&  _
        AddThousandsSeparator($nTotBytes)&' bytes)'
    GUICtrlSetData($idLbl,$t)

    Local $nMsg

    While 1
        $nMsg = GUIGetMsg()
        Switch $nMsg
            Case $GUI_EVENT_CLOSE,$btnClose
                ExitLoop

            Case $listview
        $col = GUICtrlGetState($listview)
        If $col = 2 Then _GUICtrlListView_SimpleSort($listview, True, 5)  



#cs
            Case $btnRemove
                Local $bDel=False,$filspc
                If _MsgBoxEx($MB_YESNO + $MB_DEFBUTTON2 + $MB_ICONQUESTION,"","Remove checked files from backup?", _
                0,WinGetHandle('')) Then
                    For $i = _GUICtrlListView_GetItemCount($idListview)-1 To 0 Step -1
                        If _GUICtrlListView_GetItemChecked($idListview,$i) Then
                            $nTotBytes -= StringReplace(_GUICtrlListView_GetItemText($idListview,$i,$kSize),',','')
                            $filspc = _GUICtrlListView_GetItemText($idListview,$i,$kPath)
                            $filspc &= '\'&_GUICtrlListView_GetItemText($idListview,$i,$kFnam)
                            $t = _GUICtrlListView_GetItemText($idListview,$i,$kExt)
                            If $t<>'' Then
                                $filspc &= '.'&$t
                            EndIf
;~                          FileDelete($filspc) ; for now!!
                            $bDel = True
                            _GUICtrlListView_DeleteItem($idListview,$i)
                        EndIf
                    Next
                    If $bDel Then
                        Local $q = _GUICtrlListView_GetItemCount($idListview)
                        GUICtrlSetData($idLbl,AddThousandsSeparator($q)&' files ('&AddThousandsSeparator($nTotBytes)&' bytes)')

                        If $q=0 Then
                            GUICtrlSetState($btnRemove,$GUI_DISABLE)
                        EndIf
                    EndIf
                EndIf
#ce
        EndSwitch
    WEnd
    GUIDelete($FormFF)
EndFunc

Func PopulateListView($idListview,$idLbl,$sPath, ByRef $nTotBytes)
    Local $sFnamExt,$sAtts,$sDirs='',$p,$sFnam,$sExt,$a1,$nSize,$t,$nItem
    Local $iSrch = FileFindFirstFile($sPath&'\*.*')
    If $iSrch=-1 Then
        Return
    EndIf
    While True
        $sFnamExt = FileFindNextFile($iSrch)
        If @error Then
            ExitLoop
        EndIf
        $sAtts = FileGetAttrib($sPath&'\'&$sFnamExt)
        If StringInStr($sAtts,'D') Then
            $sDirs &= '?'&$sPath&'\'&$sFnamExt
        Else
            $p = StringInStr($sFnamExt,'.',0,-1)    ; last
            If $p=0 Then
                $sFnam = $sFnamExt
                $sExt = ''
            Else
                $sFnam = StringLeft($sFnamExt,$p-1)
                $sExt = StringTrimLeft($sFnamExt,$p)
            EndIf
            $nItem = _GUICtrlListView_GetItemCount($idListview)
            _GUICtrlListView_AddItem($idListview,$sFnam)
            _GUICtrlListView_AddSubItem($idListview,$nItem,$sExt,$kExt)
            $nSize = FileGetSize($sPath&'\'&$sFnamExt)
            _GUICtrlListView_AddSubItem($idListview,$nItem,AddThousandsSeparator($nsize),$kSize)
            $nTotBytes += $nSize
            $a1 = FileGetTime($sPath&'\'&$sFnamExt,$FT_MODIFIED,0)
            $t = $a1[0]&'-'&$a1[1]&'-'&$a1[2]&' '&$a1[3]&':'&$a1[4]
            _GUICtrlListView_AddSubItem($idListview,$nItem,$t,$kDateTime)
            _GUICtrlListView_AddSubItem($idListview,$nItem,$sPath,$kPath)
            _GUICtrlListView_AddSubItem($idListview,$nItem,$nsize,$kSize2)
        EndIf
    WEnd
    FileClose($iSrch)
    GUICtrlSetData($idLbl,AddThousandsSeparator($nTotBytes)&' bytes')
#cs
    If $sDirs<>'' Then
        $sDirs = StringTrimLeft($sDirs,1)
        $a1 = StringSplit($sDirs,'?',0+2)   ; char,0-based
        For $i = 0 To UBound($a1)-1
            PopulateListView($idListview,$idLbl,$a1[$i], $nTotBytes)
        Next
    EndIf
#ce
EndFunc

Func AddThousandsSeparator($n)
    Local $s = String($n)
    Local $sRet=''
    For $i = StringLen($s) To 1 Step -3
        $sRet = StringMid($s,$i-2,3)&','&$sRet
    Next
    $sRet = StringTrimRight($sRet,1)
    If $i<>0 Then
        $sRet = StringLeft($s,3+$i)&$sRet
    EndIf
    Return $sRet
EndFunc

 

Link to comment
Share on other sites

Your idea is clever! I hadn't thought of it.

I am not yet seeing how to get numeric sorting for the fifth column but string sorting for text columns.

Edit1: OK: SimpleSort automatically sorts numerically if the data in a column is integer, else string-wise.

BTW parameter 2 of SimpleSort is ByRef, so:

If $col=2 Then
    $col = 5
EndIf
_GUICtrlListView_SimpleSort($listview, $g_vSortSense,$col)

Edit 2: $g_vSortSense will need to be a 1-d array, e.g. $a1SortSenses[6] to track sort sense of each column (except $kSize).

I see that SimpleSort calls _ArraySort() to do the sorting. I had hoped that Windows would do it: would be much faster.

Edited by c.haslam
Spoiler

CDebug Dumps values of variables including arrays and DLL structs, to a GUI, to the Console, and to the Clipboard

 

Link to comment
Share on other sites

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...