Jump to content

Recommended Posts

Posted (edited)

Screenshot of statusbar:

image.thumb.png.610e192a9f3c17ee50f90b57a11e3167.png

I just finished some nice functional changes for the statusbar.

  • First part shows the typical total number of folders and files in the selected directory
  • Second part shows the file size of the currently selected file (currently limited to 1 file at the moment)
  • Third part shows the free space (and percentage) on the drive that the current directory resides on

I still have to nail down measurements a bit, but should be ready to release an updated script tomorrow.

Edited by WildByDesign
Posted (edited)

FilesAu3-2025-12-13:

  • Allow multiple file/folder selection in ListView
    • Works with mouse drag, Ctrl+left-click and Shift+left-click
    • Ctrl+A (select all) is not working yet (may need help with this)
  • Statusbar has 4 parts now
    • Part0 shows how many items (files & folders) in total in the current directory
    • Part1 shows how many items (files & folders) are currently selected
    • Part2 shows the total file size of the currently selected files
    • Part3 shows disk space free (and %) on drive that current folder resides on
  • Removed the .. (go back to previous dir) entry from the top of the ListView

I think that I covered all of the changes. :)

 

EDIT: It also contains the updated 2.10.3 version of the TreeListExplorer UDF.

EDIT2: By the way, I do have variables that store the total number of files selected and the number of folders selected. But when I tried to put that on the statusbar, it just visually appeared like too much information. So I ended up just putting them together as "items" like is done in File Explorer and Explorer++.

Edited by WildByDesign
Posted

I created a function that is working successfully to switch the Files Au3 GUI (and all controls) from dark mode to light mode and vice versa. The hard part for that is done. Now I just need to make a menu entry (likely check/uncheck) in the menubar to finish it. I'll have to do that later in the day and upload the changes here.

After that, I'm going to create a context menu attached to the ListView control to explore the possibilities there. :)

Posted

@WildByDesign though quickly tested, your last version of "FilesAu3-2025-12-13" seems to work fine. I got a couple of questions if you don't mind :

1) Why the need of this function...

$iFileSizes += _WinAPI_GetFileSize($sSelectedLV)

; From Trong
Func _WinAPI_GetFileSize($sFileNameOrHandle)
    ; 12 lines in the function
EndFunc   ;==>_WinAPI_GetFileSize

...can't we get exactly the same with a single line, that uses an AutoIt native function ?

$iFileSizes += FileGetSize($sSelectedLV)

2) In the main loop, we see :

Switch $iMsg
    Case $sBack ; 3 as $sBack is the 1st control created in the GUI
        __History_Undo($hFolderHistory)
    Case $sForward ; 4 as $sForward is the 2nd control created in the GUI
        __History_Redo($hFolderHistory)
    ...
    Case  -12, -6, 4 ;$GUI_EVENT_RESIZED, $GUI_EVENT_MAXIMIZE, Frames resizing
        ; resize header to match listview width
        _resizeHeader()
    ...
EndSwitch

So there are two Case 4 . How will the 2nd Case 4 ever be triggered ?

3) ListView sort works fine, by triggering $HDN_ITEMCLICK in the "detached" header control, then sorting the corresponding listview column. But it would be enjoyable to add the sort arrow (ascending or descending) to the clicked header item.

With a "normal" listview it would be done easily ("normal" means the listview got its own header) but with a detached header like found in this script, then you'll have to add code for getting this result. For what it's worth, I found a short way to display a sort arrow in your script. Just after this line...

_GUICtrlListView_RegisterSortCallBack($g_hListview, 2) ; 2 = natural sort

...add this line immediately after :

$__g_aListViewSortInfo[1][10] = $g_hHeader

This should be enough to add a sort arrow after you click on any header item.
Now what should happen concerning the sort, when you'll change path in the treeview, that's another question...

Good luck :)

 

"I think you are searching a bug where there is no bug... don't listen to bad advice."

Posted
2 hours ago, pixelsearch said:

...can't we get exactly the same with a single line, that uses an AutoIt native function ?

That's a very good question and I'm glad you asked. I did that with the assumption (foolishly) that the WinAPI function would have been more efficient and faster. In the past, I had scripts to test 15-20 different WinAPI functions in comparison to their related native functions to test performance. In almost all cases, the WinAPI functions were faster.

The problem? This time, I assumed without actually comparing performance. It seems that the native FileGetSize function is approx. 2x faster than the _WinAPI_GetFileSize function.

That is a lesson learned; no more assumptions. In that regard, I am happy that you asked. I will get rid of _WinAPI_GetFileSize and switch to FileGetSize. :)

2 hours ago, pixelsearch said:

So there are two Case 4 . How will the 2nd Case 4 ever be triggered ?

This is another great question and I'm glad that you asked as well. It was @ioa747 that had discovered that Msg 4 was being sent (from GUIFrame UDF) anytime the separator was moved. So the idea was to resize the header control whenever the separator was moved. This was necessary at some point in time.

However, I just tested now by removing that entry from the While loop and the header control still resizes beautifully. I would imagine that this has been the case since I added the GUICtrlSetResizing for each of the controls. I will update the script accordingly. :)

3 hours ago, pixelsearch said:

...add this line immediately after :

$__g_aListViewSortInfo[1][10] = $g_hHeader

