Sort ListIconGadget [Windows only]

Share your advanced PureBasic knowledge/code with the community.
User avatar
Zapman
Enthusiast
Enthusiast
Posts: 205
Joined: Tue Jan 07, 2020 7:27 pm

Re: Sort ListIconGadget [Windows only]

Post by Zapman »

ChrisR wrote: Tue Feb 25, 2025 6:59 pm Remember your childhood years
It's very, very far away :D
User avatar
Zapman
Enthusiast
Enthusiast
Posts: 205
Joined: Tue Jan 07, 2020 7:27 pm

Re: Sort ListIconGadget [Windows only]

Post by Zapman »

ChrisR wrote: Tue Feb 25, 2025 6:40 pm
Zapman wrote: Tue Feb 25, 2025 6:04 pm I totally agree that the consequence (down arrow for ascending sorting) can seem inverted for certains. But an arrow pointing to the sky when values grow from up to down is too weird for me :)
Yeah, it depends on how you see it:
An arrow pointing to the sky.
Or an arrow smaller at top, larger at bottom for ascending sorting from smallest to largest value, as in Explorer

Image
You convinced me. I updated the code with your logic.
Mesa
Enthusiast
Enthusiast
Posts: 433
Joined: Fri Feb 24, 2012 10:19 am

Re: Sort ListIconGadget [Windows only]

Post by Mesa »

Just for the game...

This code is now Windows XP compatible.

Code: Select all

