Page 1 of 3

Window ListIconGadget With Owner Data (Very Fast)

Posted: Sun Dec 28, 2025 7:49 pm
by mk-soft
After writing a tool for files, I find the Windows ListIconGadget far too slow for displaying thousands of items.
However, you can manage this yourself with the MS-ListView flags #LVS_OWNERDATA.

This applies to all created ListIconGadgets.

1. SetGadgetItemState only supports the flag #PB_ListIcon_Checked.
2. After AddGadgetItem, SetGadgetItemState or SetGadetItemColor, the ListIconGadget must be updated with UpdateListIconGadget.
3. WinCallback must be used to process the ListIconGadget events.

Since a mouse click on the check box of ListIconGadget does not respond, it had to be evaluated separately. (Took a long time to find, Window bug)
I also changed the behaviour here.

Example for testing with 100,000 items, colors and font ;)

File: ListIconGadgetEx.pb

Update v1.02.1
- Added Compiler Option #USE_GLOBAL_LISTICON_OWNER_DATA
- Added FreeListIconGadget to release list icon data
- Optimize code

Update v1.03.1
- Bugfix CheckBox and Multi Selection
- Added EventType #PB_EventType_StatusChange for CheckBox Changed
- Added Item Colors

Update v1.04.2
- Added Column Colors
- Change WinCallback

Update v1.04.3
- Added Gadget Color

Update v1.04.4
- Bugfix: Add and RemoveGadgetColumns the column colors
- Optimize: Change from PB Array to Static Array for columns

Update v1.04.6
- Bugfix: Column text must not be a zero, otherwise no column color

Update v1.04.7
- Changed max columns count to auto column count

Update v1.05.1
- Added Item Images Support
- Bugfix AddGadgetColumn

Update v1.06.2
- Added Header Color Support
- Added Compiler Option #USE_LISTICON_HEADER_COLOR
- Bugfix delete brush object

Update v1.06.4
- Remove Compiler Option #USE_LISTICON_HEADER_COLOR
- Automatically initializes the ListIcon Header Color Callback

Update v1.06.1
- Bugfix one brush release

Update v1.07.1
- Added Special Function SortListIconGadgetEx

Update v1.07.2
- Some bug fixed

Code: Select all

;-TOP

; Comment : Windows ListIconGadget Owner Data
; Author  : mk-soft
; Version : v1.07.4
; Create  : 26.12.2025
; Update  : 11.01.2026

; Description
;  - Call UpdateListIconGadget after AddGadgetItem, SetGadgetItemState, SetGadgetItemColor, SetGadgetItemImage
;  - Call FreeListIconGadget to release gadget data
;  
;  EventType:
;    The check box sends an EventType #PB_EventType_StatusChange. EventData receives the item.
;  

EnableExplicit

Import ""
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    SetWindowSubclass_(hWnd, *fnSubclass, uIdSubclass, dwRefData) As "SetWindowSubclass" 
    GetWindowSubclass_(hWnd, *fnSubclass, uIdSubclass, *dwRefData) As "GetWindowSubclass"
    RemoveWindowSubclass_(hWnd, *fnSubclass, uIdSubclass) As "RemoveWindowSubclass"
    DefSubclassProc_(hWnd, uMsg, wParam, lParam) As "DefSubclassProc"
  CompilerElse
    SetWindowSubclass_(hWnd, *fnSubclass, uIdSubclass, dwRefData) As "_SetWindowSubclass@16" 
    GetWindowSubclass_(hWnd, *fnSubclass, uIdSubclass, *dwRefData) As "_GetWindowSubclass@16"
    RemoveWindowSubclass_(hWnd, *fnSubclass, uIdSubclass) As "_RemoveWindowSubclass@12"
    DefSubclassProc_(hWnd, uMsg, wParam, lParam) As "_DefSubclassProc@16" 
  CompilerEndIf
EndImport 

;- Configuration

#USE_GLOBAL_LISTICON_OWNER_DATA = #True

;- Define

#LISTICON_COLUMN_SIZE = 20

#LVIS_UNCHECKED = $1000;
#LVIS_CHECKED = $2000  ;

#ILC_COLOR32 = $00000020