This should be enough to add a sort arrow after you click on any header item.
Now what should happen concerning the sort, when you'll change path in the treeview, that's another question...

This is fantastic. You're right, it does work and quite well. Thank you.

I will come up with a way to allow the sort option to persist from directory to directory. This is exciting. And I agree with you, it is very nice to have the sort arrow.

I appreciate the testing, questions and recommendations that you shared. Great stuff. I will make these changes right away.

Posted

FilesAu3-2025-12-14:

  • All changes suggested by @pixelsearch
    • Still need to finish sorting persistence (TO DO)
  • Updated TreeListExplorer UDF to version 2.11.1
  • Using Date Modified now instead of Date Created
    • Likely add more columns later
  • Add theme switch to View menu
    • Lightning fast switching back and forth between dark mode and light mode

There's probably some more changes that I've forgotten to mention but I'm tired now.

Posted (edited)

For anyone that has been following, I need some opinions on something.

With the detached header and ListView, everything is looking great and working great. However, I'm trying to implement a column sort that can persist when you switch directories. I've realized that that is going to be more difficult than I had first thought.

New Concept:

What if we don't use a detached header and just use the ListView's own built-in header?

Well, we run into the WS_EX_COMPOSITED bug in that case and the entire ListView will flicker and use lots of CPU. So I figured out another (working) concept that allows us to use the built-in LV header. Whenever we move the separator (which resizes all frames), we can simply add (and remove after) the LVS_NOCOLUMNHEADER extended style.

Please keep in mind that the attached code example is in light mode since the LV header is not subclassed and that the only thing to really focus on in this example is the movement of the separator.

Pros and Cons:

Just like the detached header combined with the ListView, there are pros and cons. This will make all of the ListView functionality easier. This should also benefit with the column sorting although I haven't gotten that far yet.

The only con that I have noticed is that you visually see the LV header disappear when the separator is moving and reappear after. That is no surprise, of course. But without that, the entire ListView (not just the header) would disappear and flicker and use high CPU.

  • But the question is... is this minor visual thing worth it to have improved ListView functionality?
  • Or is the detached header and ListView visually a better experience?

Thanks :)

EDIT: We might be able to fix that visual thing by temporarily showing a copycat detached header and moving the ListView down by the height of a header (since it moves up temporarily to fill the gap left by hiding the LV header). Then reverse that. It's not like we can click on the header and use it while we are clicking/dragging the separator anyway.

 

EDIT 2: Failed experiment. Continuing with detached header with ListView.

 

 

Edited by WildByDesign
Posted (edited)

What are your thoughts about another callback function for my UDF?
Instead of sorting the ListView, sort the Items before adding them to the ListView.

Func _TreeListSort($hSystem, $hView, $sPath, ByRef $arFilenames, $bFolder)
    ; do your sorting of $arFilenames
EndFunc

This would be called for every list of drives, folders and files seperately.
The folders would always be on top, but the folders and files could be sorted.

On that note: would it be a good idea, to mix that up by combining all files and folders in one array for sorting?

Edited by Kanashius
Posted
21 minutes ago, Kanashius said:

What are your thoughts about another callback function for my UDF?

Yes, this would be a great idea for your UDF and very useful too.

22 minutes ago, Kanashius said:

The folders would always be on top, but the folders and files could be sorted.

This makes sense, always having folders at the top.

If this sorting function could be called at anytime, that would be great. For example, if a user clicks on one of the headers.

If possible, could you make it so that you can optionally specify the column number to sort?

24 minutes ago, Kanashius said:

On that note: would it be a good idea, to mix that up by combining all files and folders in one array for sorting?

This is up to you. Whatever you think makes the most sense. I don’t think that I would need to access the array directly. So I would suggest that you do what is best for you and what makes sense overall.

Posted

@pixelsearch I've been having an issue occasionally with the listview column resizing related to the following code section:

Case $HDN_TRACK, $HDN_TRACKW

It never happens in light mode; only in dark mode. So my feeling is that it is related to the dark mode header subclassing. I don't think that we can fix it with your functions because they are perfect in light mode. Likely a timing issue with the subclassing.

I do have something that fixes it and I would like your opinion on it, please. There are two different methods (similar) that fix it. So I would like your opinion on which is the better solution.

1. Adding $HDN_ENDTRACK, $HDN_ENDTRACKW onto the same Case

Case $HDN_TRACK, $HDN_TRACKW, $HDN_ENDTRACK, $HDN_ENDTRACKW

2. Adding a separate Case

Case $HDN_ENDTRACK, $HDN_ENDTRACKW
    Local $tNMHEADER = DllStructCreate($tagNMHEADER, $lParam)
    Local $iHeaderItem = $tNMHEADER.Item
    Local $tHDITEM = DllStructCreate($tagHDITEM, $tNMHEADER.pItem)
    Local $iHeaderItemWidth = $tHDITEM.XY
    _GUICtrlHeader_SetItemWidth($g_hHeader, $iHeaderItem, $iHeaderItemWidth)
    _GUICtrlListView_SetColumnWidth($g_hListview, $iHeaderItem, _GUICtrlHeader_GetItemWidth($g_hHeader, $iHeaderItem))

I've tried both and they both fix the problem. I just don't know which method is more proper. But also which is more efficient.

