Jump to content

_ArrayDisplay: pointers or handles are not sorted correctly by clicking on a column header


Recommended Posts

  • Moderators

paulpmeier,

_ArrayDisplay does indeed use simple alphabetical sorting for columns. If you were to use my GUIListViewEx UDF (the link is in my sig) you can use your own sort algorithm for user-defined columns. But that might be using a sledgehammer to crack a nut.....

M23

Public_Domain.png.2d871819fcb9957cf44f4514551a2935.png Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind

Open spoiler to see my UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Link to comment
Share on other sites

If I replace 'StrCmpLogicalW' with 'StrCmpCW' in line 613
in ArrayDisplayInternals.au3, the sorting is correct.
Pointers and handles can also be sorted this way.

But numeric values are then also sorted alphabetically.

Paul

Edited by paulpmeier
Link to comment
Share on other sites

Hi Paul,
Long explanation coming :)

Your comments can be explained, because _ArrayDisplay always uses a comparison type based on Windows API StrCmpLogical, as you can see in the help file, topic _GUICtrlListView_RegisterSortCallBack, where the 2nd parameter (= 2) means "Use Windows API StrCmpLogical"

This corresponds to line #448 in ArrayDisplayInternals.au3 (version 3.3.14.5)

__ArrayDisplay_RegisterSortCallBack($idListView, 2, True, "__ArrayDisplay_SortCallBack")

When this 2nd parameter = 2, then the sort isn't done in a pure alphabetically (ascii) way because "Digits in the strings are considered as numerical content rather than text", as explained on the MSDN web page.

A note from Wikipedia concerning this "natural sort" :

Natural sort order is an ordering of strings in alphabetical order, except that multi-digit numbers are treated atomically, i.e., as if they were a single character.

Natural sort order has been promoted as being more human-friendly ("natural") than the machine-oriented pure alphabetical order.

It could explain why, in your very last diagram, when the 1st character in col1 is a digit (0 for all rows), then "01" means 1, "02" => 2, "03" => 3, while "0A" and "0B" equal to 0 (because their 1st 0 is not followed by a digit), which could explain why "0A" and "0B" are placed on the 1st two lines, in an ascending sort using StrCmpLogical

Your other column (col0) doesn't have the same character at 1st place but a simple "1", "2", "3", "A", "B"
In this case, I guess that StrCmpLogical immediately sorts the letters after the numbers when the sort is ascending, after comparing for example "3" to "A" and finding that the ascii code for "A" is higher than the ascii code for "3", which will result in an ascending sort like this : 1 - 2 - 3 - A - B

So it seems that both columns have their contents treated very differently, depending on the fact that a digit is found or not... at least this is the only explanation I can find. If anyone could confirm or disprove this explanation, please do not hesitate, thanks :)

I find it hard to solve your issue, because changing permanently the 2nd parameter in line #448 (from 2 to 0, meaning you want a pure String comparison) will solve your case... but will bring issues when you have numerals to compare. Same if you change it permanently from 2 to 1 ("String treated as number")

The only (bad) way I found to sort correctly your pointers & handles (or any hex number starting with 0x) in your 1st diagram is :
* Line 448 : 2nd parameter = 1 (String treated as number)

* Lines 596 to 600, added a test on "0x" :

If $__g_aArrayDisplay_SortInfo[8] = 1 Then
    ; force Treat as Number if possible
    If StringIsFloat($sVal1) Or StringIsInt($sVal1) Or StringLeft($sVal1, 2) = "0x" Then $sVal1 = Number($sVal1)
    If StringIsFloat($sVal2) Or StringIsInt($sVal2) Or StringLeft($sVal2, 2) = "0x" Then $sVal2 = Number($sVal2)
EndIf

Then an array like this one...

Global $aSortTest[6][1] = [["0x0B"], ["0x020"], ["0x0C"], ["0x03"], ["0x01"], ["0x0A"]]

...would correctly be sorted (ascending)

Hex.png.1529a4e6cc3b9eddbe9609b2fc0ab401.png

This is the reason why, in my CSV file editor, a choice is given to the user, before each sort (by right-clicking a header column) so the user chooses the kind of sort he wants, instead of being stuck with a "forced" way of sorting :

Sort.png.0e3e98e2acdd38ecfdb81862caa3af53.png

This required of course to register the sort, do the sort, then unregister it each time the user wants to sort, no big deal.

So, if this is really important to you, I guess you'll have to tweak a bit your own version of ArrayDisplayInternals.au3
Good luck :)

Link to comment
Share on other sites

  • 2 weeks later...

Paul, you're welcome :)
I just tried a little context menu inside _ArrayDisplay to choose the way of sorting, it seems to give good results.
Lets have a look at the results first, then the personal modifications made to ArrayDisplayInternals.au3 code.

Test script :

#include <Array.au3>

Global $aSortTest[12][1] = [["0x0B"], ["0x020"], ["0x0C"], ["0x03"], ["0x01"], ["0x0A"], _
                           ["0x0FF"], ["0x030"], ["0x04"], ["0x0E"], ["0x0D"], ["0x02"]]
