Jump to content
c.haslam

Sorting a ListView where columns have different sort sequences

Recommended Posts

c.haslam
Posted (edited)

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

 

Share this post


Link to post
Share on other sites
AutoBert

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

 

Share this post


Link to post
Share on other sites
c.haslam

I do not see how this example uses a different sort sequence for each column.


Spoiler

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

 

Share this post


Link to post
Share on other sites
mikell

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

 

Share this post


Link to post
Share on other sites
c.haslam
Posted (edited)

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

 

Share this post


Link to post
Share on other sites
c.haslam

Many thanks for the help.


Spoiler

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

 

Share this post


Link to post
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

×