Posted
51 minutes ago, WildByDesign said:

I've been having an issue occasionally with the listview column resizing related to the following code section:

Case $HDN_TRACK, $HDN_TRACKW

A reason could be because you inadvertently changed the code I indicated in this post. Your actual code is...

Case $HDN_TRACK, $HDN_TRACKW
    ...
    _GUICtrlHeader_SetItemWidth($g_hHeader, $iHeaderItem, $iHeaderItemWidth)
    _GUICtrlListView_SetColumnWidth($g_hListview, $iHeaderItem, _GUICtrlHeader_GetItemWidth($g_hHeader, $iHeaderItem))
    Return False ; to continue tracking the divider

...while mine is :

Case $HDN_TRACK, $HDN_TRACKW
    ...
    _GUICtrlHeader_SetItemWidth($g_hHeader, $iHeaderItem, $iHeaderItemWidth)
    _resizeLVCols2()
    Return False ; to continue tracking the divider

The same kind of modification should be applied to the HDN_DIVIDERDBLCLICK part. Your actual code...

Case $HDN_DIVIDERDBLCLICK, $HDN_DIVIDERDBLCLICKW
    ...
    _GUICtrlListView_SetColumnWidth($g_hListview, $iHeaderDiv, $LVSCW_AUTOSIZE)
    _GUICtrlHeader_SetItemWidth($g_hHeader, $iHeaderDiv, _GUICtrlListView_GetColumnWidth($g_hListview, $iHeaderDiv))

...while mine is

Case $HDN_DIVIDERDBLCLICK, $HDN_DIVIDERDBLCLICKW
    ...
    _GUICtrlListView_SetColumnWidth($g_hListview, $iHeaderDiv, $LVSCW_AUTOSIZE)
    _GUICtrlHeader_SetItemWidth($g_hHeader, $iHeaderDiv, _GUICtrlListView_GetColumnWidth($g_hListview, $iHeaderDiv))
    _resizeLVCols2()

If these 2 modifications solve your issue, that's great. If not, we'll try to find why...

"I think you are searching a bug where there is no bug... don't listen to bad advice."

Posted
17 minutes ago, pixelsearch said:

A reason could be because you inadvertently changed the code I indicated in this post. Your actual code is...

The reason why I changed it was in my pursuit of trying to solve the bug. I switched it back to your code and it still ends up the same:

image.png.567cfef0f58a3568d3625bcbefcdb528.png

 

Sometimes it is off by just a bit and sometimes a lot. But I have only experienced it in dark mode.

This is why I figured that it must be a timing issue when combined with the subclassing of the header and why I thought of trying $HDN_ENDTRACK/$HDN_ENDTRACKW to follow up with one last shot at the column resizing at the end. Somehow it seems to resolve the problem 100% of the time.

Posted
2 hours ago, WildByDesign said:

1. Adding $HDN_ENDTRACK, $HDN_ENDTRACKW onto the same Case

Case $HDN_TRACK, $HDN_TRACKW, $HDN_ENDTRACK, $HDN_ENDTRACKW

If this Case solves your issue in dark mode 100% of the time, then I would go with it.

"I think you are searching a bug where there is no bug... don't listen to bad advice."

Posted
28 minutes ago, pixelsearch said:

If this Case solves your issue in dark mode 100% of the time, then I would go with it.

It looks like it still fails with the following:

_GUICtrlHeader_SetItemWidth($g_hHeader, $iHeaderItem, $iHeaderItemWidth)
    _resizeLVCols2()

But when I switch back to mine I haven't been able to get it to fail:

_GUICtrlHeader_SetItemWidth($g_hHeader, $iHeaderItem, $iHeaderItemWidth)
_GUICtrlListView_SetColumnWidth($g_hListview, $iHeaderItem, _GUICtrlHeader_GetItemWidth($g_hHeader, $iHeaderItem))

I don't quite understand why. But again, it seems to only happen in dark mode.

Posted

FilesAu3-2025-12-18:

  • Added Properties to File menu and to ListView context menu
    • Clicking Properties will show the Properties dialog for a single file/folder or multiple selection
  • Right-aligned the Size column
  • Fixed the docking/resizing of the buttons and input (path) box
  • Fixed a rare issue with the header resizing not matching the ListView column size

:)

Posted (edited)

@WildByDesign Hello :)

I'm not satisfied with my code, when a header divider is tracked, or the horizontal scrollbar is used etc... because it doesn't fully work in all cases (by my side)

So I decided to restart from scratch, first by trying to understand what happens exactly in a normal listview ("normal" = its header is attached) when it comes to scrolling, divider tracking etc... How do the header reacts to these events ?

I secretly hoped that understanding this would help me to mimic correctly the scrolling/tracking part in your script, which uses a detached header control ("detached" = the listview is created with the $LVS_NOCOLUMNHEADER style and a header control will be linked to the listview)

1) So let's start with this script concerning a normal listview :

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

Opt("MustDeclareVars", 1)

HotKeySet("{F9}", "_Infos2")

Global $g_hListview, $g_hHeader

Example()