_ArrayDisplay($aSortTest)

2012186219_Numericsort(goodon0xcol).png.5e1b1ff7de5cfee7062ec963b3552c00.png

The pic above is  your case : you need to choose the Numeric sort to be able to sort correctly a column of handles, pointers or any hex number starting with 0x

1236120933_Naturalsort(goodonRowcol).png.038bfe3ac28c12fbcd0b3656d049f944.png

In the pic above, we see it's very different if we want to sort the "Row" column. It needs a Natural sort to be correctly sorted because it is composed of letters ("Row ") and numbers. I suppose this is the main reason why Dev's forced the Natural sort in ArrayDisplay (when the default way of sorting in GuiListView.au3 is a Numeric sort +++) . Maybe if this Row column had only been composed of numeric elements (like the Excel column numerotation) then Dev's would have kept the original default Numeric sort found in GuiListView.au3... maybe :)

Here is the code change in _ArrayDisplay, it's certainly optimizable but it may give ideas to users who want to tweak their own version of ArrayDisplayInternals.au3

1) Creation of a context menu associated to the listview (lines to be inserted after the listview has been created)

; Create ListView context menu (so user can choose the kind of sort he wants)
Local $idContext_Menu = GUICtrlCreateContextMenu($idListView)
Local $idContext_0 = GUICtrlCreateMenuItem("String sort", $idContext_Menu)
Local $idContext_1 = GUICtrlCreateMenuItem("Numeric sort", $idContext_Menu)
Local $idContext_2 = GUICtrlCreateMenuItem("Natural sort <<<", $idContext_Menu) ; default (keeps compatibility with old scripts)
Local $iSort_Desired = 2, $iSort_Actual = -1 ; 0 = String sort, 1 = Numeric Sort, 2 = Natural sort

2) Inside the While... Wend loop, 3 Case added (Case $idContext_0 ... Case $idContext_1 ... Case $idContext_2)

Case $idContext_0 ; String sort (chosen by user in context menu)
    $iSort_Desired = 0
    GUICtrlSetData($idContext_0, "String sort <<<")
    GUICtrlSetData($idContext_1, "Numeric sort")
    GUICtrlSetData($idContext_2, "Natural sort")

Case $idContext_1 ; Numeric sort (chosen by user in context menu)
    $iSort_Desired = 1
    GUICtrlSetData($idContext_0, "String sort")
    GUICtrlSetData($idContext_1, "Numeric sort <<<")
    GUICtrlSetData($idContext_2, "Natural sort")

Case $idContext_2 ; Natural sort (chosen by user in context menu)
    $iSort_Desired = 2
    GUICtrlSetData($idContext_0, "String sort")
    GUICtrlSetData($idContext_1, "Numeric sort")
    GUICtrlSetData($idContext_2, "Natural sort <<<")

3) Inside the While... Wend loop, modifications made in Case $idListView code :

Case $idListView ; left click on listview column header => sort
    If $iSort_Desired <> $iSort_Actual Then ; note that $iSort_Actual = -1 at 1st passage
        DllCallbackFree($__g_aArrayDisplay_SortInfo[2])
        __ArrayDisplay_RegisterSortCallBack($idListView, $iSort_Desired, True, "__ArrayDisplay_SortCallBack")
        $iSort_Actual = $iSort_Desired
    EndIf
    ; Kick off the sort callback
    __ArrayDisplay_SortItems($idListView, GUICtrlGetState($idListView))

4) Original line #448 to be deleted because it was placed outside the While... Wend loop (eventual sorts are now registered / unregistered within the While... Wend loop, inside the Case $idListView as shown just above)

#448 __ArrayDisplay_RegisterSortCallBack($idListView, 2, True, "__ArrayDisplay_SortCallBack")

5) Original lines #598 et #599 amended to convert strings to numbers when "0x" is found at the beginning of the string.
These 2 lines are originally already included inside a test where the user choosed voluntarily a Numeric sort. In case you're afraid that a string starting with "0x" is not a valid hex number, then you can use guinness _IsHex() function, found in this link, where he checks the string with a Regex expression '^0x[[:xdigit:]]+$'

If StringIsInt($sVal1) Or StringIsFloat($sVal1) Or StringLeft($sVal1, 2) = "0x" Then $sVal1 = Number($sVal1)
If StringIsInt($sVal2) Or StringIsFloat($sVal2) Or StringLeft($sVal2, 2) = "0x" Then $sVal2 = Number($sVal2)

6) As discussed in this link , original line #560 has to be moved "just below" the 2 lines below it.
This will make the sort arrow visible when you click on a column header in ArrayDisplay
It will also correct the value found in the variable $hHeader and in the array element $__g_aArrayDisplay_SortInfo[10]
This is fixed in trac ticket  #3791

