Sign in to follow this  
Followers 0
timbo

ListView SortItems - Prevent toggling of Asc/Desc

2 posts in this topic

#1 ·  Posted (edited)

Hello All,

Unfortunately I imagine I am asking the impossible, but I will make my attempt anyway.

Overview:

Basically I would like to sort the same column twice without affecting the Asc/Desc (i.e. If currently sorted in Asc order, then sort again in Asc order). e.g. if an item value has dynamically changed, and it needs to be sorted back into the correct sequence.

Scenario:

I have a listview which has items dynamically updated (via GUICtrlSetData Or _GUICtrlListView_SetItem, not important). When I have the listview sorted, and one of those items is updated, I need to trigger a sort again (to reposition the item in the correct order). This can easily be done by calling "_GUICtrlListView_SimpleSort" and toggling the "$B_DESCENDING" value (will result in the column being sorted using same Asc/Desc order as previously called).

Problem:

As many of you know "SimpleSort" sorts the values (via an array), then repopulates the listview in such a way that the values no longer relate to the original "controlID" that it was created with (via "GUICtrlCreateListViewItem"). On the other hand, "_GUICtrlListView_SortItems" manages to sort the values while maintaining the "controlID". This means that formatting (set via "GUICtrlSetBkColor" and "GUICtrlSetColor") stick to the intended values.

Example:

I only just figured all this out myself, so I thought I should provide some code to help others understand:

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

Opt('MustDeclareVars', 1)

Example()

Func Example()
    GUICreate("listview items", 220, 250, 100, 200, -1)

    Local $listview = GUICtrlCreateListView("Col 0|Col 1|Col 2", 10, 10, 200, 170)
    Local $B_DESCENDING[3]

    Local $listValues[10]
    For $i = 0 To UBound($listValues)-1
        If $i = 2 Then
            $listValues[$i] = GUICtrlCreateListViewItem("Green|"&($i+1)&"|"&($i+1), $listview)
            Global $greenVal=($i+1)
        ElseIf $i = 6 Then
            $listValues[$i] = GUICtrlCreateListViewItem("Red|"&($i+1)&"|"&($i+1), $listview)
        ElseIf $i = 8 Then
            $listValues[$i] = GUICtrlCreateListViewItem("Blue|"&($i+1)&"|"&($i+1), $listview)
        Else
            $listValues[$i] = GUICtrlCreateListViewItem("White|"&($i+1)&"|"&($i+1), $listview)
        EndIf
    Next

    GUICtrlSetBkColor($listValues[2],0xD0FFD0)
    GUICtrlSetBkColor($listValues[6],0xFFD0D0)
    GUICtrlSetBkColor($listValues[8],0xCDDEFD)

    Local $button = GUICtrlCreateButton("Update green row and re-sort", 10, 185, 200, 20)

    Local $radio[2]
    $radio[0] = GUICtrlCreateRadio("Sort via SortItems", 5, 210, 200, 20)
    $radio[1] = GUICtrlCreateRadio("Sort via SimpleSort", 5, 230, 200, 20)
    GUICtrlSetState($radio[0], $GUI_CHECKED)

    GUISetState()

    _GUICtrlListView_RegisterSortCallBack($listview)
    Global $lastSortCol=2
    _GUICtrlListView_SortItems($listview, $lastSortCol) ;Sort by Col 2 on open

    Do
        Local $msg = GUIGetMsg()
        Select
            Case $msg = $listview
                If BitAND(GUICtrlRead($radio[0]), $GUI_CHECKED) = $GUI_CHECKED Then
                    $lastSortCol=GUICtrlGetState($listview)
                    _GUICtrlListView_SortItems($listview, $lastSortCol)
                ElseIf BitAND(GUICtrlRead($radio[1]), $GUI_CHECKED) = $GUI_CHECKED Then
                    $lastSortCol=GUICtrlGetState($listview)
                    _GUICtrlListView_SimpleSort($listview, $B_DESCENDING, $lastSortCol)
                EndIf
            Case $msg = $button
                $greenVal=$greenVal*5
                GUICtrlSetData($listValues[2], "|"&$greenVal&"|"&$greenVal)
                Sleep(2000)
                If BitAND(GUICtrlRead($radio[0]), $GUI_CHECKED) = $GUI_CHECKED Then
                    _GUICtrlListView_SortItems($listview, $lastSortCol)
                ElseIf BitAND(GUICtrlRead($radio[1]), $GUI_CHECKED) = $GUI_CHECKED Then
                    $B_DESCENDING = NOT $B_DESCENDING
                    _GUICtrlListView_SimpleSort($listview, $B_DESCENDING, $lastSortCol)
                EndIf
        EndSelect
    Until $msg = $GUI_EVENT_CLOSE

    _GUICtrlListView_UnRegisterSortCallBack($listview)
EndFunc   ;==>Example

Steps to recreate:

1) Load above automation

- note that column 2 is already sorted in Asc order)

2) Click the "Update green row and re-sort" button