;==============================================
Func Example()
    Local $sLVcoldef, $bIcons = True, $iColWidth = 140, $nRows = 20, $nCols = 4 ; <======= test different values here

    Local $hGUI = GUICreate("Native ListView", 400, 400, -1, -1, $WS_OVERLAPPEDWINDOW, $WS_EX_TOPMOST)

    For $i = 0 To $nCols - 1
        $sLVcoldef &= "Column" & $i & "|" ; no problem with last superfluous "|"
    Next

    Local $idListview = GUICtrlCreateListView($sLVcoldef, 40, 40, 320, 320, -1, $LVS_EX_HEADERDRAGDROP)
    $g_hListview = GUICtrlGetHandle($idListview)
    $g_hHeader = _GUICtrlListView_GetHeader($idListview)

    For $i = 0 To $nCols - 1
        _GUICtrlListView_SetColumnWidth($idListview, $i, $iColWidth)
    Next

    If $bIcons Then
        Local $hImageList = _GUIImageList_Create(20, 20, 5) ; 20,20 = width/height (pixels) of each image/icon . 3rd param. 5 for 32 bits.
        For $i = 0 To $nRows - 1
            _GUIImageList_AddIcon($hImageList, @SystemDir & "\shell32.dll", $i + 204)
        Next
        _GUICtrlListView_SetImageList($idListview, $hImageList, 1) ; 1 = image list with small icons
    EndIf

    _GUICtrlListView_BeginUpdate($idListview)
    For $i = 0 To $nRows - 1
        _GUICtrlListView_AddItem( $idListview, "item" & $i & "-000000000Z", ($bIcons ? $i : -1) )
        For $j = 1 To $nCols
            _GUICtrlListView_AddSubItem($idListview, $i, "row" & $i & "-" & "col" & $j, $j)
        Next
    Next
    _GUICtrlListView_EndUpdate($idListview)

    GUISetState(@SW_SHOW, $hGUI)
    _GUICtrlListView_RegisterSortCallBack($idListView, 2) ; 2 = natural sort

    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop
            Case $idListView
                _GUICtrlListView_SortItems($idListView, GUICtrlGetState($idListView))
        EndSwitch
    WEnd

    _GUICtrlListView_UnRegisterSortCallBack($idListView)
    GUIDelete($hGUI)
EndFunc   ;==>Example

;==============================================
Func _Infos2()
    ; this function requires 2 global variables defined before : $g_hListview and $g_hHeader
    Local Static $iTest, $iIconWidth
    Local $iCol, $iHDColWidth, $iLVColWidth, $aRectHD, $aRectLV
    Local $aOrder = _GUICtrlHeader_GetOrderArray($g_hHeader)

    Local $aHeaderPos = WinGetPos($g_hHeader)
    Local $aCoord[2] = [$aHeaderPos[0], $aHeaderPos[1]]
    _ScreenToClient($g_hListview , $aCoord) ; $aCoord passed ByRef
    ConsoleWrite("Header position (left / width) : " & $aCoord[0] & " / " & $aHeaderPos[2] & @crlf)

    $aRectLV = _GUICtrlListView_GetItemRect($g_hListView, 0, $LVIR_BOUNDS) ; bounding rectangle of the entire item, including the icon
    ConsoleWrite("Entire LV item0 (left / width) : " & $aRectLV[0] & " / " & ($aRectLV[2] - $aRectLV[0]) & @crlf)

    $iTest +=1
    ConsoleWrite("#" & $iTest & @TAB & "Column" & @TAB & "Left" & @TAB & "Right" & @TAB & "Width" & @crlf)

    If $iTest = 1 Then
        ; in case column 0 got an icon, retrieve the width of the icon
        $aRectLV = _GUICtrlListView_GetItemRect($g_hListView, 0, $LVIR_ICON) ; bounding rectangle of the icon (if any)
        $iIconWidth = $aRectLV[2] - $aRectLV[0] ; without icon : 4 - 4 => 0 (tested, the famous "4" !)
                                                ; with icon of 20 pixels : 24 - 4 = 20
    EndIf

    For $iIndex = 1 To $aOrder[0]
        $iCol = $aOrder[$iIndex]
        $aRectHD = _GUICtrlHeader_GetItemRect($g_hHeader, $iCol) ; remember $aRectHD[0] & $aRectHD[2] always >= 0 in a Header rectangle
        If $iCol > 0 Then ; LV subitem
            $aRectLV = _GUICtrlListView_GetSubItemRect($g_hListView, 0, $iCol) ; $aRectLV[0] & $aRectLV[2] may be < 0 in a ListView rectangle
        Else ; column 0 needs _GUICtrlListView_GetItemRect()
            $aRectLV = _GUICtrlListView_GetItemRect($g_hListView, 0, $LVIR_LABEL) ; bounding rectangle of the item text
            $aRectLV[0] -= (4 + $iIconWidth) ; adjust LV col 0 left coord (+++)
        EndIf
        $iHDColWidth = _GUICtrlHeader_GetItemWidth($g_hHeader, $iCol)
        $iLVColWidth = _GUICtrlListView_GetColumnWidth($g_hListView, $iCol)
        ConsoleWrite("+Header" & @TAB & $iCol & @TAB & $aRectHD[0] & @TAB & $aRectHD[2] & @TAB & $iHDColWidth & @crlf)
        ConsoleWrite("-ListV"  & @TAB & $iCol & @TAB & $aRectLV[0] & @TAB & $aRectLV[2] & @TAB & $iLVColWidth & @crlf)
    Next

    ConsoleWrite(@crlf)