;~  Local Const $LVM_GETHEADER = (0x1000 + 31) ; 0x101F
    Local $hHeader =  HWnd(GUICtrlSendMsg($hWnd, 0x101F, 0, 0))

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

7) Finally, DllCallbackFree should be added before both GuiDelete($hGUI) as discussed in this link :

DllCallbackFree($__g_aArrayDisplay_SortInfo[2])
GUIDelete($hGUI)

Hope I didn't miss something important. If Mods think it's better to upload (or not) the complete amended ArrayDisplayInternals.au3 somewhere (for test or whatever) I'll do it. I just thought it could be a bit dangerous to share it fully here, because all these tweaks are very new.

Dinner time :)

 

Edit : 11/21/2020

Parts 1) and 2) in the code above can be scripted differently, to get a shorter code based on menu radioitems.

1)  Creation of a context menu associated to the listview (lines to be inserted after the listview has been created)

; Create ListView context menu (so user can choose the kind of sort he wants)
Local $idContext_Menu = GUICtrlCreateContextMenu($idListView)
Local $idContext_0 = GUICtrlCreateMenuItem("String sort", $idContext_Menu, -1, 1)  ; menuradioitem
Local $idContext_1 = GUICtrlCreateMenuItem("Numeric sort", $idContext_Menu, -1, 1) ; menuradioitem
Local $idContext_2 = GUICtrlCreateMenuItem("Natural sort", $idContext_Menu, -1, 1) ; menuradioitem
GUICtrlSetState(-1, 1) ; $GUI_CHECKED = 1 (Natural sort is the default, to keep compatibility)
Local $iSort_Desired = 2, $iSort_Actual = -1 ; 0 = String sort, 1 = Numeric Sort, 2 = Natural sort

2) Inside the While... Wend loop, 3 Case added (Case $idContext_0 ... Case $idContext_1 ... Case $idContext_2)

Case $idContext_0 ; String sort (chosen by user in context menu)
    $iSort_Desired = 0

Case $idContext_1 ; Numeric sort (chosen by user in context menu)
    $iSort_Desired = 1

Case $idContext_2 ; Natural sort (chosen by user in context menu)
    $iSort_Desired = 2

This will display a context menu based on menu radio items :

106474227_radioitemsincontextmenu.png.30742ed75cf0c657a5ad4bfbd7461698.png

Thanks to lemony  Malkey in this post from 2008 (I discovered it right now !)

Edited by pixelsearch
11/21/2020 : changes done in parts 1) and 2) of the code
Link to comment
Share on other sites

On 11/7/2020 at 2:51 PM, pixelsearch said:

So it seems that both columns have their contents treated very differently, depending on the fact that a digit is found or not... at least this is the only explanation I can find.

It certainly could be, on the other hand would this mean that a big sort might get to the last element in the first pass before deciding to do it a different way? 

Yeesh! 

Code hard, but don’t hard code...

Link to comment
Share on other sites

Hi JockoDundee :)

Not sure of that, I think that items to compare are passed 2 by 2 to the sort callback function.
Let's detail an example :

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

Example()

Func Example()
    Local $aSortTest[6] = ["2", "A", "C", "B", "3", "1"]

    Local $hGUI = GUICreate("Test", 200, 100, -1, 80, -1, $WS_EX_TOPMOST)
    Local $idButton = GUICtrlCreateButton("Click me", 60, 40, 80, 30)

    GUISetState(@SW_SHOW)

    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop

            Case $idButton
                GUISetState(@SW_DISABLE, $hGUI)
                _ArrayDisplay($aSortTest)
                ; Now click only once on the column header to sort the column <===============
                GUISetState(@SW_ENABLE, $hGUI)
        EndSwitch
    WEnd
    GUIDelete($hGUI)
EndFunc   ;==>Example

We need to add a couple of lines inside the sort callback function, to display in the Console what happens when the function will be called several times during the sort process :

Func __ArrayDisplay_SortCallBack($nItem1, $nItem2, $hWnd)

    Local Static $iCounter = 0
    $iCounter += 1
    ConsoleWrite("$iCounter = " & $iCounter & "   $nItem1 = " & $nItem1 & "   $nItem2 = " & $nItem2 & @lf)
    ...

Results :

1419275108_Sortexplanation.png.2830382a294d9a38355e9883eeee6e63.png

When the user clicks on a column header, then $LVM_SORTITEMSEX message is processed and here is what we can read on its MSDN web page :

LVM_SORTITEMSEX message
Parameter lParam : Pointer to an application-defined comparison function. It is called during the sort operation each time the relative order of two list items needs to be compared.

As we can see in the console, the callback function (the "application-defined comparison function") has been called 10 times during this script, 1st time comparing item 1 to item 2, second time comparing item 0 to item 1 etc...

Each time that 2 items have been compared, then Windows (not us) builds internally the final sorted array, maybe by inserting the 2 items at their correct place, moving them, changing the indexes in the listview control etc...

A "sort guru" could certainly explain it much better than I do, but well... you got the idea :)

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...