- this will update the green row, sleep for 2000, then trigger a sort

You should notice that col 2 was initially sorted in Asc order, then after the sort was triggered it toggled to Desc (I want it to sort again in Asc order). Obviously, toggling Asc/Desc is the intended purpose (i.e. clicking on headings), but I'm wondering if an exception can be triggered for situations like this?

I have also built in the ability to switch between sort methods ("SortItems" and "SimpleSort") to display the issue with formatting and "controlIds". (Edit: Click the headings of the listview to see what I mean)

Many thanks in advance,

Timbo

Edited by timbo

Share this post


Link to post
Share on other sites



#2 ·  Posted (edited)

Hello again,

Wouldn't you know it, 30 mintutes after I post I solved it...

I can't say I entirely understand my solution, but it seems to work. I encourage anyone with the knowledge to clarify (or correct) my solution.

I noticed that when you call "_GUICtrlListView_RegisterSortCallBack", it generates an array which is updated in subsequent "_GUICtrlListView_SortItems" calls. The array is shown below:

$aListViewSortInfo[$iIndex][1] = $hWnd ; Handle/ID of listview

    $aListViewSortInfo[$iIndex][2] = _
            DllCallbackRegister("__GUICtrlListView_Sort", "int", "int;int;hwnd") ; Handle of callback
    $aListViewSortInfo[$iIndex][3] = -1 ; $nColumn
    $aListViewSortInfo[$iIndex][4] = -1 ; nCurCol
    $aListViewSortInfo[$iIndex][5] = 1 ; $nSortDir
    $aListViewSortInfo[$iIndex][6] = -1 ; $nCol
    $aListViewSortInfo[$iIndex][7] = 0 ; $bSet
    $aListViewSortInfo[$iIndex][8] = $fNumbers ; Treat as numbers?
    $aListViewSortInfo[$iIndex][9] = $fArrows ; Use arrows in the header of the columns?
    $aListViewSortInfo[$iIndex][10] = $hHeader ; Handle to the Header

When "SortItems" is called the array item "$aListViewSortInfo[$iIndex][7]" gets set to 0, I have no idea what that does, but if you stop that from happening, it won't toggle the Asc/Desc.

So, my poorly designed solution consists on the following:

1) Setup the usual sorting methods for your listview

2) Copy the "_GUICtrlListView_SortItems" function from the "GuiListView.au3" include, paste it into your own script, and change the function name to avoid a conflict

3) Delete (or comment out) the reference to

$aListViewSortInfo[$iIndex][7] = 0 ; $bSet

4) Call the usual "_GUICtrlListView_SortItems" on listview heading clicks, and call the renamed function when calling from your own function

Hope this helps others, I also hope I haven't made a rookie mistake by incorrectly updating code (of which I have no clue on how it works).

(Edit: Decided to add my modified function below:)

Func _GUICtrlListView_SortItems_NoToggle($hWnd, $iCol, $nSortDir=0) ;$nSortDir: 1=Ascending, -1=Descending, 0=Current
    Local $iRet, $iIndex, $pFunction, $hHeader, $iFormat

    If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)

    For $x = 1 To $aListViewSortInfo[0][0]
        If $hWnd = $aListViewSortInfo[$x][1] Then
            $iIndex = $x
            ExitLoop
        EndIf
    Next

    $pFunction = DllCallbackGetPtr($aListViewSortInfo[$iIndex][2]) ; get pointer to call back
    $aListViewSortInfo[$iIndex][3] = $iCol ; $nColumn = column clicked
    If $nSortDir<>0 Then $aListViewSortInfo[$iIndex][5] = $nSortDir ; $nSortDir
    $aListViewSortInfo[$iIndex][4] = $aListViewSortInfo[$iIndex][6] ; nCurCol = $nCol
    $iRet = _SendMessage($hWnd, $LVM_SORTITEMS, $hWnd, $pFunction, 0, "hwnd", "ptr")
    If $iRet <> 0 Then
        If $aListViewSortInfo[$iIndex][9] Then ; Use arrow in header
            $hHeader = $aListViewSortInfo[$iIndex][10]
            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
            $iFormat = _GUICtrlHeader_GetItemFormat($hHeader, $iCol)
            If $aListViewSortInfo[$iIndex][5] = 1 Then ; ascending
                _GUICtrlHeader_SetItemFormat($hHeader, $iCol, BitOR($iFormat, $HDF_SORTUP))
            Else ; descending
                _GUICtrlHeader_SetItemFormat($hHeader, $iCol, BitOR($iFormat, $HDF_SORTDOWN))
            EndIf
        EndIf
    EndIf

    Return $iRet <> 0
EndFunc   ;==>_GUICtrlListView_SortItems_NoToggle

I added the variable "$nSortDir" to the function call which allows you to specify Asc/Desc (note: 1=Asc, -1=Desc, 0=Current).

Regards,

Timbo

Edited by timbo

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
Sign in to follow this  
Followers 0