EndFunc   ;==>Infos2

;============================================
Func _ScreenToClient($hWnd, ByRef $aCoord)
    Local $tPoint = DllStructCreate("int X;int Y")
    DllStructSetData($tPoint, "X", $aCoord[0])
    DllStructSetData($tPoint, "Y", $aCoord[1])
    DllCall("user32.dll", "bool", "ScreenToClient", "hwnd", $hWnd, "struct*", $tPoint)
    $aCoord[0] = $tPoint.X
    $aCoord[1] = $tPoint.Y
EndFunc   ;==>_ScreenToClient

The big surprise for me was to discover that the header control found in a normal listview doesn't have a fixed width. It's width changes constantly when you scroll horizontally, or track a divider etc...

Here is a pic showing the header control initial size (when the LV is freshly displayed and the horizontal scrollbar is at its very LEFT place :

NativeLV-initialheaderwidth.png.e215c17705f62ce972fa71f046da75c4.png

Now a pic showing how the header control width grows (and grows) when you move the scrollbar thumb to its very RIGHT position :

NativeLV-increasedheaderwidth.png.b6b806ed438dd6c78eefa6556e6ca318.png

The function Infos2() helped me a lot to discover what was happening, analyze the negative coords etc... Here is an example of what the function Infos2() could show in the console, when you press the hotkey F9 :
(#4 is just a counter, showing how many times you pressed F9)

Header position (left / width) : -217 / 537
Entire LV item0 (left / width) : -217 / 585
#4      Column  Left    Right   Width
+Header 1       0       140     140
-ListV  1       -217    -77     140
+Header 0       140     179     39
-ListV  0       -77     -38     39
+Header 2       179     445     266
-ListV  2       -38     228     266
+Header 3       445     585     140
-ListV  3       228     368     140

For the record, my experiments showed me that the header width changes in these 2 cases :

=> When the scrollbar thumb is not at its very left place, then the header window width increases (if you scroll to the right) and decreases (if you scroll to the left)

=> When the scrollbar thumb is at its very right position, then tracking any header divider to the left decreases the header window width

As you can see, both operations can modify the header window size : moving the LV horizontal scrollbar OR tracking a header divider to the left when the scrollbar thumb is at its very right place. Maybe there are other operations that modify the header window size but for now I discovered only these 2 ways.

2) Now let's try to mimic all this in your listview ($LVS_NOCOLUMNHEADER and detached header) with this script :

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

Opt("MustDeclareVars", 1)

HotKeySet("{F9}", "_Infos2")

Global $g_hChild, $g_hListview, $g_hHeader, $g_iIconWidth, $g_bHeaderEndDrag

Example()

;==============================================
Func Example()
    Local $sLVcoldef, $bIcons = True, $iColWidth = 140, $nRows = 20, $nCols = 4 ; <======= test different values here

    Local $hGUI = GUICreate("Detached header control", 400, 400, -1, -1, $WS_OVERLAPPEDWINDOW, $WS_EX_TOPMOST)
    GUISetBkColor(0x808080)

    $g_hChild = GUICreate("ChildWindow", 320, 320, 40, 40, $WS_CHILD, -1, $hGUI)
    GUISetBkColor(0x606060)

    $g_hHeader = _GUICtrlHeader_Create($g_hChild)
    Local $iHeaderHeight = _WinAPI_GetWindowHeight($g_hHeader)
    For $i = 0 To $nCols - 1
        _GUICtrlHeader_AddItem($g_hHeader, "Column" & $i, $iColWidth)
        $sLVcoldef &= $i & "|" ; no problem with last superfluous "|"
    Next

    Local $idListview = GUICtrlCreateListView($sLVcoldef, 0, $iHeaderHeight, 320, 320 - $iHeaderHeight, _
        BitOR($GUI_SS_DEFAULT_LISTVIEW, $LVS_NOCOLUMNHEADER), $LVS_EX_FULLROWSELECT)
    $g_hListview = GUICtrlGetHandle($idListview)

    If $bIcons Then
        Local $hImageList = _GUIImageList_Create(20, 20, 5) ; 20,20 = width/height (pixels) of each image/icon . 3rd param. 5 for 32 bits.
        For $i = 0 To $nRows - 1
            _GUIImageList_AddIcon($hImageList, @SystemDir & "\shell32.dll", $i + 204)
        Next
        _GUICtrlListView_SetImageList($idListview, $hImageList, 1) ; 1 = image list with small icons
    EndIf

    _GUICtrlListView_BeginUpdate($idListview)
    For $i = 0 To $nRows - 1
        _GUICtrlListView_AddItem( $idListview, "item" & $i & "-000000000Z", ($bIcons ? $i : -1) )
        For $j = 1 To $nCols
            _GUICtrlListView_AddSubItem($idListview, $i, "row" & $i & "-" & "col" & $j, $j)
        Next
    Next
    _GUICtrlListView_EndUpdate($idListview)

    ; resize listview columns to match header widths + update global variable $g_iIconWidth
    _resizeLVCols()

    GUISetState(@SW_SHOW, $hGUI)
    GUISetState(@SW_SHOW, $g_hChild)

    _GUICtrlListView_RegisterSortCallBack($idListView, 2) ; 2 = natural sort
    $__g_aListViewSortInfo[1][10] = $g_hHeader ; hack to add a sort arrow to the "detached" header

    GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")

    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop
        EndSwitch

        If $g_bHeaderEndDrag Then
            $g_bHeaderEndDrag = False
            _reorderLVCols()
        EndIf
    WEnd

    _GUICtrlListView_UnRegisterSortCallBack($idListView)
    _GUICtrlHeader_Destroy($g_hHeader)
    GUIDelete($g_hChild)
    GUIDelete($hGUI)
EndFunc   ;==>Example

;==============================================
Func _Infos2()
    ; this function requires 2 global variables defined before : $g_hListview and $g_hHeader
    Local Static $iTest, $iIconWidth
    Local $iCol, $iHDColWidth, $iLVColWidth, $aRectHD, $aRectLV
    Local $aOrder = _GUICtrlHeader_GetOrderArray($g_hHeader)

    Local $aHeaderPos = WinGetPos($g_hHeader)
    Local $aCoord[2] = [$aHeaderPos[0], $aHeaderPos[1]]
    _ScreenToClient($g_hListview , $aCoord) ; $aCoord passed ByRef
    ConsoleWrite("Header position (left / width) : " & $aCoord[0] & " / " & $aHeaderPos[2] & @crlf)

    $aRectLV = _GUICtrlListView_GetItemRect($g_hListView, 0, $LVIR_BOUNDS) ; bounding rectangle of the entire item, including the icon
    ConsoleWrite("Entire LV item0 (left / width) : " & $aRectLV[0] & " / " & ($aRectLV[2] - $aRectLV[0]) & @crlf)

    $iTest +=1
    ConsoleWrite("#" & $iTest & @TAB & "Column" & @TAB & "Left" & @TAB & "Right" & @TAB & "Width" & @crlf)

    If $iTest = 1 Then
        ; in case column 0 got an icon, retrieve the width of the icon
        $aRectLV = _GUICtrlListView_GetItemRect($g_hListView, 0, $LVIR_ICON) ; bounding rectangle of the icon (if any)
        $iIconWidth = $aRectLV[2] - $aRectLV[0] ; without icon : 4 - 4 => 0 (tested, the famous "4" !)
                                                ; with icon of 20 pixels : 24 - 4 = 20
    EndIf

    For $iIndex = 1 To $aOrder[0]
        $iCol = $aOrder[$iIndex]
        $aRectHD = _GUICtrlHeader_GetItemRect($g_hHeader, $iCol) ; remember $aRectHD[0] & $aRectHD[2] always >= 0 in a Header rectangle
        If $iCol > 0 Then ; LV subitem
            $aRectLV = _GUICtrlListView_GetSubItemRect($g_hListView, 0, $iCol) ; $aRectLV[0] & $aRectLV[2] may be < 0 in a ListView rectangle
        Else ; column 0 needs _GUICtrlListView_GetItemRect()
            $aRectLV = _GUICtrlListView_GetItemRect($g_hListView, 0, $LVIR_LABEL) ; bounding rectangle of the item text
            $aRectLV[0] -= (4 + $iIconWidth) ; adjust LV col 0 left coord (+++)
        EndIf
        $iHDColWidth = _GUICtrlHeader_GetItemWidth($g_hHeader, $iCol)
        $iLVColWidth = _GUICtrlListView_GetColumnWidth($g_hListView, $iCol)
        ConsoleWrite("+Header" & @TAB & $iCol & @TAB & $aRectHD[0] & @TAB & $aRectHD[2] & @TAB & $iHDColWidth & @crlf)
        ConsoleWrite("-ListV"  & @TAB & $iCol & @TAB & $aRectLV[0] & @TAB & $aRectLV[2] & @TAB & $iLVColWidth & @crlf)
    Next

    ConsoleWrite(@crlf)
EndFunc   ;==>Infos2

;============================================
Func _ScreenToClient($hWnd, ByRef $aCoord)
    Local $tPoint = DllStructCreate("int X;int Y")
    DllStructSetData($tPoint, "X", $aCoord[0])
    DllStructSetData($tPoint, "Y", $aCoord[1])
    DllCall("user32.dll", "bool", "ScreenToClient", "hwnd", $hWnd, "struct*", $tPoint)
    $aCoord[0] = $tPoint.X
    $aCoord[1] = $tPoint.Y
EndFunc   ;==>_ScreenToClient

;==============================================
Func _resizeLVCols() ; resize listview columns to match header widths (1st display only, before any horizontal scrolling)
    For $i = 0 To _GUICtrlHeader_GetItemCount($g_hHeader) - 1
        _GUICtrlListView_SetColumnWidth($g_hListview, $i, _GUICtrlHeader_GetItemWidth($g_hHeader, $i))
    Next

    ; In case column 0 got an icon, retrieve the width of the icon
    Local $aRectLV = _GUICtrlListView_GetItemRect($g_hListView, 0, $LVIR_ICON) ; bounding rectangle of the icon (if any)
    $g_iIconWidth = $aRectLV[2] - $aRectLV[0] ; without icon : 4 - 4 => 0 (tested, the famous "4" !)
                                              ; with icon of 20 pixels : 24 - 4 = 20
EndFunc   ;==>_resizeLVCols

;==============================================
Func _resizeLVCols2() ; called while a header item is tracked or a divider is double-clicked. Also called while the listview is scrolled horizontally.
    Local Static $iX_Old = 99999
    Local $iCol, $aRectLV
    Local $aOrder = _GUICtrlHeader_GetOrderArray($g_hHeader)
    $iCol = $aOrder[1] ; left column (may not be column 0, if column 0 was dragged/dropped elsewhere)
    If $iCol > 0 Then ; LV subitem
        $aRectLV = _GUICtrlListView_GetSubItemRect($g_hListView, 0, $iCol)
    Else ; column 0 needs _GUICtrlListView_GetItemRect()
        $aRectLV = _GUICtrlListView_GetItemRect($g_hListView, 0, $LVIR_LABEL) ; bounding rectangle of the item text
        $aRectLV[0] -= (4 + $g_iIconWidth) ; adjust LV col 0 left coord (+++)
    EndIf
    If $iX_Old = $aRectLV[0] Then Return ; no need to move/resize the detached header in this case (experimental, to be confirmed)
    If $aRectLV[0] < 0 Then ; horizontal scrollbar is NOT at left => move and resize the detached header (mimic a normal listview)
        WinMove($g_hHeader, "", $aRectLV[0], 0, WinGetClientSize($g_hChild)[0] - $aRectLV[0], Default)
    Else ; horizontal scrollbar is at left => move and resize the detached header to its initial coords & size
        WinMove($g_hHeader, "", 0, 0, WinGetClientSize($g_hChild)[0], Default)
    EndIf
    $iX_Old = $aRectLV[0]

EndFunc   ;==>_resizeLVCols2

;==============================================
Func _reorderLVCols()
    ; remove LVS_NOCOLUMNHEADER from listview
    Local $i_Style_Old = _WinAPI_GetWindowLong($g_hListView, $GWL_STYLE)
    _WinAPI_SetWindowLong($g_hListView, $GWL_STYLE, BitXOR($i_Style_Old, $LVS_NOCOLUMNHEADER))

    ; reorder listview columns order to match header items order
    Local $aOrder = _GUICtrlHeader_GetOrderArray($g_hHeader)
    _GUICtrlListView_SetColumnOrderArray($g_hListview, $aOrder)

    ; add LVS_NOCOLUMNHEADER back to listview
    _WinAPI_SetWindowLong($g_hListView, $GWL_STYLE, $i_Style_Old)

EndFunc   ;==>_reorderLVCols

;==============================================
Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam

    Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
    Local $hWndFrom = HWnd(DllStructGetData($tNMHDR, "hWndFrom"))
    Local $iCode = DllStructGetData($tNMHDR, "Code")
    Switch $hWndFrom
        Case $g_hHeader
            Switch $iCode
                Case $HDN_TRACK, $HDN_TRACKW
                    Local $tNMHEADER = DllStructCreate($tagNMHEADER, $lParam)
                    Local $iHeaderItem = $tNMHEADER.Item
                    Local $tHDITEM = DllStructCreate($tagHDITEM, $tNMHEADER.pItem)
                    Local $iHeaderItemWidth = $tHDITEM.XY
                    _GUICtrlHeader_SetItemWidth($g_hHeader, $iHeaderItem, $iHeaderItemWidth)
                    _GUICtrlListView_SetColumnWidth($g_hListview, $iHeaderItem, $iHeaderItemWidth)
                    _resizeLVCols2()
                    Return False ; to continue tracking the divider

                Case $HDN_ENDDRAG
                    $g_bHeaderEndDrag = True
                    Return False ; to allow the control to automatically place and reorder the item

                Case $HDN_DIVIDERDBLCLICK, $HDN_DIVIDERDBLCLICKW
                    ; "Notifies a header control's parent window that the user double-clicked the divider area of the control." (msdn)
                    ; Let's use it to auto-size the corresponding listview column.
                    Local $tNMHEADER = DllStructCreate($tagNMHEADER, $lParam)
                    Local $iHeaderItem = $tNMHEADER.Item
                    _GUICtrlListView_SetColumnWidth($g_hListview, $iHeaderItem, $LVSCW_AUTOSIZE)
                    _GUICtrlHeader_SetItemWidth($g_hHeader, $iHeaderItem, _GUICtrlListView_GetColumnWidth($g_hListview, $iHeaderItem))
                    _resizeLVCols2()

                Case $HDN_ITEMCLICK, $HDN_ITEMCLICKW
                    ; "Notifies a header control's parent window that the user clicked the control." (msdn)
                    ; Let's use it to sort the corresponding listview column.
                    Local $tNMHEADER = DllStructCreate($tagNMHEADER, $lParam)
                    Local $iHeaderItem = $tNMHEADER.Item
                    _GUICtrlListView_SortItems($g_hListview, $iHeaderItem)
            EndSwitch

        Case $g_hListView
            Switch $iCode
                Case $LVN_ENDSCROLL
                    Local Static $tagNMLVSCROLL = $tagNMHDR & ";int dx;int dy"
                    Local $tNMLVSCROLL = DllStructCreate($tagNMLVSCROLL, $lParam)
                    If $tNMLVSCROLL.dy = 0 Then ; ListView horizontal scrolling
                        _resizeLVCols2()
                    EndIf
            EndSwitch
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

As you can see in the function _resizeLVCols2() , there are 2 lines starting with :

WinMove($g_hHeader, ...)

That was the key to solve the horizontal scrolling / tracking issues when using a detached header control.
How could we ever scroll / track correctly without analyzing what happens in a normal listview ?

Please let me know if you find an issue with this last script.
Fingers crossed... that you won't :)

Edit 2025-12-21 => 2 changes in 2nd listing :
* Case $HDN_DIVIDERDBLCLICK, $HDN_DIVIDERDBLCLICKW
added one line _resizeLVCols2() at the end of its code.

* Func _resizeLVCols2() ; called while...
this comment becomes ; called while a header item is tracked or a divider is double-clicked. Also called while the listview is scrolled horizontally.

Edited by pixelsearch
see comment : Edit 2025-12-21

"I think you are searching a bug where there is no bug... don't listen to bad advice."

Posted

@pixelsearch Your deep dives into technical challenges is always inspiring and eye opening. Further, the way that you present your detective work, as I would call it, is always incredibly thorough yet very well organized in ways that is easy to take in that information. I always appreciate and look forward to your deep dives like this.

Just like that owner drawn status bar, you were never really fully satisfied until everything was perfect. At least that was my interpretation. And that is a great quality. A great way to think... the reality that anything and everything can always generally be improved in various ways.

23 minutes ago, pixelsearch said:

The function Infos2() helped me a lot to discover what was happening, analyze the negative coords etc... Here is an example of what the function Infos2() could show in the console, when you press the hotkey F9 :

This was brilliant. And it was a great idea to track exactly how a regular ListView header behaves to understand what is happening under the surface.

24 minutes ago, pixelsearch said:

How could we ever scroll / track correctly without analyzing what happens in a normal listview ?

This line a thinking and questioning has led to what appears to be perfection.

25 minutes ago, pixelsearch said:

Please let me know if you find an issue with this last script.

I've tested the example quite thoroughly and the behaviour is truly beautiful. You really can't tell that it's a detached header combined with the ListView. It's smooth as well.

I'm actually really quite excited about integrating these changes into Files Au3. It also motivates me to keep on pushing with more improvements.

Thank you again for the deep dive and the updated code. I always learn a lot from your deep dives. Not even just the coding, but also the way of thinking.

Posted (edited)

@WildByDesign thanks for the very kind words !

On 12/20/2025 at 1:02 AM, pixelsearch said:

Maybe there are other operations that modify the header window size but for now I discovered only these 2 ways.

And now they were 3...
The third way is to scroll the listview horizontally to the very right, then double-click any visible header divider to autosize its corresponding listview column . Then the headers won't align correctly with the listview columns (the header window width has decreased)

The fix is easy in your script, as we need to WinMove the header in this 3rd case too :

1) Let's add an additional line at the end of this part of code :