;*****************************************************************************
;
;                      ColumnSortedListIconGadget.pbi
;                    Zapman - Feb 2025-7 - Windows Only
;
;   This file must be saved under the name 'ColumnSortedListIconGadget.pbi'
;
;      This library provides functionality to add sorting capabilities
;  to a ListIconGadget, including sorting based on ascending or descending
;         order, and displaying arrows for sorting in the header.
; The library can also be used to remove accents from text for proper sorting.
;
;*****************************************************************************
;
CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
CompilerEndIf
;
; To sort items by date, the local date format is needed.
; Adapt the following if necessary:
;
Global DateFormat$ = Space(255)   ; Create a buffer to get the local date format:
GetLocaleInfo_(#LOCALE_USER_DEFAULT, #LOCALE_SSHORTDATE, @DateFormat$, Len(DateFormat$))

Global DateSeparator$ = Space(4)
GetLocaleInfo_(#LOCALE_USER_DEFAULT, #LOCALE_SDATE, @DateSeparator$, Len(DateSeparator$))
DateFormat$ = "%" + LCase(ReplaceString(DateFormat$, DateSeparator$, DateSeparator$ + "%"))
DateFormat$ = ReplaceString(DateFormat$, "%d" + DateSeparator$, "%dd" + DateSeparator$)
DateFormat$ = ReplaceString(DateFormat$, "%m" + DateSeparator$, "%mm" + DateSeparator$)
;
; To sort items by floating numbers, the local decimal separator is needed.
; Adapt the following if necessary:
;
Global DecimalSeparator$ = Space(2)   ; Create a buffer to get the local decimal separator
GetLocaleInfo_(#LOCALE_USER_DEFAULT, #LOCALE_SDECIMAL, @DecimalSeparator$, Len(DecimalSeparator$))
;
; You can also decide how you want the sorts to be done:
;
Global RemoveAccentsWhenSorting = #True
Global NoCaseSorting            = #True
;
; ****************************************************************************************
;
;-   1--- FIRST PART: MISCELLANEOUS FUNCTIONS (possibly reusable for other needs) ---
;
CompilerIf Not(Defined(RemoveAccents, #PB_Procedure))
  Procedure.s RemoveAccents(Text$)
    ; Function to remove accents from a string. By Zapman.
    If Text$
      Protected length = Len(Text$) * 2
      Protected char$, ct, Result$
      Protected NormalizedText$ = Space(Length)
      ;
      Length = FoldString_(#MAP_COMPOSITE, @Text$, - 1, @NormalizedText$, Length) - 1
      ;
      If Length > 0 And Length <> Len(Text$)
        For ct = 1 To Length
          char$ = Mid(NormalizedText$, ct, 1)
          If Asc(char$) < 128
            Result$ + char$
          EndIf
        Next
      Else
        Result$ = Text$
      EndIf
      ;
      ProcedureReturn Result$
    EndIf
  EndProcedure
CompilerEndIf
;
; ****************************************************************************************
;
;-              2--- SECOND PART: SPECIALIZED FUNCTIONS FOR THIS LIBRARY ---
;
; Enumeration to define the sort type:
Enumeration CSLI_SortType
  #CSLI_Descent   ; Ascending sort.
  #CSLI_Ascent    ; Descending sort.
  #CSLI_Unsorted  ; No sorting (default).
EndEnumeration
;
Procedure CSLI_CreateListIconArrows(ArrowType)
  ;
  ; Function to create sorting arrow images (Up, Down, and Up-Down arrows).
  ;
  Protected TSize = 16
  Protected ASize = TSize / 4
  Protected SX = TSize / 2 - ASize
  Protected SY = TSize / 2
  Protected NewImage = CreateImage(#PB_Any, TSize, TSize, 32, #PB_Image_Transparent)
  Protected VASize, Color
  ;
  If NewImage
    If ArrowType = #CSLI_Ascent       ; UpArrow.
      VASize = -ASize
    ElseIf ArrowType = #CSLI_Descent  ; DownArrow.
      VASize = ASize
      SY - ASize + 1
    ElseIf ArrowType = #CSLI_Unsorted ; UpDownArrow.
      VASize = ASize
      SY     = TSize / 2 + 1
    EndIf
    If StartDrawing(ImageOutput(NewImage))
      ;
      DrawingMode(#PB_2DDrawing_AlphaBlend)
      Box(0, 0, TSize, TSize, 0) ; Transparent background.
                                 ;
      If ArrowType = #CSLI_Unsorted
        Color = RGBA(192, 192, 192, 255) ; Gray for unsorted.
      Else
        Color = RGBA(1, 1, 1, 255) ; Quasi-Black for sorted arrows.
                                   ; A 'Full-Black' (0, 0, 0, 255) is not used because the FillArea()
                                   ; function doesn't work with full-black.
      EndIf
      ;
      Line(SX, SY, ASize * 2 + 1, 1, Color) ; Draw the arrow shape.
      Line(SX, SY, ASize, VASize, Color)
      Line(SX + ASize, SY + VASize, ASize, - VASize, Color)
      FillArea(SX + ASize, SY + VASize / 2, Color, Color)
      If ArrowType = #CSLI_Unsorted
        ; A double arrow (UpDown) is drawn for #CSLI_Unsorted.
        SY - ASize + 1
        VASize = -ASize
        Line(SX, SY, ASize * 2 + 1, 1, Color)
        Line(SX, SY, ASize, VASize, Color)
        Line(SX + ASize, SY + VASize, ASize, - VASize, Color)
        FillArea(SX + ASize, SY + VASize / 2, Color, Color)
      EndIf
      StopDrawing()
    EndIf
    ProcedureReturn NewImage
  EndIf
EndProcedure
;
Procedure CSLI_CreateListIconImgList(LIHandle)
  ;
  ; Function to create an image list for the ListIcon header.
  ;
  Protected hHeader, hImgL, ct, Img
  ;
  If IsGadget(LIHandle)
    LIHandle = GadgetID(LIHandle) ; Get the control handle from the gadget's num.
  EndIf
  ;
  hHeader = SendMessage_(LIHandle, #LVM_GETHEADER, 0, 0)
  If SendMessage_(hHeader, #HDM_GETIMAGELIST, 0, 0) = 0
    hImgL = ImageList_Create_(16, 16, #ILC_COLOR32 | #ILC_MASK, 0, 0)
    For ct = #CSLI_Descent To #CSLI_Unsorted
      Img = CSLI_CreateListIconArrows(ct)
      ImageList_Add_(hImgL, ImageID(Img), 0)
      ; Microsoft doc says to free the original image after adding it to the image list.
      FreeImage(Img)
    Next
    SendMessage_(hHeader, #HDM_SETIMAGELIST, 0, hImgL)
  EndIf
EndProcedure
;
Procedure CSLI_FreeImageListFromGadget(LIHandle)
  ;
  ; Destroy both image lists of a ListIconGadget.
  ;
  ; Retrieve and destroy the ImageList for list items (row icons)
  Protected hImageList, hHeader, hHeaderImageList
  ;
  hImageList = SendMessage_(LIHandle, #LVM_GETIMAGELIST, #LVSIL_NORMAL, 0)
  If hImageList
    ImageList_Destroy_(hImageList)
    SendMessage_(LIHandle, #LVM_SETIMAGELIST, #LVSIL_NORMAL, 0)
  EndIf
  ;
  ; Retrieve the handle of the header control
  hHeader = SendMessage_(LIHandle, #LVM_GETHEADER, 0, 0)
  If hHeader
    ; Retrieve and destroy the ImageList for the header (column icons)
    hHeaderImageList = SendMessage_(hHeader, #HDM_GETIMAGELIST, 0, 0)
    If hHeaderImageList
      ImageList_Destroy_(hHeaderImageList)
      SendMessage_(hHeader, #HDM_SETIMAGELIST, 0, 0)
    EndIf
  EndIf
EndProcedure

; Define structure for sorting information.
Structure SortInfoStruct
  GadgetHandle.i
  Column.l
  AscentDescent.l
EndStructure
;
Procedure CSLI_ListIconSortCallback(*item1.long, *item2.long, *SortInfo.SortInfoStruct)
  ;
  ; Sorting callback function used by the ListIcon to compare items.
  ;
  Protected A$ = Space(200), B$ = Space(200), A2$, A.q = 0, B.q = 0, lvi.LV_ITEM
  Protected result = 0, AscentDescent
  ;
  lvi\iSubItem   = *SortInfo\Column
  lvi\pszText    = @A$
  lvi\cchTextMax = 200
  SendMessage_(*SortInfo\GadgetHandle, #LVM_GETITEMTEXT, *item1\l, @lvi)
  lvi\pszText = @B$
  SendMessage_(*SortInfo\GadgetHandle, #LVM_GETITEMTEXT, *item2\l, @lvi)
  ;
  If A$ = B$
    ProcedureReturn 0 ; Items are equal.
  EndIf
  ;
  ; Determine if sorting is ascending or descending.
  If *SortInfo\AscentDescent = #CSLI_Descent : AscentDescent = -1 : Else : AscentDescent = 1 : EndIf
  ;
  ; Sorting based on numeric or alphabetical values.
  A$ = ReplaceString(A$, DecimalSeparator$, ".")
  B$ = ReplaceString(B$, DecimalSeparator$, ".")
  ;
  If FindString(A$, "0")
    A2$ = ReplaceString(A$, "0", "")
    If A2$ = "" Or A2$ = "." Or A2$ = "$"
      A$ = "0"
    EndIf
  EndIf
  ;
  If A$ = "0" Or Val(A$) > 0 Or Val(B$) > 0
    A = Val(A$)
    B = Val(B$)
  EndIf
  If ValF(A$) > A Or ValF(B$) > B
    A = ValF(A$)
    B = ValF(B$)
  EndIf
  If ValD(A$) > A Or ValD(B$) > B
    A = ValD(A$)
    B = ValD(B$)
  EndIf
  ;
  If CountString(A$, DateSeparator$) > 1 Or CountString(B$, DateSeparator$) > 1
    If ParseDate(DateFormat$, A$) > ParseDate(DateFormat$, B$)
      Result = AscentDescent
    Else
      Result = -AscentDescent
    EndIf
  ElseIf Abs(A) > 0 Or A$ = "0"
    If A > B Or ValF(A$) > ValF(B$)
      Result = AscentDescent
    Else
      Result = -AscentDescent
    EndIf
  Else
    If RemoveAccentsWhenSorting
      A$ = RemoveAccents(A$)
      B$ = RemoveAccents(B$)
    EndIf
    If NoCaseSorting
      A$ = LCase(A$)
      B$ = LCase(B$)
    EndIf
    If A$ > B$
      Result = AscentDescent
    Else
      Result = -AscentDescent
    EndIf
  EndIf
  ;
  ProcedureReturn result
EndProcedure
;
Procedure CSLI_ListIconHideHeaderImages(LIHandle)
  ;
  ; Function to hide images in the ListIcon header (useful when arrows are hidden).
  ; It will keep the value stored in the columns by #LVM_SETCOLUMN
  ; (index of the current image for each column), but no image will be shown.
  ;
  Protected NColumn, LVC.LVCOLUMN
  ;
  If IsGadget(LIHandle)
    LIHandle = GadgetID(LIHandle) ; Get the control handle from the gadget's num.
  EndIf
  ;
  While SendMessage_(LIHandle, #LVM_GETCOLUMN, NColumn, @LVC)
    ; Hide images
    LVC\mask   = #LVCF_FMT
    LVC\fmt    = #LVCFMT_COL_HAS_IMAGES
    LVC\iImage = 0
    SendMessage_(LIHandle, #LVM_SETCOLUMN, NColumn, @LVC)
    NColumn + 1
  Wend
EndProcedure

; Declare SortListIcon procedure:
Declare SortListIcon(LIHandle, column, AscentDescent)
;
Procedure CSLI_WinLIProc(hWnd, uMsg, wParam, lParam)
  ;
  ; Window callback procedure to handle ListIcon sorting interaction.
  ;
  Protected NColumn, LVC.LVCOLUMN, LIHandle, AscentDescent
  Protected *NMHDR.NMHDR, *NMLV.NMLISTVIEW
  ;
  Select uMsg
    Case #WM_NOTIFY
      *NMHDR = lParam
      If GetProp_(*NMHDR\hWndFrom, "CSLI_Sort")
        ; Handle sort operations on column click:
        If *NMHDR\code = #LVN_COLUMNCLICK
          *NMLV    = lParam
          LIHandle = *NMHDR\hWndFrom
          ; Retrieve actual image-index:
          LVC\mask = #LVCF_IMAGE
          SendMessage_(LIHandle, #LVM_GETCOLUMN, *NMLV\iSubItem, @LVC)
          AscentDescent = LVC\iImage
          ; Switch the index:
          If AscentDescent = #CSLI_Ascent
            AscentDescent = #CSLI_Descent
          Else
            AscentDescent = #CSLI_Ascent
          EndIf
          ;
          ; Set image-index to UpDown for all columns:
          While SendMessage_(LIHandle, #LVM_GETCOLUMN, NColumn, @LVC)
            LVC\iImage = #CSLI_Unsorted
            SendMessage_(LIHandle, #LVM_SETCOLUMN, NColumn, @LVC)
            NColumn + 1
          Wend
          ;
          If GetProp_(LIHandle, "CSLI_ShowArrow") = 0
            ; If CSLI_ShowArrow is not set, hide the images:
            CSLI_ListIconHideHeaderImages(LIHandle)
          EndIf
          ;
          ; Sort and update image-index for the clicked column.
          ; If CSLI_ShowArrow is set, this will update the shown image
          ; for the clicked column:
          SortListIcon(LIHandle, *NMLV\iSubItem, AscentDescent)
          ;
        ElseIf *NMHDR\code = #LVN_DELETEALLITEMS
          ; The ListIcon gadget is being destroyed.
          ; Clean the memory from images:
          CSLI_FreeImageListFromGadget(*NMHDR\hWndFrom)
        EndIf
      EndIf
  EndSelect
  ;
  Protected CSLI_OldCallBack = GetProp_(hWnd, "CSLI_OldCallBack")
  ProcedureReturn CallWindowProc_(CSLI_OldCallBack, hWnd, uMsg, wParam, lParam)
EndProcedure
;
; ****************************************************************************************
;
;-                 3--- THIRD PART: MAIN FUNCTIONS OF THIS LIBRARY ---
;
; ****************************************************************************************
;
Procedure MakeListIconSortable(GadgetNum, TrueFalse = #True)
  ;
  ; Initialyze all the needed values of 'LIHandle' to allow it
  ; to be sortable.
  ;
  Protected i.l, GadgetID, LVC.LVCOLUMN, NColumn, NItems
  ;
  If IsGadget(GadgetNum)
    Protected LIHandle = GadgetID(GadgetNum) ; Get the control handle from the gadget's num.
  Else
    LIHandle  = GadgetNum
    GadgetNum = GetProp_(LIHandle, "PB_ID") ; Get the gadget's num from its handle.
  EndIf
  ;
  If IsGadget(GadgetNum) And GadgetType(GadgetNum) = #PB_GadgetType_ListIcon  ; Ensure it's a valid ListIcon gadget.
    If TrueFalse                                                              ; Check if sorting should be enabled.
      NItems = SendMessage_(LIHandle, #LVM_GETITEMCOUNT, 0, 0)                ; Get the number of items in the ListIcon.
      For i = 0 To NItems - 1                                                 ; Loop through all the items:
        SetGadgetItemData(GadgetNum, i, i)                                    ; Set the item data to its index for sorting purposes.
      Next
      SetProp_(LIHandle, "CSLI_Sort", 1)  ; Store the sorting state in the ListIcon handle property.
                                          ; This will allow to know that this gadget had been made sortable.
      CSLI_CreateListIconImgList(LIHandle); Create the image list for sorting icons.
      If GetProp_(LIHandle, "CSLI_ShowArrow")  ; If the sorting arrow is enabled...
        NColumn = 0
        While SendMessage_(LIHandle, #LVM_GETCOLUMN, NColumn, @LVC)
          LVC\mask   = #LVCF_IMAGE  ; Set the column to use image (for sorting arrows).
          LVC\iImage = #CSLI_Unsorted  ; Set the initial sorting state as unsorted.
          SendMessage_(LIHandle, #LVM_SETCOLUMN, NColumn, @LVC)  ; Set the column information.
          NColumn + 1
        Wend
      Else
        CSLI_ListIconHideHeaderImages(LIHandle)  ; If CSLI_ShowArrow is not set, hide the header images.
      EndIf
      ;
      Protected hWnd = GetAncestor_(LIHandle, #GA_ROOT)  ; Get the root window handle for the ListIcon.
      Protected CSLI_OldCallBack = GetProp_(hWnd, "CSLI_OldCallBack")  ; Retrieve the CSLI_OldCallBack procedure if any.
      If CSLI_OldCallBack = 0                                          ; If there's no existing CSLI_OldCallBack, set it:
        CSLI_OldCallBack = SetWindowLongPtr_(hWnd, #GWL_WNDPROC, @CSLI_WinLIProc())  ; Set a custom window procedure.
        SetProp_(hWnd, "CSLI_OldCallBack", CSLI_OldCallBack)                         ; Store the Oldcallback procedure address.
      EndIf
    Else  ; If sorting is disabled
      CSLI_ListIconHideHeaderImages(LIHandle)  ; Hide any header images.
      CSLI_FreeImageListFromGadget(LIHandle)   ; Free the image list from the gadget.
      hWnd             = GetAncestor_(LIHandle, #GA_ROOT)  ; Get the root window handle for the ListIcon.
      CSLI_OldCallBack = GetProp_(hWnd, "CSLI_OldCallBack")  ; Retrieve the old callback procedure.
      If CSLI_OldCallBack                                    ; If there was a custom callback set earlier...
                                                             ; Reset to standard management:
        SetWindowLongPtr_(hWnd, #GWL_WNDPROC, CSLI_OldCallBack)  ; Restore the original window procedure.
        RemoveProp_(hWnd, "CSLI_OldCallBack")                    ; Remove the stored callback address.
      EndIf
    EndIf
  EndIf
EndProcedure
;
;https://www.purebasic.fr/english/viewtopic.php?t=55085
Procedure LIG_SetSortIcon(ListGadget.i, Column.i, SortOrder.i)
  ; http://stackoverflow.com/questions/254129/how-To-i-display-a-sort-arrow-in-the-header-of-a-List-view-column-using-c
  Protected ColumnHeader.i
  Protected ColumnCount.i
  Protected hditem.HD_ITEM
  Protected Cnt.i
  
  #NoSort      = 0
  #AscSort     = 1
  #DescSort    = 2
  
  ColumnHeader = SendMessage_(GadgetID(ListGadget), #LVM_GETHEADER, 0, 0)
  
  ColumnCount = SendMessage_(ColumnHeader, #HDM_GETITEMCOUNT, 0, 0)
  
  For Cnt = 0 To ColumnCount - 1
    hditem\mask = #HDI_FORMAT
    
    If SendMessage_(ColumnHeader, #HDM_GETITEM, Cnt, @hditem) = 0
      Debug "ERROR! LIG_SetSortIcon 1"
    EndIf
    
    hditem\mask = #HDI_FORMAT
    If (Cnt = Column And SortOrder <> #NoSort)
      Select SortOrder
        Case #AscSort ; wenn aufsteigend sortiert werden soll
          hditem\fmt & ~#HDF_SORTDOWN
          hditem\fmt | #HDF_SORTUP
          Debug "sortup"
        Case #DescSort
          hditem\fmt & ~#HDF_SORTUP
          hditem\fmt | #HDF_SORTDOWN
          Debug "sortdown"
      EndSelect
    Else
      hditem\fmt & ~#HDF_SORTUP
      hditem\fmt & ~#HDF_SORTDOWN
    EndIf
    
    If (SendMessage_(ColumnHeader, #HDM_SETITEM, Cnt, @hditem) = 0)
      Debug "ERROR! LIG_SetSortIcon 2"
    EndIf
    
  Next
EndProcedure

Procedure.b LIG_GetSortOrder(ListGadget.i, Column.i)
  Protected ColumnHeader.i
  Protected hditem.HD_ITEM
  Protected RetVal.b
  
  ColumnHeader = SendMessage_(GadgetID(ListGadget), #LVM_GETHEADER, 0, 0)
  
  hditem\mask = #HDI_FORMAT
  
  If SendMessage_(ColumnHeader, #HDM_GETITEM, Column, @hditem)
    If (hditem\fmt & #HDF_SORTUP) = #HDF_SORTUP
      
      RetVal = #AscSort
    ElseIf (hditem\fmt & #HDF_SORTDOWN) = #HDF_SORTDOWN
      
      RetVal = #DescSort
    Else
      
      RetVal = #NoSort
    EndIf
    
  Else
    Debug "ERROR! LIG_GetSortOrder"
    RetVal =  - 1
    
  EndIf
  
  ProcedureReturn RetVal
  
EndProcedure
Procedure SortListIcon(GadgetNum, column, AscentDescent)
  ;
  ; Sort the whole content of a ListIconGadget from one column values.
  ;
  ; column : from 0 to the last column number.
  ; AscentDescent : #CSLI_Descent for ascending, #CSLI_Ascent for descending.
  ;,,
  Protected SortInfo.SortInfoStruct, LVC.LVCOLUMN, Order
  ;
  
  If IsGadget(GadgetNum)
    Protected LIHandle = GadgetID(GadgetNum) ; Get the control handle from the gadget's num.
  Else
    LIHandle  = GadgetNum
    GadgetNum = GetProp_(LIHandle, "PB_ID") ; Get the gadget's num from its handle.
  EndIf
  If IsGadget(GadgetNum) And GadgetType(GadgetNum) = #PB_GadgetType_ListIcon  ; Ensure it's a ListIcon gadget
    MakeListIconSortable(GadgetNum)                                           ; Initialize the ListIcon gadget with sorting enabled.
    If OSVersion() = #PB_OS_Windows_XP
      #NoSort   = 0
      #AscSort  = 1
      #DescSort = 2
      Select LIG_GetSortOrder(GadgetNum, column)
        Case #NoSort, #DescSort
          Order = #AscSort
        Case #AscSort
          Order = #DescSort
      EndSelect
      LIG_SetSortIcon(GadgetNum, column, Order)
    EndIf
    SortInfo\GadgetHandle  = LIHandle  ; Store the gadget handle in the sorting structure.
    SortInfo\Column        = column          ; Store the column to be sorted in the sorting structure.
    SortInfo\AscentDescent = AscentDescent  ; Store the sorting order (ascending or descending).
    SendMessage_(LIHandle, #LVM_SORTITEMS, @SortInfo, @CSLI_ListIconSortCallback())  ; Perform the sorting using the callback function.
                                                                                     ;
                                                                                     ; Update the image index for the column to reflect the sorting order.
    LVC\mask   = #LVCF_IMAGE                                                           ; Set the column to use an image (for the sorting arrow).
    LVC\iImage = AscentDescent                                                      ; Set the image index based on the sorting order.
    SendMessage_(LIHandle, #LVM_SETCOLUMN, column, @LVC)                             ; Set the updated column information.
                                                                                     ;
    If GetProp_(LIHandle, "CSLI_ShowArrow") = 0                                      ; If CSLI_ShowArrow is not enabled...
                                                                                     ; Hide images in the columns.
                                                                                     ; This will keep the value stored in the column by the preceeding #LVM_SETCOLUMN message
                                                                                     ; (index of the current image for the column), but no image will be shown.
      LVC\mask   = #LVCF_FMT                                                         ; Set the column format to hide images:
      LVC\fmt    = #LVCFMT_COL_HAS_IMAGES
      LVC\iImage = 0  ; Hide the image.
      SendMessage_(LIHandle, #LVM_SETCOLUMN, column, @LVC)  ; Apply the changes to the column.
    EndIf
    ProcedureReturn #True  ; Return True to indicate success.
  EndIf
  
  
EndProcedure
;


Procedure ShowListIconSortingArrows(LIHandle, ShowHide = #True)
  ;
  ; Hide or show the sorting arrows in the header of 'LIHandle'.
  ;
  Protected Order, Column = 0
  
  If IsGadget(LIHandle)  ; Check if the provided handle is a valid gadget.
    LIHandle = GadgetID(LIHandle) ; Get the control handle from the gadget's num.
  EndIf
  SetProp_(LIHandle, "CSLI_ShowArrow", ShowHide)  ; Store whether the sorting arrows should be shown or hidden.
  MakeListIconSortable(LIHandle)                  ; Re-initialize the ListIcon with the updated sorting arrows setting.
  
EndProcedure
;
; ****************************************************************************************
;
;-                         4--- FORTH PART: DEMO PROCEDURE ---
;
; ****************************************************************************************
;
CompilerIf #PB_Compiler_IsMainFile
  ; The following won't run when this file is used as 'Included'.
  Define HWindow = OpenWindow(#PB_Any, 0, 0, 485, 260, "Sortable ListIconGadget demo", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  ;
  Define SortListIconGagdetRef = ListIconGadget(#PB_Any, 0, 0, WindowWidth(HWindow), 225, "", 0, #PB_ListIcon_FullRowSelect | #PB_ListIcon_AlwaysShowSelection)
  ;
  Define MakeListIconSortableCheckBox = CheckBoxGadget(#PB_Any, 10, WindowHeight(HWindow) - 30, 150, 22, "Make ListIcon Sortable")
  SetGadgetState(MakeListIconSortableCheckBox, 1)
  ;
  Define ShowListIconSortingArrowsCheckBox = CheckBoxGadget(#PB_Any, 200, WindowHeight(HWindow) - 30, 180, 22, "Show ListIcon Sorting Arrows")
  SetGadgetState(ShowListIconSortingArrowsCheckBox, 1)
  ShowListIconSortingArrows(SortListIconGagdetRef) ; <-- This will automatically set the ListIconGagdet to 'Sortable'.
                                                   ; If you just want to make ListIconGagdet 'Sortable' without showing the arrows in the header,
                                                   ; you can call 'MakeListIconSortable()' instead of ShowListIconSortingArrows().
                                                   ;
  AddGadgetColumn(SortListIconGagdetRef, 0, "Column 1", 150)
  AddGadgetColumn(SortListIconGagdetRef, 1, "Column 2", 100)
  AddGadgetColumn(SortListIconGagdetRef, 2, "Column 3", 100)
  AddGadgetColumn(SortListIconGagdetRef, 3, "Column 4", 110)
  ;
  Define mDate.q = Date()
  Define a, A$, B$, C$, D$, x
  ;
  For a = 0 To 8
    A$ = "COLUMN 1, Row " + RSet(Str(a), 3, "0") + Chr(10)
    If a = 0
      A$ = "élève " + A$
    EndIf
    If a = 1
      A$ = "devant " + A$
    EndIf
    If a = 2
      A$ = "front " + A$
    EndIf
    If a = 3
      A$ = "Absent " + A$
    EndIf
    If a = 4
      A$ = "Final " + A$
    EndIf
    x  = Random($FFFF)
    B$ = RSet(Str(x), 5, "0") + Chr(10)
    x  = Random($7FFFFFFF)
    C$ = "$" + RSet(Hex(x), 8, "0") + Chr(10)
    D$ = FormatDate(DateFormat$, mDate)
    mDate + 100000
    AddGadgetItem(SortListIconGagdetRef, - 1, A$ + B$ + C$ + D$)
  Next
  ;
  SortListIcon(SortListIconGagdetRef, 0, #CSLI_Ascent)
  ;
  Repeat
    Define Event = WaitWindowEvent()
    If Event = #PB_Event_Gadget
      Select EventGadget()
        Case MakeListIconSortableCheckBox
          Define TrueFalse = GetGadgetState(MakeListIconSortableCheckBox)
          SetGadgetState(ShowListIconSortingArrowsCheckBox, TrueFalse)
          If TrueFalse
            ShowListIconSortingArrows(SortListIconGagdetRef) ; <-- This will automatically call MakeListIconSortable(SortListIconGagdetRef, #True).
          Else
            MakeListIconSortable(SortListIconGagdetRef, #False)
          EndIf
        Case ShowListIconSortingArrowsCheckBox
          TrueFalse = GetGadgetState(ShowListIconSortingArrowsCheckBox)
          If TrueFalse
            SetGadgetState(MakeListIconSortableCheckBox, #True)
          EndIf
          ShowListIconSortingArrows(SortListIconGagdetRef, TrueFalse)
      EndSelect
    EndIf
  Until Event = #PB_Event_CloseWindow
CompilerEndIf

Mesa.
User avatar
Zapman
Enthusiast
Enthusiast
Posts: 205
Joined: Tue Jan 07, 2020 7:27 pm

Re: Sort ListIconGadget [Windows only]

Post by Zapman »

Mesa wrote: Wed Feb 26, 2025 11:19 amThis code is now Windows XP compatible.
Very nice work, Mesa!
Thank you for this suggestion which gives the Explorer's appearance to the sorted column, like the code of nalor did.
It can also work on later versions of Windows.

I'll work on it to better integrate this option (for example, it's not ideal to have to redefine the #NoSort, #AscSort and #DescSort constants...) and set a constant at the beginning of the program to allow to choose between to the types of appearance.
User avatar
Zapman
Enthusiast
Enthusiast
Posts: 205
Joined: Tue Jan 07, 2020 7:27 pm

Re: Sort ListIconGadget [Windows only]

Post by Zapman »

The code within the first message of this subject has been udated.
- Comments had been added to explain all the story about local date formats,
- Other comments had been added to facilitate code adaptation for special needs,
- There are now two possible manners to show the sorting arrows: on left or above the column titles.
- The demonstration code allows to choose between these two options:
Image
ebs
Enthusiast
Enthusiast
Posts: 557
Joined: Fri Apr 25, 2003 11:08 pm

Re: Sort ListIconGadget [Windows only]

Post by ebs »

Hi @Zapman,

Thanks for the code update and the arrow options. I noticed one problem:

When either of the "show arrows" options are selected, clicking on the same column header alternates between ascending and descending sort, as expected.

However, if "don't show arrows" is selected, the first click sorts in ascending order, but the second (and subsequent) clicks on the same header don't do anything.
User avatar
Zapman
Enthusiast
Enthusiast
Posts: 205
Joined: Tue Jan 07, 2020 7:27 pm

Re: Sort ListIconGadget [Windows only]

Post by Zapman »

ebs wrote: Fri Feb 28, 2025 3:37 pm If "don't show arrows" is selected, the first click sorts in ascending order, but the second (and subsequent) clicks on the same header don't do anything.
That's true.
I'm working on a new version to fix that and with many other improvements.

Thank you, ebs.
ebs
Enthusiast
Enthusiast
Posts: 557
Joined: Fri Apr 25, 2003 11:08 pm

Re: Sort ListIconGadget [Windows only]

Post by ebs »

Waiting anxiously... :D
User avatar
Zapman
Enthusiast
Enthusiast
Posts: 205
Joined: Tue Jan 07, 2020 7:27 pm

Re: Sort ListIconGadget [Windows only]

Post by Zapman »

ebs wrote: Fri Feb 28, 2025 7:08 pmWaiting anxiously... :D
You can relax :)

The code within the first message of this subject has been udated.
  • Fixed: Sort works well (both ascending and descending) even when arrows are hidden.
  • Fixed: The column justification format (left-center-right) was lost after sorts. It's OK now.
  • Improved: The gadget item's data was used for sorting. This is no longer the case. You can therefore use the data for other things, if necessary (with SetGadgetItemData() and GetGadgetItemData()).
  • Improved: Hours are sorted whatever the format used to represent them (see the last column of the demo).
User avatar
Lord
Addict
Addict
Posts: 900
Joined: Tue May 26, 2009 2:11 pm

Re: Sort ListIconGadget [Windows only]

Post by Lord »

I'm just curios:
Why is "6:00 PM" greater than "16h30mn"?
Isn't "6:00PM" same as "16h00mn"?
So "6:00PM" should be lower than "16h30mn".

Edit:
Just noticed, that if "Don't show arrows" is selected
after arrows were allowed and sorting was used,
the arrow remains visible.
Image
User avatar
Mindphazer
Enthusiast
Enthusiast
Posts: 456
Joined: Mon Sep 10, 2012 10:41 am
Location: Savoie

Re: Sort ListIconGadget [Windows only]

Post by Mindphazer »

Lord wrote: Sat Mar 01, 2025 5:23 pm I'm just curios:
Why is "6:00 PM" greater than "16h30mn"?
Isn't "6:00PM" same as "16h00mn"?
So "6:00PM" should be lower than "16h30mn".
Nope
6:00PM is the same as 18h00, not 16h00
16h00 is equal to 4:00PM
So 6:00PM is indeed greater than 16h30 (which is equal to 4:30PM)
MacBook Pro 16" M4 Pro - 24 Gb - MacOS 15.4.1 - Iphone 15 Pro Max - iPad at home
...and unfortunately... Windows at work...
User avatar
Lord
Addict
Addict
Posts: 900
Joined: Tue May 26, 2009 2:11 pm

Re: Sort ListIconGadget [Windows only]

Post by Lord »

Mindphazer wrote: Sat Mar 01, 2025 6:20 pm...
Nope
...
Of course.
Sorry for this. I just was looking at the "30".
My fingers were faster than my brain. :wink:
Image
User avatar
Zapman
Enthusiast
Enthusiast
Posts: 205
Joined: Tue Jan 07, 2020 7:27 pm

Re: Sort ListIconGadget [Windows only]

Post by Zapman »

Lord wrote: Sat Mar 01, 2025 5:23 pmIf "Don't show arrows" is selected after arrows were allowed and sorting was used, the arrow remains visible.
Thanks again, Lord, for your accurate testing. This bug is fixed with the actual version.
And also:
  • I have simplified the used of the library by reducing two functions in one. Now, simply call

    Code: Select all

    MakeListIconSortable(ListIconGadget, setting)
    with four possible values for the 'Setting' parameter:
    • #CSLI_NotSortable ; Reset the gadget to normal (not sortable).
    • #CSLI_NoArrow ; Make the gadget sortable but don't show arrows.
    • #CSLI_LeftArrows ; Show arrows on left side.
    • #CSLI_TopArrows ; Show arrows above the column title.
  • I've added a 'Sporty sorting' possibility, for times noted as in sport's competitions.
For sport's competitions, times are noted as:
' for minute
'' for second
''' for hundredths of a second
so: 1'30''80''' means: one minute, 30 seconds and 80 hundredths of a second.
The last symbol is sometimes omitted, so 1'30 = 1'30'' and 1'30''80 = 1'30''80'''
User avatar
Lord
Addict
Addict
Posts: 900
Joined: Tue May 26, 2009 2:11 pm

Re: Sort ListIconGadget [Windows only]

Post by Lord »

Hi Zapman!

These are nice additions! And are working fine.

I now have some ideas for additional funkionality, but this would
blow up the code and make it more a extended LIG:
- different justification of columns and columns title
maybe user initiated by RMB
Image
dcr3
Enthusiast
Enthusiast
Posts: 181
Joined: Fri Aug 04, 2017 11:03 pm

Re: Sort ListIconGadget [Windows only]

Post by dcr3 »

I haven't tested everything thoroughly in this sort module.

These format dates sort properly.

"%yyyy/%mm/%dd"
"%yyyy.%mm.%dd"
"%dd/%mm/%yyyy

But the following dates format don't sort properly.

"%yyyy-%mm-%dd"
"%dd-%mm-%yyyy"
"%dd.%mm.%yyyy"

Also list of dates, like.
25 Jan 2025
25 January 2025
25-Jan-2025
25/Jan/2025
or with any combination above, don't sort.
Post Reply