Structure udtListIconItem
  State.l
  ItemColor.l
  ItemColorBk.l
  ItemImage.l
  UserData.i
  Array Column.s(#LISTICON_COLUMN_SIZE)
  Array ColumnColor.l(#LISTICON_COLUMN_SIZE)
  Array ColumnColorBk.l(#LISTICON_COLUMN_SIZE)
  *Reference
EndStructure

Structure udtListIconData
  ExStyle.l
  ; Items
  CountItems.l
  CountColumns.l
  List Items.udtListIconItem()
  ; Images
  hImageList.i
  Map ImageList.l()
  ; Header Colors
  IsHeaderColor.l
  HeaderColor.l
  HeaderColorBk.i
  HeaderColorSelect.l
  HeaderColorSelectBk.i
  Array HeaderColumnColor.l(#LISTICON_COLUMN_SIZE)
  Array HeaderColumnColorBk.i(#LISTICON_COLUMN_SIZE)
  Text.s{256}
  Null.u
EndStructure

Global NewMap ListIconData.udtListIconData()
Global CheckBoxSize = DesktopScaledX(16)
Global HeaderLineColor = GetSysColorBrush_(#COLOR_3DFACE)

; ----

;- Private

Procedure LvnGetDispInfoCB(*Data.NMLVDISPINFO)
  Protected gadget, *items.udtListIconData
  With *Data
    gadget = \hdr\idFrom
    If Not FindMapElement(ListIconData(), Str(gadget))
      ProcedureReturn #False
    EndIf
    *items = @ListIconData()
    If \item\iItem >= *items\CountItems
      ProcedureReturn #False
    EndIf
    SelectElement(*items\Items(), \item\iItem)
    If \item\mask & #LVIF_TEXT
      \item\pszText = @*items\Items()\Column(\item\iSubItem)
    EndIf
    If \item\mask & #LVIF_STATE
      \item\stateMask = #LVIS_STATEIMAGEMASK
      \item\state = *items\Items()\State
    EndIf
    If \item\mask & #LVIF_IMAGE
      If *items\Items()\ItemImage >= 0
        \item\iImage = *items\Items()\ItemImage
      EndIf
    EndIf
    ProcedureReturn #True
  EndWith
  
EndProcedure

; ----

Procedure LvnCustomDrawCB(*DrawData.NMLVCUSTOMDRAW)
  Protected r1, gadget, gadgetcolor, *items.udtListIconData
  With *DrawData
    gadget = \nmcd\hdr\idFrom
    If Not FindMapElement(ListIconData(), Str(gadget))
      ProcedureReturn #PB_Default
    EndIf
    *items = @ListIconData()
    If \nmcd\dwItemSpec >= *items\CountItems
      ProcedureReturn #PB_ProcessPureBasicEvents
    EndIf
    If \nmcd\dwItemSpec < 0
      ProcedureReturn #PB_ProcessPureBasicEvents
    EndIf
    Select \nmcd\dwDrawStage
      Case #CDDS_PREPAINT
        r1 = #PB_ProcessPureBasicEvents
        
      Case #CDDS_ITEMPREPAINT
        r1 = #CDRF_NOTIFYSUBITEMDRAW
        
      Case #CDDS_SUBITEMPREPAINT
        SelectElement(*items\Items(), \nmcd\dwItemSpec)
        ; Text Color
        If *items\Items()\ColumnColor(\iSubItem) <> #PB_Default
          \clrText = *items\Items()\ColumnColor(\iSubItem)
        ElseIf *items\Items()\ItemColor <> #PB_Default
          \clrText = *items\Items()\ItemColor
        Else
          gadgetcolor = GetGadgetColor(\nmcd\hdr\idFrom, #PB_Gadget_FrontColor)
          If gadgetcolor = #PB_Default
            \clrText = #CLR_DEFAULT
          Else
            \clrText = gadgetcolor
          EndIf
        EndIf
        ; Back Color
        If *items\Items()\ColumnColorBk(\iSubItem) <> #PB_Default
          \clrTextBk = *items\Items()\ColumnColorBk(\iSubItem)
        ElseIf *items\Items()\ItemColorBk <> #PB_Default
          \clrTextBk = *items\Items()\ItemColorBk
        Else
          gadgetcolor = GetGadgetColor(\nmcd\hdr\idFrom, #PB_Gadget_BackColor)
          If gadgetcolor = #PB_Default
            \clrTextBk = #CLR_DEFAULT
          Else
            \clrTextBk = gadgetcolor
          EndIf
        EndIf
        r1 = #CDRF_DODEFAULT
        
      Default
        r1 = #PB_ProcessPureBasicEvents
        
    EndSelect
    ProcedureReturn r1
  EndWith
EndProcedure

; ----

Procedure LvnClickCB(*ItemData.NMITEMACTIVATE)
  Protected gadget, *items.udtListIconData, lvItem.LVITEM, rect.RECT
  With *ItemData
    gadget = \hdr\idFrom
    If Not FindMapElement(ListIconData(), Str(gadget))
      ProcedureReturn #False
    EndIf
    If Not (ListIconData()\ExStyle & #LVS_EX_CHECKBOXES)
      ProcedureReturn #False
    EndIf
    If \iItem < 0
      ProcedureReturn #False
    EndIf
    If \ptAction\x > CheckBoxSize
      ProcedureReturn #False
    EndIf
    If \iItem >= ListIconData()\CountItems
      ProcedureReturn #False
    EndIf
    SelectElement(ListIconData()\Items(), \iItem)
    If ListIconData()\Items()\State & #LVIS_CHECKED
      ListIconData()\Items()\State | #LVIS_UNCHECKED & ~#LVIS_CHECKED
    Else
      ListIconData()\Items()\State | #LVIS_CHECKED & ~#LVIS_UNCHECKED
    EndIf
    ; Redraw selected item
    SendMessage_(\hdr\hwndFrom, #LVM_GETITEMRECT, \iItem, @rect)
    InvalidateRect_(\hdr\hwndFrom, rect, #True)
    ; Send PB message
    PostEvent(#PB_Event_Gadget, GetActiveWindow(), \hdr\idFrom, #PB_EventType_StatusChange, \iItem)
    ProcedureReturn #True
  EndWith
EndProcedure

; ----

Procedure LvnKeyDownCB(*KeyData.LVKEYDOWN)
  Protected gadget, iItem, *items.udtListIconData, lvItem.LVITEM, rect.RECT
  With *KeyData
    gadget = \hdr\idFrom
    If Not FindMapElement(ListIconData(), Str(gadget))
      ProcedureReturn #False
    EndIf
    If Not (ListIconData()\ExStyle & #LVS_EX_CHECKBOXES)
      ProcedureReturn #False
    EndIf
    If \wVKey <> #VK_SPACE
      ProcedureReturn #False
    EndIf
    iItem = SendMessage_(\hdr\hwndFrom, #LVM_GETSELECTIONMARK, 0, 0)
    If iItem < 0 Or iItem >= ListIconData()\CountItems
      ProcedureReturn #False
    EndIf
    SelectElement(ListIconData()\Items(), iItem)
    If ListIconData()\Items()\State & #LVIS_CHECKED
      ListIconData()\Items()\State | #LVIS_UNCHECKED & ~#LVIS_CHECKED
    Else
      ListIconData()\Items()\State | #LVIS_CHECKED & ~#LVIS_UNCHECKED
    EndIf
    ; Redraw selected item
    SendMessage_(\hdr\hwndFrom, #LVM_GETITEMRECT, iItem, @rect)
    InvalidateRect_(\hdr\hwndFrom, rect, #True)
    ; Send PB message
    PostEvent(#PB_Event_Gadget, GetActiveWindow(), \hdr\idFrom, #PB_EventType_StatusChange, iItem)
    ProcedureReturn #True
  EndWith
EndProcedure

; ----

Procedure _DrawHeader(*ListIconData.udtListIconData, *lpCD.NMCUSTOMDRAW)
  Protected hdi.HDITEM, format, Color.l, ColorBk.i
  
  With *ListIconData
    Select *lpCD\dwDrawStage
      Case #CDDS_PREPAINT
        ProcedureReturn #CDRF_NOTIFYITEMDRAW
        
      Case #CDDS_ITEMPREPAINT
        If *lpCD\uItemState & #CDIS_SELECTED
          ; Selected Text and Background Color
          Color = \HeaderColorSelect
          ColorBk = \HeaderColorSelectBk
        Else
          ; Text Color
          If \HeaderColumnColor(*lpCD\dwItemSpec) <> #PB_Default
            Color = \HeaderColumnColor(*lpCD\dwItemSpec)
          ElseIf \HeaderColor <> #PB_Default
            Color = \HeaderColor
          Else
            color = #Black
          EndIf
          ; Background Color
          If \HeaderColumnColorBk(*lpCD\dwItemSpec) <> #PB_Default
            ColorBk = \HeaderColumnColorBk(*lpCD\dwItemSpec)
          ElseIf \HeaderColorBk <> #PB_Default
            ColorBk = \HeaderColorBk
          EndIf
        EndIf
        ; Get Header Item
        hdi\mask = #HDI_TEXT | #HDI_FORMAT
        hdi\pszText = *ListIconData + OffsetOf(udtListIconData\Text)
        hdi\cchTextMax = 256
        SendMessage_(*lpCD\hdr\hwndFrom, #HDM_GETITEM, *lpCD\dwItemSpec, @hdi)
        ; Draw Background
        If ColorBk <> #PB_Default
          FrameRect_(*lpCD\hdc, *lpCD\rc, HeaderLineColor)
          *lpCD\rc\left + 0
          *lpCD\rc\right - 1
          FillRect_(*lpCD\hdc, *lpCD\rc, ColorBk)
        EndIf
        ; Draw Text
        SetBkMode_(*lpCD\hdc,#TRANSPARENT)
        SetTextColor_(*lpCD\hdc, Color)
        format = #DT_VCENTER | #DT_SINGLELINE | #DT_END_ELLIPSIS
        If hdi\fmt & #HDF_CENTER
          format | #DT_CENTER
        EndIf
        If hdi\fmt & #HDF_RIGHT
          format | #DT_RIGHT
        EndIf
        *lpCD\rc\left + 6
        *lpCD\rc\right - 5
        DrawText_(*lpCD\hdc, @\Text, Len(\Text), *lpCD\rc, format)
        ProcedureReturn #CDRF_SKIPDEFAULT
        
      Default
        ProcedureReturn #CDRF_DODEFAULT
        
    EndSelect
  EndWith
  
EndProcedure

; ----

Procedure LvnGadgetCB(hWnd, uMsg, wParam, lParam, uIdSubclass, dwRefData)
  Protected *nmh.NMHDR
  
  Select uMsg
    Case #WM_NOTIFY
      *nmh = lParam
      Select *nmh\code
        Case #NM_CUSTOMDRAW
            ProcedureReturn _DrawHeader(dwRefData, lParam)
          
      EndSelect
      
  EndSelect
  
  ProcedureReturn DefSubclassProc_(hWnd, uMsg, wParam, lParam)
  
EndProcedure

; ----

Procedure _AddListIconImage(Gadget, *ListIconData.udtListIconData, *Item.udtListIconItem, ImageID)
  Protected IsImage, Index
  
  With *ListIconData
    If Not \hImageList
      \hImageList = ImageList_Create_(16, 16, #ILC_COLOR32, 2, 0)
      If \hImageList
        SendMessage_(GadgetID(Gadget), #LVM_SETIMAGELIST, #LVSIL_SMALL, ListIconData()\hImageList)
      Else
        ProcedureReturn #False
      EndIf
    EndIf
    If Not FindMapElement(\ImageList(), Str(ImageID))
      AddMapElement(\ImageList(), Str(ImageID))
      Index = ImageList_Add_(\hImageList, ImageID, 0)
      If index < 0
        DeleteMapElement(\ImageList())
        ProcedureReturn #False
      EndIf
      \ImageList() = Index
    EndIf
    *Item\ItemImage = \ImageList()
    ProcedureReturn #True
  EndWith
EndProcedure

; ----

;- Public

Procedure ListIconGadgetEx(Gadget, x, y, Width, height, FirstColumnTitle$, FirstColumnWith, Flags=0)
  Protected id, ExStyle , hHeader
  
  id = ListIconGadget(Gadget, x, y, Width, height, FirstColumnTitle$, FirstColumnWith, Flags | #LVS_OWNERDATA)
  If id
    If Gadget = #PB_Any
      Gadget = id
    EndIf
    If FindMapElement(ListIconData(), Str(Gadget))
      ClearList(ListIconData()\Items())
    Else
      AddMapElement(ListIconData(), Str(Gadget))
    EndIf
    ; Fix Gridlines
    ExStyle = SendMessage_(GadgetID(Gadget), #LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0)
    If Flags & #PB_ListIcon_GridLines
      ExStyle | #LVS_EX_GRIDLINES
      SendMessage_(GadgetID(Gadget), #LVM_SETEXTENDEDLISTVIEWSTYLE, 0, ExStyle)
    EndIf
    ListIconData()\ExStyle = ExStyle
    ; Items
    ListIconData()\CountItems = 0
    ListIconData()\CountColumns = 1
    ; Header
    ListIconData()\HeaderColor = #PB_Default
    ListIconData()\HeaderColorBk = #PB_Default
    ListIconData()\HeaderColorSelect = #PB_Default
    ListIconData()\HeaderColorSelectBk = #PB_Default
    FillMemory(ListIconData()\HeaderColumnColor(), #LISTICON_COLUMN_SIZE * SizeOf(LONG), #PB_Default, #PB_Long)
    FillMemory(ListIconData()\HeaderColumnColorBk(), #LISTICON_COLUMN_SIZE * SizeOf(INTEGER), #PB_Default, #PB_Integer)
  EndIf
  ProcedureReturn id
EndProcedure

; ----

Procedure FreeListIconGadget(Gadget)
  Protected count, index
  
  If GadgetType(Gadget) = #PB_GadgetType_ListIcon
    If FindMapElement(ListIconData(), Str(Gadget))
      If ListIconData()\IsHeaderColor
        RemoveWindowSubclass_(GadgetID(Gadget), @LvnGadgetCB(), @ListIconData())
      EndIf
      SendMessage_(GadgetID(Gadget), #LVM_SETITEMCOUNT, 0, 1)
      ; Release Header Colors
      If ListIconData()\HeaderColorBk <> #PB_Default
        DeleteObject_(ListIconData()\HeaderColorBk)
        ListIconData()\HeaderColorBk = #PB_Default
      EndIf
      If ListIconData()\HeaderColorSelectBk <> #PB_Default
        DeleteObject_(ListIconData()\HeaderColorSelectBk)
        ListIconData()\HeaderColorSelectBk = #PB_Default
      EndIf
      count = ListIconData()\CountColumns - 1
      For index = 0 To count
        If ListIconData()\HeaderColumnColorBk(index) <> #PB_Default
          DeleteObject_(ListIconData()\HeaderColumnColorBk(index))
        EndIf
      Next
      ; Release Images
      If ListIconData()\hImageList
        ImageList_Destroy_(ListIconData()\hImageList)
      EndIf
      InvalidateRect_(GadgetID(Gadget), 0, #True)
      DeleteMapElement(ListIconData())
    EndIf
    FreeGadget(Gadget)
  EndIf
EndProcedure

; ----

Procedure UpdateListIconGadget(Gadget)
  If FindMapElement(ListIconData(), Str(gadget))
    SendMessage_(GadgetID(Gadget), #LVM_SETITEMCOUNT, ListIconData()\CountItems, 1)
    InvalidateRect_(GadgetID(Gadget), 0, #True)
  EndIf
EndProcedure

; ----

Procedure AddGadgetColumnEx(Gadget, Column, Title$, Width)
  Protected index, count, array_size
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
    ProcedureReturn AddGadgetColumn(Gadget, Column, Title$, Width)
  EndIf
  If Not FindMapElement(ListIconData(), Str(Gadget))
    ProcedureReturn #False
  EndIf
  count = ListIconData()\CountColumns
  If Column > count
    ProcedureReturn #False
  EndIf
  If Column = -1
    Column = count
  EndIf
  ListIconData()\CountColumns + 1
  SendMessage_(GadgetID(Gadget), #LVM_SETITEMCOUNT, 0, 1)
  AddGadgetColumn(Gadget, Column, Title$, Width)
  SendMessage_(GadgetID(Gadget), #LVM_SETITEMCOUNT, ListIconData()\CountItems, 1)
  ; Update Header Column
  array_size = ListIconData()\CountColumns
  If ArraySize(ListIconData()\HeaderColumnColor()) < array_size
    ReDim ListIconData()\HeaderColumnColor(array_size)
    ReDim ListIconData()\HeaderColumnColorBk(array_size)
  EndIf
  For index = count To Column + 1 Step -1
    ListIconData()\HeaderColumnColor(index) = ListIconData()\HeaderColumnColor(index-1)
    ListIconData()\HeaderColumnColorBk(index) = ListIconData()\HeaderColumnColorBk(index-1)
  Next
  ListIconData()\HeaderColumnColor(Column) = #PB_Default
  ListIconData()\HeaderColumnColorBk(Column) = #PB_Default
  ; Update Items Columns
  array_size = 0
  If ListIconData()\CountItems
    FirstElement(ListIconData()\Items())
    If ArraySize(ListIconData()\Items()\Column()) < ListIconData()\CountColumns
      array_size = ListIconData()\CountColumns
    EndIf
    ForEach ListIconData()\Items()
      If array_size
        ReDim ListIconData()\Items()\Column(array_size)
        ReDim ListIconData()\Items()\ColumnColor(array_size)
        ReDim ListIconData()\Items()\ColumnColorBk(array_size)
      EndIf
      For index = count To Column + 1 Step -1
        ListIconData()\Items()\Column(index) = ListIconData()\Items()\Column(index-1)
        ListIconData()\Items()\ColumnColor(index) = ListIconData()\Items()\ColumnColor(index-1)
        ListIconData()\Items()\ColumnColorBk(index) = ListIconData()\Items()\ColumnColorBk(index-1)
      Next
      ListIconData()\Items()\Column(Column) = #Empty$
      ListIconData()\Items()\ColumnColor(Column) = #PB_Default
      ListIconData()\Items()\ColumnColorBk(Column) = #PB_Default
    Next
  EndIf
  InvalidateRect_(GadgetID(Gadget), 0, #True)
  ProcedureReturn #True
EndProcedure

; ----

Procedure RemoveGadgetColumnEx(Gadget, Column)
  Protected index, count
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
    ProcedureReturn RemoveGadgetColumn(Gadget, Column)
  EndIf
  If Not FindMapElement(ListIconData(), Str(Gadget))
    ProcedureReturn #False
  EndIf
  If Column = #PB_All
    count = ListIconData()\CountColumns - 1
    SendMessage_(GadgetID(Gadget), #LVM_SETITEMCOUNT, 0, 1)
    ClearList(ListIconData()\Items())
    ListIconData()\CountItems = 0
    ListIconData()\CountColumns = 0
    ; Release Header Column Colors
    For index = 0 To count
      If ListIconData()\HeaderColumnColorBk(index) <> #PB_Default
        DeleteObject_(ListIconData()\HeaderColumnColorBk(index))
      EndIf
    Next
    Dim ListIconData()\HeaderColumnColor(#LISTICON_COLUMN_SIZE)
    Dim ListIconData()\HeaderColumnColorBk(#LISTICON_COLUMN_SIZE)
    FillMemory(ListIconData()\HeaderColumnColor(), #LISTICON_COLUMN_SIZE * SizeOf(LONG), #PB_Default, #PB_Long)
    FillMemory(ListIconData()\HeaderColumnColorBk(), #LISTICON_COLUMN_SIZE * SizeOf(INTEGER), #PB_Default, #PB_Integer)
    RemoveGadgetColumn(Gadget, Column)
  Else
    count = ListIconData()\CountColumns
    If Column >= count
      ProcedureReturn #False
    EndIf
    If Column < 0
      ProcedureReturn #False
    EndIf
    SendMessage_(GadgetID(Gadget), #LVM_SETITEMCOUNT, 0, 1)
    RemoveGadgetColumn(Gadget, Column)
    SendMessage_(GadgetID(Gadget), #LVM_SETITEMCOUNT, ListIconData()\CountItems, 1)
    ListIconData()\CountColumns - 1
    count - 1
    ; Update Header Columns
    If ListIconData()\HeaderColumnColorBk(Column) <> #PB_Default
      DeleteObject_(ListIconData()\HeaderColumnColorBk(Column))
    EndIf
    For index = Column To count
      ListIconData()\HeaderColumnColor(index) = ListIconData()\HeaderColumnColor(index + 1)
      ListIconData()\HeaderColumnColorBk(index) = ListIconData()\HeaderColumnColorBk(index + 1)
    Next
    ListIconData()\HeaderColumnColor(index) = #PB_Default
    If ListIconData()\HeaderColumnColorBk(index) <> #PB_Default
      DeleteObject_(ListIconData()\HeaderColumnColorBk(index))
      ListIconData()\HeaderColumnColorBk(index) = #PB_Default
    EndIf
    ; Update Items Columns
    ForEach ListIconData()\Items()
      For index = Column To count
        ListIconData()\Items()\Column(index) = ListIconData()\Items()\Column(index + 1)
        ListIconData()\Items()\ColumnColor(index) = ListIconData()\Items()\ColumnColor(index + 1)
        ListIconData()\Items()\ColumnColorBk(index) = ListIconData()\Items()\ColumnColorBk(index + 1)
      Next
      ListIconData()\Items()\Column(index) = #Empty$
      ListIconData()\Items()\ColumnColor(index) = #PB_Default
      ListIconData()\Items()\ColumnColorBk(index) = #PB_Default
    Next
  EndIf
  If ListIconData()\CountColumns = 0
    If ListIconData()\IsHeaderColor
      ListIconData()\IsHeaderColor = #False
      RemoveWindowSubclass_(GadgetID(Gadget), @LvnGadgetCB(), @ListIconData())
    EndIf
    ; Release Header Colors
    ListIconData()\HeaderColor = #PB_Default
    If ListIconData()\HeaderColorBk <> #PB_Default
      DeleteObject_(ListIconData()\HeaderColorBk)
      ListIconData()\HeaderColorBk = #PB_Default
    EndIf
    ; Release Header Selection Colors
    ListIconData()\HeaderColorSelect = #PB_Default
    If ListIconData()\HeaderColorSelectBk <> #PB_Default
      DeleteObject_(ListIconData()\HeaderColorSelectBk)
      ListIconData()\HeaderColorSelectBk = #PB_Default
    EndIf
  EndIf
  InvalidateRect_(GadgetID(Gadget), 0, #True)
  ProcedureReturn #True
EndProcedure

; ----

Procedure AddGadgetItemEx(Gadget, Item, Text$, ImageID=0, Flags=0)
  Protected *item.udtListIconItem, count, columns, index, size
  
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
    ProcedureReturn AddGadgetItem(Gadget, Item, Text$, ImageID, Flags)
  EndIf
  If Not FindMapElement(ListIconData(), Str(Gadget))
    ProcedureReturn #False
  EndIf
  count = ListSize(ListIconData()\Items())
  If Item = -1
    LastElement(ListIconData()\Items())
    AddElement(ListIconData()\Items())
  ElseIf Item = count
    LastElement(ListIconData()\Items())
    AddElement(ListIconData()\Items())
  ElseIf Item < count
    SelectElement(ListIconData()\Items(), Item)
    InsertElement(ListIconData()\Items())
  Else
    ProcedureReturn #False
  EndIf
  ListIconData()\CountItems = ListSize(ListIconData()\Items())
  columns = ListIconData()\CountColumns
  If ArraySize(ListIconData()\Items()\Column()) < columns
    ReDim ListIconData()\Items()\Column(columns)
    ReDim ListIconData()\Items()\ColumnColor(columns)
    ReDim ListIconData()\Items()\ColumnColorBk(ListIconData()\CountColumns)
  EndIf
  *item = @ListIconData()\Items()
  *item\State = #LVIS_UNCHECKED
  *item\ItemColor = #PB_Default
  *item\ItemColorBk = #PB_Default
  *item\ItemImage = -1
  *item\UserData = 0
  columns = CountString(Text$, #LF$)
  If columns >= ListIconData()\CountColumns
    columns = ListIconData()\CountColumns - 1
  EndIf
  For index = 0 To columns
    *item\Column(index) = StringField(Text$, index + 1, #LF$)
  Next
  columns + 1
  For index = columns To ListIconData()\CountColumns - 1
    *item\Column(index) = #Empty$
  Next
  size = ArraySize(ListIconData()\Items()\Column()) * SizeOf(LONG)
  FillMemory(@*item\ColumnColor(), size, #PB_Default, #PB_Long)
  FillMemory(@*item\ColumnColorBk(), size, #PB_Default, #PB_Long)
  If ImageID
    _AddListIconImage(Gadget, @ListIconData(), *Item, ImageID)
  EndIf
EndProcedure

; ----

Procedure RemoveGadgetItemEx(Gadget, Item)
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
    ProcedureReturn RemoveGadgetItem(Gadget, Item)
  EndIf
  If Not FindMapElement(ListIconData(), Str(Gadget))
    ProcedureReturn #False
  EndIf
  If Item >= ListIconData()\CountItems
    ProcedureReturn #False
  EndIf
  If Item < 0
    ProcedureReturn #False
  EndIf
  SelectElement(ListIconData()\Items(), Item)
  DeleteElement(ListIconData()\Items())
  ListIconData()\CountItems = ListSize(ListIconData()\Items())
  SendMessage_(GadgetID(Gadget), #LVM_SETITEMCOUNT, ListIconData()\CountItems, 1)
  InvalidateRect_(GadgetID(Gadget), 0, #True)
  ProcedureReturn #True
EndProcedure

; ----

Procedure ClearGadgetItemsEx(Gadget)
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
    ProcedureReturn ClearGadgetItems(Gadget)
  EndIf
  If Not FindMapElement(ListIconData(), Str(Gadget))
    ProcedureReturn #False
  EndIf
  ClearList(ListIconData()\Items())
  ListIconData()\CountItems = 0
  SendMessage_(GadgetID(Gadget), #LVM_SETITEMCOUNT, 0, 1)
  InvalidateRect_(GadgetID(Gadget), 0, #True)
  ProcedureReturn #True
EndProcedure

; ----

Procedure SetGadgetItemStateEx(Gadget, Item, State)
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
    ProcedureReturn SetGadgetItemState(Gadget, Item, State)
  EndIf
  If Not FindMapElement(ListIconData(), Str(Gadget))
    ProcedureReturn #False
  EndIf
  If Item >= ListIconData()\CountItems
    ProcedureReturn #False
  EndIf
  If Item < 0
    ProcedureReturn #False
  EndIf
  SelectElement(ListIconData()\Items(), Item)
  If State & #PB_ListIcon_Checked
    ListIconData()\Items()\State | #LVIS_CHECKED & ~#LVIS_UNCHECKED
  Else
    ListIconData()\Items()\State | #LVIS_UNCHECKED & ~#LVIS_CHECKED
  EndIf
EndProcedure

; ----

Procedure GetGadgetItemStateEx(Gadget, Item)
  Protected r1
  
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
    ProcedureReturn GetGadgetItemState(Gadget, Item)
  EndIf
  If Not FindMapElement(ListIconData(), Str(Gadget))
    ProcedureReturn 0
  EndIf
  If Item >= ListIconData()\CountItems
    ProcedureReturn 0
  EndIf
  If Item < 0
    ProcedureReturn 0
  EndIf
  SelectElement(ListIconData()\Items(), Item)
  If ListIconData()\Items()\State & #LVIS_CHECKED
    r1 = #PB_ListIcon_Checked
  EndIf
  If SendMessage_(GadgetID(Gadget), #LVM_GETITEMSTATE, Item, #LVIS_SELECTED) & #LVIS_SELECTED
    r1 | #PB_ListIcon_Selected
  EndIf
  ProcedureReturn r1
EndProcedure

; ----

Procedure SetGadgetItemDataEx(Gadget, Item, Value)
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
    ProcedureReturn SetGadgetItemData(Gadget, Item, Value)
  EndIf
  If Not FindMapElement(ListIconData(), Str(Gadget))
    ProcedureReturn #False
  EndIf
  If Item >= ListIconData()\CountItems
    ProcedureReturn #False
  EndIf
  If Item < 0
    ProcedureReturn #False
  EndIf
  SelectElement(ListIconData()\Items(), Item)
  ListIconData()\Items()\UserData = Value
  ProcedureReturn #True
EndProcedure

; ----

Procedure GetGadgetItemDataEx(Gadget, Item)
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
    ProcedureReturn GetGadgetItemData(Gadget, Item)
  EndIf
  If Not FindMapElement(ListIconData(), Str(Gadget))
    ProcedureReturn 0
  EndIf
  If Item >= ListIconData()\CountItems
    ProcedureReturn 0
  EndIf
  If Item < 0
    ProcedureReturn 0
  EndIf
  SelectElement(ListIconData()\Items(), Item)
  ProcedureReturn ListIconData()\Items()\UserData
EndProcedure

; ----

Procedure SetGadgetItemTextEx(Gadget, Item, Text$, Column=-1)
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon Or Item < 0
    If Column = -1
      ProcedureReturn SetGadgetItemText(Gadget, Item, Text$)
    Else
      ProcedureReturn SetGadgetItemText(Gadget, Item, Text$, Column)
    EndIf
  EndIf
  If Not FindMapElement(ListIconData(), Str(Gadget))
    ProcedureReturn 0
  EndIf
  If Item >= ListIconData()\CountItems
    ProcedureReturn 0
  EndIf
  If Column >= ListIconData()\CountColumns
    ProcedureReturn #False
  EndIf
  If Column < 0
    Column = 0
  EndIf
  SelectElement(ListIconData()\Items(), Item)
  ListIconData()\Items()\Column(Column) = Text$
  InvalidateRect_(GadgetID(Gadget), 0, #True)
  ProcedureReturn #True
EndProcedure

; ----

Procedure.s GetGadgetItemTextEx(Gadget, Item, Column=-1)
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon Or Item < 0
    If Column = -1
      ProcedureReturn GetGadgetItemText(Gadget, Item)
    Else
      ProcedureReturn GetGadgetItemText(Gadget, Item, Column)
    EndIf
  EndIf
  If Not FindMapElement(ListIconData(), Str(Gadget))
    ProcedureReturn ""
  EndIf
  If Item >= ListIconData()\CountItems
    ProcedureReturn ""
  EndIf
  If Column >= ListIconData()\CountColumns
    ProcedureReturn ""
  EndIf
  If Column < 0
    Column = 0
  EndIf
  SelectElement(ListIconData()\Items(), Item)
  ProcedureReturn ListIconData()\Items()\Column(Column)
EndProcedure

; ----

Procedure SetGadgetItemColorEx(Gadget, Item, ColorType, Color, Column=-1)
  Protected color_red, color_green, color_blue
  
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
    If Column = -1
      ProcedureReturn SetGadgetItemColor(Gadget, Item, ColorType, Color)
    Else
      ProcedureReturn SetGadgetItemColor(Gadget, Item, ColorType, Color, Column)
    EndIf
  EndIf
  If Not FindMapElement(ListIconData(), Str(Gadget))
    ProcedureReturn 0
  EndIf
  If Item >= ListIconData()\CountItems
    ProcedureReturn 0
  EndIf
  If Item < 0
    ; Set Header Colors
    If Not ListIconData()\IsHeaderColor
      ; Initalize Header Colors
      ListIconData()\IsHeaderColor = #True
      SetWindowSubclass_(GadgetID(0), @LvnGadgetCB(), @ListIconData(), @ListIconData())
      ListIconData()\HeaderColorSelect = #Black ; GetSysColor_(#COLOR_HIGHLIGHTTEXT)
      ListIconData()\HeaderColorSelectBk = CreateSolidBrush_($FFE1CA) ; CreateSolidBrush_(GetSysColor_(#COLOR_HIGHLIGHT))
    EndIf
    If Column < 0
      ; Set Header Color
      Select ColorType
        Case #PB_Gadget_FrontColor
          ListIconData()\HeaderColor = Color
        Case #PB_Gadget_BackColor
          If ListIconData()\HeaderColorBk <> #PB_Default
            DeleteObject_(ListIconData()\HeaderColorBk)
            DeleteObject_(ListIconData()\HeaderColorSelectBk)
          EndIf
          ListIconData()\HeaderColorBk = CreateSolidBrush_(Color)
          color_red = Red(color) * 92 / 100
          color_green = Green(color) * 92 / 100
          color_blue = Blue(color) * 92 / 100
          color = RGB(color_red, color_green, color_blue)
          ListIconData()\HeaderColorSelect = ListIconData()\HeaderColor
          ListIconData()\HeaderColorSelectBk = CreateSolidBrush_(Color)
          
      EndSelect
      ProcedureReturn #True
    ElseIf Column < ListIconData()\CountColumns
      ; Set Header Column Color
      Select ColorType
        Case #PB_Gadget_FrontColor
          ListIconData()\HeaderColumnColor(Column) = Color
        Case #PB_Gadget_BackColor
          If ListIconData()\HeaderColumnColorBk(Column) <> #PB_Default
            DeleteObject_(ListIconData()\HeaderColumnColorBk(Column))
          EndIf
          ListIconData()\HeaderColumnColorBk(Column) = CreateSolidBrush_(Color)
          
      EndSelect
      ProcedureReturn #True
    Else
      ProcedureReturn #False
    EndIf
  Else
    ; Set Items Colors
    SelectElement(ListIconData()\Items(), Item)
    If Column < 0
      ; Set Item Color
      Select ColorType
        Case #PB_Gadget_FrontColor
          ListIconData()\Items()\ItemColor = Color
        Case #PB_Gadget_BackColor
          ListIconData()\Items()\ItemColorBk = Color
      EndSelect
      ProcedureReturn #True
    ElseIf Column < ListIconData()\CountColumns
      ; Set Column Color
      Select ColorType
        Case #PB_Gadget_FrontColor
          ListIconData()\Items()\ColumnColor(Column) = Color
        Case #PB_Gadget_BackColor
          ListIconData()\Items()\ColumnColorBk(Column) = Color
      EndSelect
      ProcedureReturn #True
    Else
      ProcedureReturn #False
    EndIf
  EndIf
EndProcedure

; ----

Procedure GetGadgetItemColorEx(Gadget, Item, ColorType, Column=-1)
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
    If Column = -1
      ProcedureReturn GetGadgetItemColor(Gadget, Item, ColorType)
    Else
      ProcedureReturn GetGadgetItemColor(Gadget, Item, ColorType, Column)
    EndIf
  EndIf
  If Not FindMapElement(ListIconData(), Str(Gadget))
    ProcedureReturn 0
  EndIf
  If Item >= ListIconData()\CountItems
    ProcedureReturn 0
  EndIf
  If Item < 0
    ProcedureReturn #PB_Default
  EndIf
  If Column >= ListIconData()\CountColumns
    ProcedureReturn #PB_Default
  EndIf
  SelectElement(ListIconData()\Items(), Item)
  If Column < 0
    Select ColorType
      Case #PB_Gadget_FrontColor
        ProcedureReturn ListIconData()\Items()\ItemColor
      Case #PB_Gadget_BackColor
        ProcedureReturn ListIconData()\Items()\ItemColor
    EndSelect
  Else
    Select ColorType
      Case #PB_Gadget_FrontColor
        ProcedureReturn ListIconData()\Items()\ColumnColor(Column)
      Case #PB_Gadget_BackColor
        ProcedureReturn ListIconData()\Items()\ColumnColorBk(Column)
    EndSelect
  EndIf
  ProcedureReturn #PB_Default
EndProcedure

; ----

Procedure SetGadgetItemImageEx(Gadget, Item, ImageID)
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
    ProcedureReturn SetGadgetItemImage(Gadget, Item, ImageID)
  EndIf
  If Not FindMapElement(ListIconData(), Str(Gadget))
    ProcedureReturn #False
  EndIf
  If Item >= ListIconData()\CountItems
    ProcedureReturn #False
  EndIf
  If Item < 0
    ProcedureReturn #False
  EndIf
  SelectElement(ListIconData()\Items(), Item)
  If ImageID
    _AddListIconImage(Gadget, @ListIconData(), @ListIconData()\Items(), ImageID)
  Else
    ListIconData()\Items()\ItemImage = -1
  EndIf
  ProcedureReturn #True
EndProcedure

; ----

;- Private Special Function

Procedure _CustomSortInteger(*a.udtListIconItem, *b.udtListIconItem)
  Protected *strA.string, *strB.string, a.i, b.i
  *strA = @*a\Reference
  *strB = @*b\Reference
  a = Val(*strA\s)
  b = Val(*strB\s)
  If a < b
    ProcedureReturn #PB_Sort_Lesser
  ElseIf a > b
    ProcedureReturn #PB_Sort_Greater
  Else
    ProcedureReturn #PB_Sort_Equal
  EndIf
EndProcedure

Procedure _CustomSortDouble(*a.udtListIconItem, *b.udtListIconItem)
  Protected *strA.string, *strB.string, a.d, b.d
  *strA = @*a\Reference
  *strB = @*b\Reference
  a = ValD(*strA\s)
  b = ValD(*strB\s)
  If a < b
    ProcedureReturn #PB_Sort_Lesser
  ElseIf a > b
    ProcedureReturn #PB_Sort_Greater
  Else
    ProcedureReturn #PB_Sort_Equal
  EndIf
EndProcedure

;- Public Special Function

; Description
; - Option: #PB_Sort_Ascending, #PB_Sort_Descending, #PB_Sort_NoCase
; - Typ   : #PB_String, #PB_Integer, #PB_Double

Procedure SortListIconGadgetEx(Gadget, Option, Column, Typ = #PB_String, StartItem = #PB_Default, EndItem = #PB_Default)
  If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
    ProcedureReturn #False
  EndIf
  If FindMapElement(ListIconData(), Str(Gadget))
    If Column >= ListIconData()\CountColumns
      ProcedureReturn #False
    EndIf
    ; Build Sort Reference
    ForEach ListIconData()\Items()
      ListIconData()\Items()\Reference = @ListIconData()\Items()\Column(Column)
    Next
    If StartItem <> #PB_Default
      If EndItem = #PB_Default
        EndItem = ListIconData()\CountItems - 1
      EndIf
      Select Typ
        Case #PB_String
          SortStructuredList(ListIconData()\Items(), Option, OffsetOf(udtListIconItem\Reference), #PB_String, StartItem, EndItem)
        Case #PB_Integer
          CustomSortList(ListIconData()\Items(), @_CustomSortInteger(), Option, StartItem, EndItem)
        Case #PB_Double
          CustomSortList(ListIconData()\Items(), @_CustomSortDouble(), Option, StartItem, EndItem)
      EndSelect
    Else
      Select Typ
        Case #PB_String
          SortStructuredList(ListIconData()\Items(), Option, OffsetOf(udtListIconItem\Reference), #PB_String)
        Case #PB_Integer
          CustomSortList(ListIconData()\Items(), @_CustomSortInteger(), Option)
        Case #PB_Double
          CustomSortList(ListIconData()\Items(), @_CustomSortDouble(), Option)
      EndSelect
    EndIf
    InvalidateRect_(GadgetID(Gadget), 0, 1)
  EndIf
EndProcedure

; ----

;- Macros

CompilerIf #USE_GLOBAL_LISTICON_OWNER_DATA
  
  Macro ListIconGadget(Gadget, x, y, Width, height, FirstColumnTitle, FirstColumnWith, Flags=0)
    ListIconGadgetEx(Gadget, x, y, Width, height, FirstColumnTitle, FirstColumnWith, Flags)
  EndMacro
  
  Macro AddGadgetColumn(Gadget, Column, Title, Width)
    AddGadgetColumnEx(Gadget, Column, Title, Width)
  EndMacro
  
  Macro RemoveGadgetColumn(Gadget, Column)
    RemoveGadgetColumnEx(Gadget, Column)
  EndMacro
  
  Macro AddGadgetItem(Gadget, Item, Text, ImageID=0, Flags=0)
    AddGadgetItemEx(Gadget, Item, Text, ImageID, Flags)
  EndMacro
  
  Macro RemoveGadgetItem(Gadget, Item)
    RemoveGadgetItemEx(Gadget, Item)
  EndMacro
  
  Macro ClearGadgetItems(Gadget)
    ClearGadgetItemsEx(Gadget)
  EndMacro
  
  Macro SetGadgetItemState(Gadget, Item, State)
    SetGadgetItemStateEx(Gadget, Item, State)
  EndMacro
  
  Macro GetGadgetItemState(Gadget, Item)
    GetGadgetItemStateEx(Gadget, Item)
  EndMacro
  
  Macro SetGadgetItemData(Gadget, Item, Value)
    SetGadgetItemDataEx(Gadget, Item, Value)
  EndMacro
  
  Macro GetGadgetItemData(Gadget, Item)
    GetGadgetItemDataEx(Gadget, Item)
  EndMacro
  
  Macro SetGadgetItemText(Gadget, Item, Text, Column=-1)
    SetGadgetItemTextEx(Gadget, Item, Text, Column)
  EndMacro
  
  Macro GetGadgetItemText(Gadget, Item, Column=-1)
    GetGadgetItemTextEx(Gadget, Item, Column)
  EndMacro
  
  Macro SetGadgetItemColor(Gadget, Item, ColorType, Color, Column=-1)
    SetGadgetItemColorEx(Gadget, Item, ColorType, Color, Column)
  EndMacro
  
  Macro GetGadgetItemColor(Gadget, Item, ColorType, Column=-1)
    GetGadgetItemColorEx(Gadget, Item, ColorType, Column)
  EndMacro
  
  Macro SetGadgetItemImage(Gadget, Item, ImageID)
    SetGadgetItemImageEx(Gadget, Item, ImageID)
  EndMacro
  
CompilerEndIf

; ----

Procedure WinCallback(hWnd, uMsg, wParam, lParam) 
  Protected r1, *nmh.NMHDR
  
  Select uMsg
    Case #WM_NOTIFY
      *nmh = lParam
      Select *nmh\code
        Case #LVN_GETDISPINFO
          If LvnGetDispInfoCB(lParam)
            ProcedureReturn #True
          EndIf
          
        Case #NM_CUSTOMDRAW
          r1 = LvnCustomDrawCB(lParam)
          If r1 <> #PB_Default
            ProcedureReturn r1
          EndIf
          ; More Custom draw
          
        Case #NM_CLICK
          LvnClickCB(lParam)
          
        Case #LVN_KEYDOWN
          LvnKeyDownCB(lParam)
          
      EndSelect
  EndSelect 
  
  ProcedureReturn #PB_ProcessPureBasicEvents 
EndProcedure 

;- ********

;-Example

CompilerIf #PB_Compiler_IsMainFile
  
  #CharSortAscend = "▲ "
  #CharSortDescend = "▼ "
  
  UsePNGImageDecoder()
  
  Procedure ListFillData()
    Protected i, time, image0, image1, image2 , dblVal.d
    
    time = ElapsedMilliseconds()
    
    ; Create Images
    CreateImage(0, 16, 16, 32, #PB_Image_Transparent)
    If StartDrawing(ImageOutput(0))
      DrawingMode(#PB_2DDrawing_AllChannels)
      Circle(7, 7, 7, $FF000000 | #Red)
      DrawingMode(#PB_2DDrawing_Outlined)
      Circle(7, 7, 7, $FF000000 | #Black)
      StopDrawing()
    EndIf
    CreateImage(1, 16, 16, 32, #PB_Image_Transparent)
    If StartDrawing(ImageOutput(1))
      DrawingMode(#PB_2DDrawing_AllChannels)
      Circle(7, 7, 7, $FF000000 | #Yellow)
      DrawingMode(#PB_2DDrawing_Outlined)
      Circle(7, 7, 7, $FF000000 | #Black)
      StopDrawing()
    EndIf
    CreateImage(2, 16, 16, 32, #PB_Image_Transparent)
    If StartDrawing(ImageOutput(2))
      DrawingMode(#PB_2DDrawing_AllChannels)
      Circle(7, 7, 7, $FF000000 | #Green)
      DrawingMode(#PB_2DDrawing_Outlined)
      Circle(7, 7, 7, $FF000000 | #Black)
      StopDrawing()
    EndIf
    
    LoadImage(3, #PB_Compiler_Home + "Examples\Sources\Data\world.png")
    LoadImage(4, #PB_Compiler_Home + "Examples\Sources\Data\Drive.bmp")
    
    For i = 0 To 100000
      dblVal = 0.001 * Random(100000)
      AddGadgetItem(0, -1, "Item " + i + #LF$ + Str(i + 100) + #LF$ + Str(i + 1000) + #LF$ + Str(Random(10000)) + #LF$ + Str(Random(10000)) + #LF$ + StrD(dblVal, 3), ImageID(Random(4)))
    Next
    
    
    SetGadgetItemState(0, 2, #PB_ListIcon_Checked)
    
    ; Header Colors
    SetGadgetItemColor(0, -1, #PB_Gadget_FrontColor, $701919)
    SetGadgetItemColor(0, -1, #PB_Gadget_BackColor, $FFBF00)
    
    ; Header Column Color
    SetGadgetItemColor(0, -1, #PB_Gadget_FrontColor, #White, 2)
    SetGadgetItemColor(0, -1, #PB_Gadget_BackColor, #Blue, 2)
    
    ; Item Colors
    SetGadgetItemColor(0, 1, #PB_Gadget_BackColor, #Red)
    SetGadgetItemColor(0, 1, #PB_Gadget_FrontColor, #Yellow)
    SetGadgetItemColor(0, 1, #PB_Gadget_BackColor, #Green, 0)
    SetGadgetItemColor(0, 1, #PB_Gadget_FrontColor, #Black, 0)
    
    SetGadgetItemColor(0, 2, #PB_Gadget_BackColor, #Green, 1)
    SetGadgetItemColor(0, 2, #PB_Gadget_FrontColor, #Black, 1)
    
    SetGadgetItemColor(0, 3, #PB_Gadget_BackColor, #Red)
    SetGadgetItemColor(0, 3, #PB_Gadget_FrontColor, #Yellow)
    SetGadgetItemColor(0, 3, #PB_Gadget_BackColor, #Blue, 2)
    SetGadgetItemColor(0, 3, #PB_Gadget_FrontColor, #White, 2)
    
    SetGadgetItemColor(0, 5, #PB_Gadget_BackColor, $D0D0D0)
    
    i - 1
    SetGadgetItemColor(0, i, #PB_Gadget_BackColor, #Green)
    SetGadgetItemColor(0, i, #PB_Gadget_FrontColor, #Blue)
    
    AddGadgetItem(0, 10, "Insert Item 10" + #LF$ + Str(1) + #LF$ +Str(2))
    
    SetGadgetItemImage(0, 1, 0)
    SetGadgetItemImage(0, 3, 0)
    SetGadgetItemImage(0, 5, ImageID(0))
    
    UpdateListIconGadget(0)
    
    Debug "Fill Time " + Str(ElapsedMilliseconds() - time)
    
  EndProcedure
  
  Procedure ListCheckBoxData()
    Protected i, cnt
    
    cnt = CountGadgetItems(0) - 1
    For i = 0 To cnt
      If Random(1)
        SetGadgetItemState(0, i, #PB_ListIcon_Checked)
        SetGadgetItemColor(0, i, #PB_Gadget_FrontColor, #Black)
        SetGadgetItemColor(0, i, #PB_Gadget_BackColor, $FFE2B0)
      Else
        SetGadgetItemState(0, i, 0)
        SetGadgetItemColor(0, i, #PB_Gadget_FrontColor, #PB_Default)
        SetGadgetItemColor(0, i, #PB_Gadget_BackColor, #PB_Default)
      EndIf
    Next
    UpdateListIconGadget(0)
  EndProcedure
  
  ; ----
  
  Procedure UpdateWindow()
    Protected dx, dy
    dx = WindowWidth(0)
    dy = WindowHeight(0) - StatusBarHeight(0) - MenuHeight()
    ; Resize Gadgets
    ResizeGadget(0, 0, 0, dx, dy)
  EndProcedure
  
  LoadFont(0, "Arial", 11, #PB_Font_Bold)
  
  Procedure Main()
    Protected dx, dy, item, state, i, column, count, temp.s, time
    
    #WinStyle = #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget
    
    If OpenWindow(0, #PB_Ignore, #PB_Ignore, 640, 420, "Window ListIconGadget Owner Data", #WinStyle)
      ; MenuBar
      CreateMenu(0, WindowID(0))
      MenuTitle("&File")
      MenuItem(99, "E&xit")
      MenuTitle("Test")
      MenuItem(1, "Fill CheckBoxed")
      MenuItem(2, "Insert Column 1")
      MenuItem(3, "Remove Column 1")
      MenuItem(4, "Remove All Column")
      MenuItem(5, "Remove Item 1")
      MenuItem(6, "Remove All Items")
      MenuItem(7, "Get Selected Items")
      
      ; StatusBar
      CreateStatusBar(0, WindowID(0))
      AddStatusBarField(#PB_Ignore)
      
      ; Gadgets
      dx = WindowWidth(0)
      dy = WindowHeight(0) - StatusBarHeight(0) - MenuHeight()
      
      #ListStyle = #PB_ListIcon_GridLines | #PB_ListIcon_CheckBoxes | #PB_ListIcon_FullRowSelect | #PB_ListIcon_MultiSelect 
      ListIconGadget(0, 0, 0, dx, dy, "Column 0", 200, #ListStyle)
      For i = 1 To 6
        AddGadgetColumn(0, i, "Column " + i, 200)
      Next
      
      SetGadgetItemAttribute(0, 0, #PB_ListIcon_ColumnAlignment, #PB_ListIcon_Center, 1)
      SetGadgetItemAttribute(0, 0, #PB_ListIcon_ColumnAlignment, #PB_ListIcon_Right, 2)
      
      SetGadgetColor(0, #PB_Gadget_FrontColor, $4F4F2F)
      SetGadgetColor(0, #PB_Gadget_BackColor, $FFFFE0)
      SetGadgetFont(0, FontID(0))
      
      ; Bind Events
      BindEvent(#PB_Event_SizeWindow, @UpdateWindow(), 0)
      
      ; Set Window Callback
      SetWindowCallback(@WinCallback())
      
      ListFillData()
      
      ; Main Loop
      Repeat
        Select WaitWindowEvent()
          Case #PB_Event_CloseWindow
            Select EventWindow()
              Case 0
                FreeListIconGadget(0)
                Break
            EndSelect
            
          Case #PB_Event_Menu
            Select EventMenu()
              Case 1
                ListCheckBoxData()
              Case 2
                AddGadgetColumn(0, 1, "Insert", 100)
              Case 3
                RemoveGadgetColumn(0, 1)
              Case 4
                RemoveGadgetColumn(0, #PB_All)
              Case 5
                RemoveGadgetItem(0, 1)
              Case 6
                ClearGadgetItems(0)
              Case 7
                count = CountGadgetItems(0) - 1
                For i = 0 To count
                  If GetGadgetItemState(0, i) & #PB_ListIcon_Selected
                    Debug "Selected Item " + i
                  EndIf
                Next
                
              Case 99
                PostEvent(#PB_Event_CloseWindow, 0, 0)
                
            EndSelect
            
          Case #PB_Event_Gadget
            Select EventGadget()
              Case 0
                Select EventType()
                  Case #PB_EventType_Change
                    item = GetGadgetState(0)
                    state = GetGadgetItemState(0, item)
                    Debug "PB Event Change Item " + item + " / State " + state
                  Case #PB_EventType_StatusChange
                    item = EventData()
                    state = GetGadgetItemState(0, item)
                    Debug "PB Event StatusChange Item " + item + " / CheckBox " + Bool(state & #PB_ListIcon_Checked)
                    
                  Case #PB_EventType_LeftDoubleClick
                    item = GetGadgetState(0)
                    state = GetGadgetItemState(0, item)
                    Debug "PB Event LeftDoubleClick Item " + item + " / CheckBox " + Bool(state & #PB_ListIcon_Checked)
                    
                  Case #PB_EventType_ColumnClick
                    time = ElapsedMilliseconds()
                    column = GetGadgetAttribute(0, #PB_ListIcon_ClickedColumn)
                    If column < 2
                      Debug "ColumnClick - " + GetGadgetItemText(0, -1, column) + " - Sort String"
                      temp = #CharSortAscend + "Column " + column
                      SortListIconGadgetEx(0, #PB_Sort_Ascending, column)
                      Debug "Sort Time " + Str(ElapsedMilliseconds() - time) + "ms"
                    ElseIf column <= 3
                      Debug "ColumnClick - " + GetGadgetItemText(0, -1, column) + " - Sort Integer"
                      temp = #CharSortAscend + "Column " + column
                      SortListIconGadgetEx(0, #PB_Sort_Ascending, column, #PB_Integer)
                      Debug "Sort Time " + Str(ElapsedMilliseconds() - time) + "ms"
                    ElseIf column <= 4
                      Debug "ColumnClick - " + GetGadgetItemText(0, -1, column) + " - Sort Integer Desc"
                      temp = #CharSortDescend + "Column " + column
                      SortListIconGadgetEx(0, #PB_Sort_Descending, column, #PB_Integer)
                      Debug "Sort Time " + Str(ElapsedMilliseconds() - time) + "ms"
                    ElseIf column <= 5
                      Debug "ColumnClick - " + GetGadgetItemText(0, -1, column) + " - Sort Double"
                      temp = #CharSortAscend + "Column " + column
                      SortListIconGadgetEx(0, #PB_Sort_Ascending, column, #PB_Double)
                      Debug "Sort Time " + Str(ElapsedMilliseconds() - time) + "ms"
                    Else
                      Debug "ColumnClick - " + GetGadgetItemText(0, -1, column)
                      temp = "Column " + column
                    EndIf
                EndSelect
                count = GetGadgetAttribute(0, #PB_ListIcon_ColumnCount) - 1
                For i = 0 To count
                  If i = column
                    SetGadgetItemText(0, -1, temp, i)
                  Else
                    SetGadgetItemText(0, -1, "Column " + i, i)
                  EndIf
                Next
                    
            EndSelect
            
        EndSelect
      ForEver
      
    EndIf
    
  EndProcedure : Main()
  
CompilerEndIf

Re: Window ListIconGadget With Owner Data (Very Fast)

Posted: Sun Dec 28, 2025 11:18 pm
by minimy
Thank you MK-Soft!
Tested in PB6.02 x64 and is like a bullet! :lol:
Wow! really fast!
Very, very nice code as allways.

Re: Window ListIconGadget With Owner Data (Very Fast)

Posted: Mon Dec 29, 2025 11:26 pm
by mk-soft
Update v1.02.1
- Added Compiler Option #USE_GLOBAL_LISTICON_OWNER_DATA
- Added FreeListIconGadget to release list icon data
- Optimize code

Update v1.02.2
- Bugfix Macro

;)

Re: Window ListIconGadget With Owner Data (Very Fast)

Posted: Wed Dec 31, 2025 12:10 pm
by HeX0R
I guess many of us had already an ownerdrawn ListIconGadget, due to the reason you already mentioned.
But since mine is very unstructured and "hacked together" I'll use yours in future, thanks for the code!

I never had to use checkboxes in my ownerdrawn lists before, but I think the behavior is odd:
When I tick a checkbox and then another one, both lines are being selected, is this the intended behavior?

Re: Window ListIconGadget With Owner Data (Very Fast)

Posted: Wed Dec 31, 2025 2:12 pm
by mk-soft
HeX0R wrote: Wed Dec 31, 2025 12:10 pm I guess many of us had already an ownerdrawn ListIconGadget, due to the reason you already mentioned.
But since mine is very unstructured and "hacked together" I'll use yours in future, thanks for the code!

I never had to use checkboxes in my ownerdrawn lists before, but I think the behavior is odd:
When I tick a checkbox and then another one, both lines are being selected, is this the intended behavior?
Thank you for your reply.

Multi-selection was active in the example. This is not intentional and needs to be worked on.
Example changed.. ;)

Re: Window ListIconGadget With Owner Data (Very Fast)

Posted: Thu Jan 01, 2026 1:57 pm
by mk-soft
Update v1.03.1
- Bugfix CheckBox and Multi Selection
- Added EventType #PB_EventType_StatusChange for CheckBox Changed
- Added Item Colors

Should be standard enough. ;)

With LVS OwnerData Notify CustomDraw, the dwDrawStage #CDDS_SUBITEMPREPAINT does not get through.
So first without Column Color. :(

Re: Window ListIconGadget With Owner Data (Very Fast)

Posted: Fri Jan 02, 2026 5:50 pm
by Kwai chang caine
Waouh !!! very fast, it's true :shock:
I love it 8)

Like Virtual ListIcon managing by an array 8)
Is it nearly or the same method ?

Do you plan to make the cells manually editable (With a double-click for example) ?

Thanks a lot for shared this nice code 8)

Re: Window ListIconGadget With Owner Data (Very Fast)

Posted: Fri Jan 02, 2026 7:37 pm
by mk-soft
Kwai chang caine wrote: Fri Jan 02, 2026 5:50 pm Like Virtual ListIcon managing by an array 8)
Is it nearly or the same method ?
Here, I use a linked list for the items. With an array, you would have to constantly use Redim, which can quickly become slow.

Re: Window ListIconGadget With Owner Data (Very Fast)

Posted: Sat Jan 03, 2026 8:32 am
by Kwai chang caine
Thanks for your answer 8)
This virtual listicon is very amazing
Since i have discover it, thanks to Rashad, i always use it
It's really the night and day with the native listicon :shock:
The most hard is to learn to deal with the array (A list for you) instead the real listicon :lol:

Re: Window ListIconGadget With Owner Data (Very Fast)

Posted: Sat Jan 03, 2026 12:10 pm
by mk-soft
I wrote so that you can use it as a normal ListIconGadget.
So you can deal with the internal linked list for the items and the internal array for the columns.
But you don't have to, and take the standard functions for the ListIconGadget.

The rest of how to do with LVS_OWNERDATA from the ListView can be read in the Win32 API from Microsoft.

Re: Window ListIconGadget With Owner Data (Very Fast)

Posted: Sat Jan 03, 2026 1:36 pm
by mk-soft
Update v1.04.2
- Added Column Colors
- Change WinCallback

Have now also managed to color the columns ;)

Re: Window ListIconGadget With Owner Data (Very Fast)

Posted: Sat Jan 03, 2026 3:03 pm
by mk-soft
Update v1.04.3
- Added Gadget Color

Now perfect colors ;)

Re: Window ListIconGadget With Owner Data (Very Fast)

Posted: Sat Jan 03, 2026 3:18 pm
by ZX80
Thanks mk-soft.

Small comment to improve.
I noticed some slowdown when clicking on the same element twice. These are single clicks, but with a small pause between them. Maybe you need to remember the last selected item ?

Re: Window ListIconGadget With Owner Data (Very Fast)

Posted: Sat Jan 03, 2026 5:36 pm
by mk-soft
ZX80 wrote: Sat Jan 03, 2026 3:18 pm Thanks mk-soft.

Small comment to improve.
I noticed some slowdown when clicking on the same element twice. These are single clicks, but with a small pause between them. Maybe you need to remember the last selected item ?
I compared it with the original ListIconGadget. Is the same behaviour.

P.S.
Presumably to distinguish between LeftClick and LeftDoubleClick this comes delayed.

Re: Window ListIconGadget With Owner Data (Very Fast)

Posted: Sat Jan 03, 2026 6:10 pm
by mk-soft
Update v1.04.4
- Bugfix: Add and RemoveGadgetColumns the column colors
- Optimize: Change from PB Array to Static Array for columns

Update v1.04.6
- Bugfix: Column text must not be a zero, otherwise no column color

You should always test everything ;)