Case $HDN_DIVIDERDBLCLICK, $HDN_DIVIDERDBLCLICKW
    ...
    _resizeLVCols2()

2) The comment changes in the function, new comment being :

Func _resizeLVCols2() ; called while a header item is tracked or a divider is double-clicked. Also called while the listview is scrolled horizontally.

This should do it... until we find a 4th way :D

For the record, in both lines WinMove($g_hHeader, ...) maybe you noticed the use of WinGetClientSize($g_hChild)[0] instead of using a numeric variable which already contained its value and was calculated only once.

I preferred to script it this way, having in mind that your child widow won't keep the same width in your script, as you're enlarging it and change its size constantly while tracking the frames separators or resizing the GUI. In this case, using a fixed non-recalculated value shouldn't work, we'll see in time...

Have a great day :)

Edited by pixelsearch

"I think you are searching a bug where there is no bug... don't listen to bad advice."

Posted
6 hours ago, pixelsearch said:

The third way is to scroll the listview horizontally to the very right, then double-click any visible header divider to autosize its corresponding listview column . Then the headers won't align correctly with the listview columns (the header window width has decreased)

Thanks for finding this. I just confirmed the issue and the fix as well and you are spot on. :)

I've updated it with the fix and updated the comment as well since this stuff is quite intricate and your code comments are very good.

It's quite amazing how cohesive the behavior is between the detached header and ListView now. It's really quite incredible. All of that is thanks to you.

Now, I still have some occasional issues with the alignment of dark mode header. I thought that it was 100% fixed, but it seems to happen maybe 1 out of 10 times (approx.) and is difficult to reproduce. I cleaned up the WM_NOTIFY function to make it slightly more efficient. But I assume that it has something to do with the timing of the dark mode header subclassing clashing with the timing of the column resizing in Case $HDN_TRACK, $HDN_TRACKW.

Basically what happens is when it's tracking the divider movement and you let go of the mouse, the header column size will zip back as if it lost track of the current position where the mouse left it. So this would be related to $HDN_ENDTRACK, $HDN_ENDTRACKW. It makes me wonder if the dark mode header subclass is doing a Return that gets in the way of the $HDN_ENDTRACK, $HDN_ENDTRACKW or something. I can't quite figure it out.

I'll have to share an updated script soon (today) to see if anyone else can also reproduce it.

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
×
×
  • Create New...