Jump to content

Incremental search in owner and custom drawn ListViews


LarsJ
 Share

Recommended Posts

34 minutes ago, Norm73 said:

I couldn't change the column size of the virtual ListView.

This specifically prohibits the resizing with the hands.

There was a bug with a second column update. See earlier in this thread.

 

@pixelsearch.

Thank you for your excellent example.
But the following happens:
Version 2K and an example of align columns from Larsj are made without using WM_DRAWITEM.
If leave a column selection through a combobox and remove options the  right mouse click, can even use the standard context menu.
But in your last versions 2m and 2p, WM_DRAWITEM returned again and it does (for us nonprofessional) join together all functionality. 🙄
Do you have the opportunity to combine these functions, and preferably based on the script without WM_DRAWITEM.
Many thanks.

Link to comment
Share on other sites

Hi, version "2q" below includes LarsJ column align. It is based on WM_DRAWITEM and it will be my last update for the moment.

align.png.8210de59ff2297431c2f302d63baaf94.png

#include <ComboConstants.au3>
#include <EditConstants.au3>
#include <GuiListView.au3>
#include <GUIConstantsEx.au3>
#include <StaticConstants.au3>
#include <WindowsConstants.au3>
#include "RandomArray.au3" ; LarsJ
#include "DrawItem.au3"    ;  "

Opt("MustDeclareVars", 1)

Global $g_iRows = 1000, $g_iCols = 6, $g_hGui, $g_hListView, $g_hHeader, $g_hEdit
Global $g_aCols = ["Strings", "Integers", "Floats", "Dates", "Times", "R/C"], $g_aWidths = [230, 61, 124, 70, 60, 60]
Global $g_aColAligns[$g_iCols] = [$HDF_CENTER, $HDF_RIGHT, $HDF_RIGHT, $HDF_RIGHT, $HDF_RIGHT, $HDF_RIGHT] ; $HDF_LEFT, $HDF_RIGHT, $HDF_CENTER
Global $g_idListView, $g_idMarker, $g_idComboCol, $g_idComboColDummy, $g_idEditDummy
Global $g_sSearch, $g_iSearchCol, $g_iSortDir, $g_iSearch = $g_iRows
Global $g_aArray, $g_aSubArray, $g_tDefaultIndex, $g_tIndex = DllStructCreate("uint arr[" & $g_iRows & "]")
Global $g_aIndex[$g_iCols], $g_aIndexTemp[$g_iCols] ; VarGetType's : $g_aIndex => "Array", $g_aIndex[0] => "String"
Global $fListViewHasFocus = 1 ; trying this for now, we'll see...

Example()

Func Example()
  ; Generate array & one index
  _Generate_All($g_aArray)
  $g_aSubArray = $g_aArray
  $g_tDefaultIndex = $g_tIndex

  ; Create GUI
  $g_hGui = GUICreate("Virtual ListView + match 1 or All columns + Align (2q)", 630 + 20, 788 + 30 + 20)

  ; Create Edit control (search) + dummy control
  Local $idEdit = GUICtrlCreateEdit("", 120, 10, 305, 20, BitXOR($GUI_SS_DEFAULT_EDIT, $WS_HSCROLL, $WS_VSCROLL))
  $g_hEdit = GUICtrlGetHandle($idEdit)
  $g_idEditDummy = GUICtrlCreateDummy()

  ; Create ComboBox control (how to search : RegEx or Normal ?)
  Local $idSearchHow = GUICtrlCreateCombo("RegEx search", 11, 9, 100, 20, BitOR($GUI_SS_DEFAULT_COMBO, $CBS_DROPDOWNLIST))
  Local $sSearchHow = "RegEx search", $sSearchHowPrev = $sSearchHow ; default way of searching (changeable)
  GUICtrlSetData($idSearchHow, "Normal search", $sSearchHow)

  ; Create ComboBox control (column where to search, possible All) + dummy control
  GUICtrlCreateLabel("Col", 429, 10, 20, 20, BitOR($SS_CENTERIMAGE, $SS_CENTER))
  $g_idComboCol = GUICtrlCreateCombo("All", 452, 9, 41, 20, BitOR($GUI_SS_DEFAULT_COMBO, $CBS_DROPDOWNLIST))
  Local $sSearchCol = "All" ; default column where to search (changeable) => becomes $g_iSearchCol
  $g_iSearchCol = ($sSearchCol == "All" ? - 1 : Number($sSearchCol)) ; - 1 means search All columns
  Local $iSearchColPrev = $g_iSearchCol
  For $i = 0 To $g_iCols - 1
    GUICtrlSetData($g_idComboCol, $i & "|", $g_iSearchCol)
  Next
  $g_idComboColDummy = GUICtrlCreateDummy()

  ; Create Label control (number of matching results)
  Local $idResult = GUICtrlCreateLabel(" " & $g_iRows & " rows (no pattern)", 501, 10, 135, 20, BitOR($SS_CENTERIMAGE, $SS_SUNKEN))

  ; Create ListView
  $g_idListView = GUICtrlCreateListView("", 10, 40, 630, 788, BitOr($LVS_OWNERDATA, $LVS_OWNERDRAWFIXED), $WS_EX_CLIENTEDGE)
  _GUICtrlListView_SetExtendedListViewStyle($g_idListView, BitOr($LVS_EX_DOUBLEBUFFER, $LVS_EX_FULLROWSELECT))
  $g_hListView = GUICtrlGetHandle($g_idListView)
  $g_hHeader = _GUICtrlListView_GetHeader($g_idListView)
  For $i = 0 To $g_iCols - 1
    _GUICtrlListView_AddColumn($g_idListView, $g_aCols[$i], $g_aWidths[$i])
    _GUICtrlHeader_SetItemFormat($g_hHeader, $i, $HDF_STRING + $g_aColAligns[$i])
  Next

  ; No ListView column resizing by dragging header dividers (LarsJ)
  ;_WinAPI_SetWindowLong( $hHeader, $GWL_STYLE, _WinAPI_GetWindowLong( $hHeader, $GWL_STYLE ) + $HDS_NOSIZING ) ; AutoIt 3.3.14.5 issue
  DllCall( "user32.dll", "long_ptr", @AutoItX64 ? "SetWindowLongPtrW" : "SetWindowLongW", "hwnd", $g_hHeader, "int", $GWL_STYLE, "long_ptr", _
  DllCall( "user32.dll", "long_ptr", @AutoItX64 ? "GetWindowLongPtrW" : "GetWindowLongW", "hwnd", $g_hHeader, "int", $GWL_STYLE )[0] + $HDS_NOSIZING )

  ; Create Marker (an orange line placed above the header of the column being searched)
  $g_idMarker = GUICtrlCreateLabel("", 0, 0)
  GUICtrlSetBkColor(-1, 0xFFA060) ; orange
  _MoveMarker($g_iSearchCol)
  _GUICtrlListView_SetSelectedColumn($g_idListView, $g_iSearchCol)

  ; Sorting information
  $g_iSortDir = $HDF_SORTUP
  Local $iSortCol = -1, $iSortColPrev = -1

  ; Register message handlers
  GUIRegisterMsg($WM_DRAWITEM, "WM_DRAWITEM")
  GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY") ; for LV header only
  GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")

  ; Sets the virtual number of items in a virtual list-view control
  GUICtrlSendMsg($g_idListView, $LVM_SETITEMCOUNT, $g_iRows, 0)

  GUISetState(@SW_SHOW)

  While 1
    Switch GUIGetMsg()
      Case $GUI_EVENT_CLOSE
        ExitLoop

      Case $g_idComboCol, $g_idComboColDummy, $idSearchHow
        $sSearchCol = GUICtrlRead($g_idComboCol)
        $g_iSearchCol = ($sSearchCol == "All" ? - 1 : Number($sSearchCol)) ; - 1 means search All columns
        $sSearchHow = GUICtrlRead($idSearchHow)
        Select
          Case $g_iSearchCol <> $iSearchColPrev
            _MoveMarker($g_iSearchCol) ; Search column will be selected below, after ContinueCase
            $iSearchColPrev = $g_iSearchCol
          Case $sSearchHow <> $sSearchHowPrev
            $sSearchHowPrev = $sSearchHow
          Case Else ; no change in both Combo controls (same search column, same search way)
            ContinueLoop
        EndSelect
        ContinueCase

      Case $g_idEditDummy
        _GUICtrlHeader_SetItemFormat($g_hHeader, $iSortCol, $HDF_STRING + ($iSortCol > -1 ? $g_aColAligns[$iSortCol] : 0))
        $g_sSearch = GUICtrlRead($idEdit)
        $g_tIndex = $g_tDefaultIndex
        If $g_sSearch = "" Then
          ; Empty search string, display all rows
          $g_aSubArray = $g_aArray
          $g_iSearch = $g_iRows
        Else
          ; Find rows matching the search string
          $g_iSearch = 0
          If $sSearchHow = "Normal search" Then ; all searches use RegEx => escape 12 + 1 metacharacters
            $g_sSearch = StringRegExpReplace($g_sSearch, "(\\|\.|\^|\$|\||\[|\(|\{|\*|\+|\?|\#|\))" , "\\$1")
          EndIf
          ; ConsoleWrite("$sSearchHow = " & $sSearchHow & @TAB & "$g_sSearch = " & $g_sSearch & @lf)
          If $g_iSearchCol = - 1 Then ; - 1 means search All columns
            For $i = 0 To $g_iRows - 1
              For $j = 0 To $g_iCols - 1
                If StringRegExp($g_aArray[$i][$j], "(?i)" & $g_sSearch) Then
                  For $k = 0 To $g_iCols - 1
                    $g_aSubArray[$g_iSearch][$k] = $g_aArray[$i][$k]
                  Next
                  $g_iSearch += 1
                  ContinueLoop 2
                EndIf
              Next
            Next
          Else ; Search only in 1 column
            For $i = 0 To $g_iRows - 1
              If StringRegExp($g_aArray[$i][$g_iSearchCol], "(?i)" & $g_sSearch) Then
                For $j = 0 To $g_iCols - 1
                  $g_aSubArray[$g_iSearch][$j] = $g_aArray[$i][$j]
                Next
                $g_iSearch += 1
              EndIf
            Next
          EndIf
          ; Delete eventual temporary subindexes
          For $i = 0 To $g_iCols - 1
            If VarGetType($g_aIndexTemp[$i]) = "DLLStruct" Then $g_aIndexTemp[$i] = "" ; "String"
          Next
        EndIf
        GUICtrlSetData($idResult, " " & $g_iSearch & ($g_sSearch = "" ? " rows (no pattern)" : " matching rows"))
        GUICtrlSendMsg($g_idListView, $LVM_SETITEMCOUNT, $g_iSearch, 0)
        _GUICtrlListView_SetSelectedColumn($g_hListView, $g_iSearchCol) ; seems ok here (after $LVM_SETITEMCOUNT)

      Case $g_idListView ; Sort
        $iSortCol = GUICtrlGetState($g_idListView)
        If $iSortCol <> $iSortColPrev Then
          _GUICtrlHeader_SetItemFormat($g_hHeader, $iSortColPrev, $HDF_STRING + ($iSortColPrev > -1 ? $g_aColAligns[$iSortColPrev] : 0))
        EndIf
        ; Set $g_tIndex + eventual update of $g_aIndexTemp[$iSortCol] OR $g_aIndex[$iSortCol]
        If GUICtrlRead($idEdit) Then
          _UpdateIndex($g_aIndexTemp, $iSortCol)
        Else
          _UpdateIndex($g_aIndex, $iSortCol)
        EndIf
        $g_iSortDir = (($iSortCol = $iSortColPrev) ? ($g_iSortDir = $HDF_SORTUP ? $HDF_SORTDOWN : $HDF_SORTUP) : ($HDF_SORTUP))
        _GUICtrlHeader_SetItemFormat($g_hHeader, $iSortCol, $HDF_STRING + $g_aColAligns[$iSortCol] + $g_iSortDir)
        GUICtrlSendMsg($g_idListView, $LVM_SETSELECTEDCOLUMN, $iSortCol, 0)
        GUICtrlSendMsg($g_idListView, $LVM_SETITEMCOUNT, $g_iSearch, 0)
        $iSortColPrev = $iSortCol

      Case $GUI_EVENT_RESTORE ; needed, or Marker goes back in 0, 0 after Restore (why ?)
        _MoveMarker($g_iSearchCol)
    EndSwitch
  WEnd

  ; Cleanup
  GUIDelete($g_hGui)
EndFunc   ;==>Example

;========================================================================
Func WM_DRAWITEM( $hWnd, $iMsg, $wParam, $lParam )
  ; Display items in an owner drawn ListView

  Local Static $tRect = DllStructCreate( $tagRECT ), $pRect = DllStructGetPtr( $tRect ), $tSize = DllStructCreate( $tagSIZE )
  Local Static $hBrushYellow = _WinAPI_CreateSolidBrush( 0xFFFF00 ), $hBrushCyan = _WinAPI_CreateSolidBrush( 0x00FFFF ) ; Yellow and cyan, BGR
  Local Static $hBrushHighLight = _WinAPI_GetSysColorBrush( $COLOR_HIGHLIGHT ), $hBrushButtonFace = _WinAPI_GetSysColorBrush( $COLOR_BTNFACE )

  ; We can optimize code by removing Switch statements because the ListView is the only ownerdrawn control and only $ODA_DRAWENTIRE actions are present

  Local $tDrawItem = DllStructCreate( $tagDRAWITEM, $lParam ), $itemID = DllStructGetData( $tDrawItem, "itemID" ), $iState = DllStructGetData( $tDrawItem, "itemState" ), $hDC = DllStructGetData( $tDrawItem, "hDC" ), $sItemText

  ; Loop through columns ($i is the column index)
  For $i = 0 To $g_iCols - 1

    ; Subitem rectangle
    DllStructSetData( $tRect, 2, $i ) ; Top
    DllStructSetData( $tRect, 1, $LVIR_BOUNDS ) ; Left
    GUICtrlSendMsg( $g_idListView, $LVM_GETSUBITEMRECT, $itemID, $pRect )
    DllStructSetData( $tRect, 1, DllStructGetData( $tRect, 1 ) + 3 ) ; Left margin

    ; If $i = 0 (first column), the rectangle is calculated for the entire listview item.
    ; Compensate for this by setting the width of the rectangle to the width of the first column.
    ; Before that, if item is selected, fill the entire listview item with the highlight background color.
    If $i = 0 Then
      If BitAND( $iState, $ODS_SELECTED ) Then DllCall( "user32.dll", "int", "FillRect", "handle", $hDC, "struct*", $tRect, "handle", $fListViewHasFocus = 1 ? $hBrushHighLight : $hBrushButtonFace ) ; _WinAPI_FillRect
      DllStructSetData( $tRect, 3, DllStructGetData( $tRect, 1 ) + GUICtrlSendMsg( $g_idListView, $LVM_GETCOLUMNWIDTH, 0, 0 ) )
    EndIf

    ; Retrieve subitem text
    If $g_iSortDir = 0x0400 Then ; 0x0400 = $HDF_SORTUP
      $sItemText = $g_aSubArray[$g_tIndex.arr($itemID + 1)][$i]
    Else
      $sItemText = $g_aSubArray[$g_tIndex.arr($g_iSearch - $itemID)][$i]
    EndIf

    ; Subitem rectangle for right and center aligned columns
    If $g_aColAligns[$i] = $HDF_RIGHT Or $g_aColAligns[$i] = $HDF_CENTER Then
      DllCall( "gdi32.dll", "bool", "GetTextExtentPoint32W", "handle", $hDC, "wstr", $sItemText, "int", StringLen( $sItemText ), "struct*", $tSize ) ; _WinAPI_GetTextExtentPoint32
      Switch $g_aColAligns[$i]
        Case $HDF_RIGHT
          DllStructSetData( $tRect, "Left", DllStructGetData( $tRect, "Right" ) - DllStructGetData( $tSize, "X" ) - 6 )
        Case $HDF_CENTER
          DllStructSetData( $tRect, "Left", DllStructGetData( $tRect, "Left" ) + ( DllStructGetData( $tRect, "Right" ) - DllStructGetData( $tRect, "Left" ) - DllStructGetData( $tSize, "X" ) ) / 2 - 3 )
      EndSwitch
    EndIf

    ; Subitem text color
    DllCall( "gdi32.dll", "int", "SetTextColor", "handle", $hDC, "int", BitAND( $iState, $ODS_SELECTED ) ? $fListViewHasFocus = 1 ? 0xFFFFFF : 0x000000 : 0x000000 ) ; _WinAPI_SetTextColor

    ; Draw subitem text
    DllCall( "user32.dll", "int", "DrawTextW", "handle", $hDC, "wstr", $sItemText, "int", StringLen( $sItemText ), "struct*", $tRect, "uint", 0 ) ; _WinAPI_DrawText

    ; Skip column if search for 1 column only and $i is not the searched column
    If $g_iSearchCol > - 1 And $i <> $g_iSearchCol Then ContinueLoop ; > - 1 means search for 1 column (0, 1, 2...)

    ; Mark matching substring if found in this column
    If $g_sSearch Then
      Local $sMatch = StringRegExp( $sItemText, "(?i)" & $g_sSearch, 1 )
      If Not @error Then ; match found
        Local $extended = @extended, $iLen = StringLen( $sMatch[0] )

        ; Rectangle for matching substring
        DllCall( "gdi32.dll", "bool", "GetTextExtentPoint32W", "handle", $hDC, "wstr", $sItemText, "int", $extended - $iLen - 1, "struct*", $tSize ) ; _WinAPI_GetTextExtentPoint32
        DllStructSetData( $tRect, "Left", DllStructGetData( $tRect, "Left" ) + DllStructGetData( $tSize, "X" ) )
        DllCall( "gdi32.dll", "bool", "GetTextExtentPoint32W", "handle", $hDC, "wstr", $sMatch[0], "int", $iLen, "struct*", $tSize ) ; _WinAPI_GetTextExtentPoint32
        DllStructSetData( $tRect, "Right", DllStructGetData( $tRect, "Left" ) + DllStructGetData( $tSize, "X" ) )

        ; Fill rectangle with yellow or cyan (selected) background color
        DllStructSetData( $tRect, 2, DllStructGetData( $tRect, 2 ) + 1 ) ; Top  margin
        DllCall( "user32.dll", "int", "FillRect", "handle", $hDC, "struct*", $tRect, "handle", BitAND( $iState, $ODS_SELECTED ) ? $hBrushCyan : $hBrushYellow ) ; _WinAPI_FillRect

        ; Draw matching substring in rectangle
        DllStructSetData( $tRect, 2, DllStructGetData( $tRect, 2 ) - 1 ) ; Top  margin
        DllCall( "gdi32.dll", "int", "SetTextColor", "handle", $hDC, "int", 0x000000 ) ; _WinAPI_SetTextColor
        DllCall( "user32.dll", "int", "DrawTextW", "handle", $hDC, "wstr", $sMatch[0], "int", $iLen, "struct*", $tRect, "uint", 0 ) ; _WinAPI_DrawText
      EndIf
    EndIf
  Next

  Return $GUI_RUNDEFMSG
  #forceref $hWnd, $iMsg, $wParam
EndFunc

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

  Local $tNMHEADER = DllStructCreate($tagNMHEADER, $lParam)
  Switch HWnd(DllStructGetData($tNMHEADER, "hWndFrom"))
    Case $g_hHeader
      Local $iCode = DllStructGetData($tNMHEADER, "Code")
      Switch $iCode
        Case $NM_RCLICK
          Local $aHit = _GUICtrlListView_SubItemHitTest($g_hListView)
          ; $aHit[1] : 0-based index of the LV subitem... i.e. the column in our case (may be -1 if right click on empty part of header)
          If $aHit[1] > - 1 Then ; valid column
            GUICtrlSetData($g_idComboCol, $aHit[1])
            GUICtrlSendToDummy($g_idComboColDummy)
          EndIf
      EndSwitch
  EndSwitch
  Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

;========================================================================
Func WM_COMMAND($hWnd, $iMsg, $wParam, $lParam)

  Local $hWndFrom = $lParam
  Local $iCode = BitShift($wParam, 16) ; High word
  Switch $hWndFrom
    Case $g_hEdit
      Switch $iCode
        Case $EN_CHANGE
          GUICtrlSendToDummy($g_idEditDummy)
      EndSwitch
  EndSwitch
  Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_COMMAND

;========================================================================
Func _Generate_All(ByRef $g_aArray)

  ConsoleWrite("$g_iRows = " & $g_iRows & "    $g_iCols = " & $g_iCols & @CRLF)
  Local $hTimer = TimerInit()
  $g_aArray = FAS_Random2DArrayAu3($g_iRows, "sifdtr", "abcdefghijklmnopqrstuvwxyz")
  ; $g_aArray = FAS_Random2DArrayAu3($g_iRows, "sifdtr", "abcdefghijklmnopqrstuvwxyz" & "\.^$|[({*+?#)") ; 12 + 1 RegEx metacharacters
  For $i = 0 To $g_iRows - 1
    $g_tIndex.arr($i + 1) = $i
  Next
  ConsoleWrite("Generating array & one index = " & TimerDiff($hTimer) & @CRLF & @CRLF)
EndFunc   ;==>_Generate_All

;========================================================================
Func _SortArrayStruct(Const ByRef $aArray, $iCol, $iRows)
  Local $tIndex = DllStructCreate("uint arr[" & $iRows & "]")
  Local $pIndex = DllStructGetPtr($tIndex)
  Local Static $hDll = DllOpen("kernel32.dll")
  Local Static $hDllComp = DllOpen("shlwapi.dll")

  Local $lo, $hi, $mi, $r

  ; Sorting by one column
  For $i = 1 To $iRows - 1
    $lo = 0
    $hi = $i - 1
    Do
      $mi = Int(($lo + $hi) / 2)
      $r = DllCall($hDllComp, 'int', 'StrCmpLogicalW', 'wstr', $aArray[$i][$iCol], 'wstr', $aArray[DllStructGetData($tIndex, 1, $mi + 1)][$iCol])[0]
      Switch $r
        Case -1
          $hi = $mi - 1
        Case 1
          $lo = $mi + 1
        Case 0
          ExitLoop
      EndSwitch
    Until $lo > $hi
    DllCall($hDll, "none", "RtlMoveMemory", "struct*", $pIndex + ($mi + 1) * 4, "struct*", $pIndex + $mi * 4, "ulong_ptr", ($i - $mi) * 4)
    DllStructSetData($tIndex, 1, $i, $mi + 1 + ($lo = $mi + 1))
  Next

  Return $tIndex
EndFunc   ;==>_SortArrayStruct

;========================================================================
Func _UpdateIndex(ByRef $aIndex, $iCol)

  If VarGetType($aIndex[$iCol]) = "DLLStruct" Then
    $g_tIndex = $aIndex[$iCol]
  Else
    $g_tIndex = _SortArrayStruct($g_aSubArray, $iCol, $g_iSearch)
    $aIndex[$iCol] = $g_tIndex ; "DLLStruct" (or "Int32" when no match found +++)
  EndIf
EndFunc   ;==>_UpdateIndex

;========================================================================
Func _MoveMarker($iCol)

  If $iCol = - 1 Then ; All columns
    ControlMove($g_hGui, "", $g_idMarker, 10, 40 - 3, 630, 3) ; 10 / 40 / 630 are LV coords
  Else ; 1 column (0, 1, 2...)
    Local $aRect = _GUICtrlHeader_GetItemRect($g_hHeader, $iCol)
    ControlMove($g_hGui, "", $g_idMarker, 10 + $aRect[0], 40 - 3, $aRect[2] - $aRect[0] + 1, 3)
  EndIf
EndFunc   ;==>_MoveMarker


You should be able to combine the present functionalities with $NM_CUSTOMDRAW instead of $WM_DRAWITEM, not forgetting the special context menu found in this thread, and not forgetting Norm73's wish to immediately scroll on 1st match, for example like in the pic below (2 first non-matching lines left visible on purpose) . The pic below is not a script, it's just the way it could appear :

scroll.png.407d2294178ca6099fad10cc85d235e9.png

@Norm73 unfortunately _GUICtrlListView_EnsureVisible (as indicated in one of your last post) isn't enough to do what you want. It sometimes display the requested item at the bottom of the screen (if you were selecting an item placed below it)
LVM_GETTOPINDEX is useful to retrieve the index of the topmost visible item, but unfortunately there is no "LVM_SETTOPINDEX" to force an item to be placed at topmost position.

After some searches here & there, I tested a way that can do it and it seems to work fine. Here is the code in case anyone is interested :

;~      ; It works !!!
;~      Local $iRow1stMatch = 200
;~      Local $iGetTopIndex = _GUICtrlListView_GetTopIndex($g_idListView)
;~      Local $iGetItemSpacingY = _GUICtrlListView_GetItemSpacingY($g_idListView, True) ; only once
;~      _GUICtrlListView_Scroll($g_idListView, 0, ($iRow1stMatch - $iGetTopIndex) * $iGetItemSpacingY)

Now it's time to rest, no more AutoIt during several days. Good luck guys and see you one of these days :bye:

Link to comment
Share on other sites

Thank LarsJ and pixelsearch for helping beginners.

For two weeks I've been struggling with the fact that this will work as a universal UDF function for all of my ListViews. It would be a wrong way to always create the same function for every list.

If possible, please post this version as well:

scroll.png

Edited by Norm73
Link to comment
Share on other sites

Yes, please !

And, if possible, more versions in a ZIP file.

I have not managed to run some of them because they were incomplete.

In any case, thank you very much to all.

 

Link to comment
Share on other sites

  • 2 weeks later...
On 5/27/2021 at 8:15 AM, pixelsearch said:

@Norm73 unfortunately _GUICtrlListView_EnsureVisible (as indicated in one of your last post) isn't enough to do what you want. It sometimes display the requested item at the bottom of the screen (if you were selecting an item placed below it)
LVM_GETTOPINDEX is useful to retrieve the index of the topmost visible item, but unfortunately there is no "LVM_SETTOPINDEX" to force an item to be placed at topmost position.

After some searches here & there, I tested a way that can do it and it seems to work fine. Here is the code in case anyone is interested :

;~      ; It works !!!
;~      Local $iRow1stMatch = 200
;~      Local $iGetTopIndex = _GUICtrlListView_GetTopIndex($g_idListView)
;~      Local $iGetItemSpacingY = _GUICtrlListView_GetItemSpacingY($g_idListView, True) ; only once
;~      _GUICtrlListView_Scroll($g_idListView, 0, ($iRow1stMatch - $iGetTopIndex) * $iGetItemSpacingY)

I've tried your example.
It worked well too, that's why I incorporated your idea into my code so that anyone can use it if necessary.

SVLV.png.ce473e9dd1a490681bf13f5c610e4408.png


Here I tried to implement different search options using great examples from LarsJ and pixelsearch.
Everything (seems) to be working fine, but I am bothered by one little thing that I cannot change myself. So I ask for help from more experienced people. The point is that the search is always performed in a "static" array and the search result is always sorted exactly like the array. For example, if I sorted the second column before searching, the sorting isn't preserved after the search.
If a filter is used to search, ie unnecessary lines are deleted, this is usually not noticed and is not a problem. However, if the scrolling search is used, it is immediately noticeable that the sorting has been reset.
Please help me to solve this problem so that the sorting would also be saved after the search.

 

Edited by Norm73
Link to comment
Share on other sites

Hi Norm73 :)
It's not obvious to have both options (your combo "Scroll" and "Filter") in the same script, because it may require to work on different sub-arrays when you choose each option. Also the fact that we can sort on a column while searching in another column makes it hard too. Plus this "All" option in the Combo, all these permanent and temporary indexes etc...

LarsJ himself splitted both options in 2 separate scripts (which were both very inspiring), named :
1) Incremental text search.au3
2) Show matching rows only

A couple of days ago, I ended a script (named "2s") mixing both options as you will see below, but it still requires some testing. I didn't want to post it because it will be a bit hard to maintain and my health isn't going really well (eyes, ears... you name it) so I won't come on the Forum as frequently as I used to, Times are changing, unfortunately...

Anyway, in case it may help you (or other users) here is the most interesting functionality in this version (a context menu allowing to display only matching rows, or all rows) . Pics being better than words :

2038846031_contextmenu1.png.6bad990e24968620087e6a26dc644a56.png

 

1702107125_contextmenu2.png.c0fd338941c3dfc47a37d7a7ea1edcb5.png

The context menu option "Right-click Headers" is checkable too. When checked, it allows to switch quickly from a search column to another. Now the full script "2s" :

#include <ComboConstants.au3>
#include <EditConstants.au3>
#include <GuiListView.au3>
#include <GUIConstantsEx.au3>
#include <StaticConstants.au3>
#include <WindowsConstants.au3>
#include "RandomArray.au3" ; LarsJ
#include "DrawItem.au3"    ;  "

Opt("MustDeclareVars", 1)

Global $g_iRows = 1000, $g_iCols = 6, $g_hGui, $g_hListView, $g_hHeader, $g_hEdit
Global $g_aCols = ["Strings", "Integers", "Floats", "Dates", "Times", "R/C"], $g_aWidths = [230, 61, 124, 70, 60, 60]
Global $g_aColAligns[$g_iCols] = [2, 1, 1, 1, 1, 1] ; $HDF_LEFT = 0, $HDF_RIGHT = 1, $HDF_CENTER = 2
Global $g_idListView, $g_idMarker, $g_idComboCol, $g_idComboColDummy, $g_idEditDummy
Global $g_sSearch, $g_iSearchCol, $g_iSortDir, $g_iSearch = $g_iRows, $g_iLvItem = -1
Global $g_aArray, $g_aSubArray, $g_aSubArray2, $g_tDefaultIndex, $g_tIndex = DllStructCreate("uint arr[" & $g_iRows & "]")
Global $g_aIndex[$g_iCols], $g_aIndexTemp[$g_iCols] ; VarGetType's : $g_aIndex => "Array", $g_aIndex[0] => "String"
Global $g_idContextDummy, $g_hContextmenu, $g_iItem = -1, $g_iSubItem = -1, $g_bShowAllRows = False, $g_bRightClickHeaders = True
Global $fListViewHasFocus = 1 ; trying this for now, we'll see...

Example()

Func Example()
  ; Generate array & one index
  _Generate_All($g_aArray)
  $g_aSubArray = $g_aArray
  $g_aSubArray2 = $g_aArray
  $g_tDefaultIndex = $g_tIndex

  ; Compute LV_Height based on 40 full visible rows, no additional vertical pixel.
  Local $iLV_Height = _ComputeLV_Height(40) ; only 1 param => LV got headers & border
  ConsoleWrite("Computed LV height = " & $iLV_Height & " pixels" & @crlf)

  ; Create GUI
  $g_hGui = GUICreate("Virtual ListView + Incr. Search + Show matching or All rows (2s)", 630 + 20, $iLV_Height + 30 + 20)

  ; Create Edit control (search) + dummy control
  Local $idEdit = GUICtrlCreateEdit("", 120, 10, 182, 20, BitXOR($GUI_SS_DEFAULT_EDIT, $WS_HSCROLL, $WS_VSCROLL))
  $g_hEdit = GUICtrlGetHandle($idEdit)
  $g_idEditDummy = GUICtrlCreateDummy()

  ; Create 2 Search Buttons (Prev / Next) & 2 Accelerator Keys (F3 / Shift+F3)
  Local $idPrev = GUICtrlCreateButton("Prev", 312, 10, 50, 20)
  Local $idNext = GUICtrlCreateButton("Next", 370, 10, 50, 20)
  GUICtrlSetState($idPrev, $GUI_DISABLE)
  GUICtrlSetState($idNext, $GUI_DISABLE)
  Local $aAccelKeys[2][2] = [ [ "{F3}", $idNext ], [ "+{F3}", $idPrev ] ]
  GUISetAccelerators($aAccelKeys)

  ; Create ComboBox control (how to search : RegEx or Normal ?)
  Local $idSearchHow = GUICtrlCreateCombo("RegEx search", 11, 9, 100, 20, BitOR($GUI_SS_DEFAULT_COMBO, $CBS_DROPDOWNLIST))
  Local $sSearchHow = "RegEx search", $sSearchHowPrev = $sSearchHow ; default way of searching (changeable)
  GUICtrlSetData($idSearchHow, "Normal search", $sSearchHow)

  ; Create ComboBox control (column where to search) + dummy control
  GUICtrlCreateLabel("Col", 429, 10, 20, 20, BitOR($SS_CENTERIMAGE, $SS_CENTER))
  $g_idComboCol = GUICtrlCreateCombo("0", 452, 9, 41, 20, BitOR($GUI_SS_DEFAULT_COMBO, $CBS_DROPDOWNLIST))
  $g_iSearchCol = 0 ; default column where to search (changeable)
  Local $iSearchColPrev = $g_iSearchCol
  For $i = 1 To $g_iCols - 1
    GUICtrlSetData($g_idComboCol, $i & "|", $g_iSearchCol)
  Next
  $g_idComboColDummy = GUICtrlCreateDummy()

  ; Create Label control (number of matching results)
  Local $idResult = GUICtrlCreateLabel(" " & $g_iRows & " rows (no pattern)", 501, 10, 135, 20, BitOR($SS_CENTERIMAGE, $SS_SUNKEN))

  ; Create ListView
  $g_idListView = GUICtrlCreateListView("", 10, 40, 630, $iLV_Height, BitOr($GUI_SS_DEFAULT_LISTVIEW, $LVS_OWNERDATA, $LVS_OWNERDRAWFIXED), $WS_EX_CLIENTEDGE)
  _GUICtrlListView_SetExtendedListViewStyle($g_idListView, BitOr($LVS_EX_DOUBLEBUFFER, $LVS_EX_FULLROWSELECT))
  $g_hListView = GUICtrlGetHandle($g_idListView)
  $g_hHeader = _GUICtrlListView_GetHeader($g_idListView)
  For $i = 0 To $g_iCols - 1
    _GUICtrlListView_AddColumn($g_idListView, $g_aCols[$i], $g_aWidths[$i])
    _GUICtrlHeader_SetItemFormat($g_hHeader, $i, $HDF_STRING + $g_aColAligns[$i])
  Next
  Local $iGetItemSpacingY = _GUICtrlListView_GetItemSpacingY($g_idListView, True)

  ; No ListView column resizing by dragging header dividers (LarsJ)
  ;_WinAPI_SetWindowLong( $hHeader, $GWL_STYLE, _WinAPI_GetWindowLong( $hHeader, $GWL_STYLE ) + $HDS_NOSIZING ) ; AutoIt 3.3.14.5 issue
  DllCall( "user32.dll", "long_ptr", @AutoItX64 ? "SetWindowLongPtrW" : "SetWindowLongW", "hwnd", $g_hHeader, "int", $GWL_STYLE, "long_ptr", _
  DllCall( "user32.dll", "long_ptr", @AutoItX64 ? "GetWindowLongPtrW" : "GetWindowLongW", "hwnd", $g_hHeader, "int", $GWL_STYLE )[0] + $HDS_NOSIZING )

  ; Create Marker (an orange line placed above the header of the column being searched)
  $g_idMarker = GUICtrlCreateLabel("", 0, 0)
  GUICtrlSetBkColor(-1, 0xFFA060) ; orange
  _MoveMarker($g_iSearchCol)
  _GUICtrlListView_SetSelectedColumn($g_idListView, $g_iSearchCol)

 ; Create Context menu
  $g_idContextDummy = GUICtrlCreateDummy()
  Local $idContextMenu = GUICtrlCreateContextMenu($g_idContextDummy)
  Local $idContext_GetText = GUICtrlCreateMenuItem("Get text", $idContextMenu)
  GUICtrlCreateMenuItem("", $idContextMenu)
  Local $idContext_ShowAll = GUICtrlCreateMenuItem("Show all rows", $idContextMenu)
  Local $idContext_RightClickHeaders = GUICtrlCreateMenuItem("Right-click Headers", $idContextMenu)
  GUICtrlSetState(-1, 1) ; $GUI_CHECKED = 1
  $g_hContextMenu = GuiCtrlGetHandle($idContextMenu)

  ; Sorting information
  $g_iSortDir = 0x0400 ; $HDF_SORTUP = 0x0400
  Local $iSortCol = -1, $iSortColPrev = -1

  ; Register message handlers
  GUIRegisterMsg($WM_DRAWITEM, "WM_DRAWITEM")
  GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")
  GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")

  ; Sets the virtual number of items in a virtual list-view control
  GUICtrlSendMsg($g_idListView, $LVM_SETITEMCOUNT, $g_iRows, 0)

  GUISetState(@SW_SHOW)

  ; Message loop
  Local $aSearch[$g_iRows], $iStart
  While 1
    Switch GUIGetMsg()
      Case $g_idComboCol, $g_idComboColDummy, $idSearchHow
        $g_iSearchCol = GUICtrlRead($g_idComboCol)
        $sSearchHow = GUICtrlRead($idSearchHow)
        Select
          Case $g_iSearchCol <> $iSearchColPrev
            _MoveMarker($g_iSearchCol) ; Search column will be selected below, after ContinueCase
            $iSearchColPrev = $g_iSearchCol
          Case $sSearchHow <> $sSearchHowPrev
            $sSearchHowPrev = $sSearchHow
          Case Else ; no change in both Combo controls (same search column, same search way)
            ContinueLoop
        EndSelect
        ContinueCase


      Case $g_idEditDummy
        _GUICtrlHeader_SetItemFormat($g_hHeader, $iSortCol, $HDF_STRING + ($iSortCol > -1 ? $g_aColAligns[$iSortCol] : 0))
        $g_sSearch = GUICtrlRead($idEdit)
        $g_tIndex = $g_tDefaultIndex
        ; Sorting information (same as 1st launch, to avoid descending displays when all unsorted rows are shown)
        $g_iSortDir = 0x0400 ; $HDF_SORTUP = 0x0400
        $iSortCol = -1
        $iSortColPrev = -1
        If $g_sSearch = "" Then
          ; Empty search string, display all rows
          $g_aSubArray = $g_aArray
          $g_aSubArray2 = $g_aArray
          $g_iSearch = $g_iRows
          GUICtrlSetState($idPrev, $GUI_DISABLE)
          GUICtrlSetState($idNext, $GUI_DISABLE)
        Else
          ; Find rows matching the search string
          $g_iSearch = 0
          If $sSearchHow = "Normal search" Then ; all searches use RegEx => escape 12 + 1 metacharacters
            $g_sSearch = StringRegExpReplace($g_sSearch, "(\\|\.|\^|\$|\||\[|\(|\{|\*|\+|\?|\#|\))" , "\\$1")
          EndIf
          ; ConsoleWrite("$sSearchHow = " & $sSearchHow & @TAB & "$g_sSearch = " & $g_sSearch & @lf)
          Dim $aSearch[$g_iRows]
          For $i = 0 To $g_iRows - 1
            If StringRegExp($g_aArray[$i][$g_iSearchCol], "(?i)" & $g_sSearch) Then
              For $j = 0 To $g_iCols - 1
                $g_aSubArray2[$g_iSearch][$j] = $g_aArray[$i][$j]
              Next
              $aSearch[$i] = 1 ; include row ($aSearch will serve if user wants to show all rows and click Next / Prec)
              $g_iSearch += 1
            EndIf
          Next
          ; Delete eventual temporary subindexes
          For $i = 0 To $g_iCols - 1
            If VarGetType($g_aIndexTemp[$i]) = "DLLStruct" Then $g_aIndexTemp[$i] = "" ; "String"
          Next
          $g_aSubArray = $g_bShowAllRows ? $g_aArray : $g_aSubArray2
          GUICtrlSetState($idPrev, ($g_bShowAllRows And $g_iSearch And $g_iSearch < $g_iRows) ? $GUI_ENABLE : $GUI_DISABLE)
          GUICtrlSetState($idNext, ($g_bShowAllRows And $g_iSearch And $g_iSearch < $g_iRows) ? $GUI_ENABLE : $GUI_DISABLE)
        EndIf
        GUICtrlSetData($idResult, " " & $g_iSearch & ($g_sSearch = "" ? " rows (no pattern)" : " matching rows"))
        GUICtrlSendMsg($g_idListView, $LVM_SETITEMCOUNT, $g_bShowAllRows ? $g_iRows : $g_iSearch, 0)
        _GUICtrlListView_SetSelectedColumn($g_hListView, $g_iSearchCol) ; seems ok here (after $LVM_SETITEMCOUNT)


      Case $g_idListView ; Sort
        $iSortCol = GUICtrlGetState($g_idListView)
        If $iSortCol <> $iSortColPrev Then
          _GUICtrlHeader_SetItemFormat($g_hHeader, $iSortColPrev, $HDF_STRING + ($iSortColPrev > -1 ? $g_aColAligns[$iSortColPrev] : 0))
        EndIf
        ; Set $g_tIndex + eventual update of $g_aIndex[$iSortCol] OR $g_aIndexTemp[$iSortCol]
        If $g_bShowAllRows Or $g_sSearch = "" Or $g_iSearch = $g_iRows Then
          _UpdateIndex($g_aIndex, $iSortCol)
        Else
          _UpdateIndex($g_aIndexTemp, $iSortCol)
        EndIf
        $g_iSortDir = (($iSortCol = $iSortColPrev) ? ($g_iSortDir = $HDF_SORTUP ? $HDF_SORTDOWN : $HDF_SORTUP) : ($HDF_SORTUP))
        _GUICtrlHeader_SetItemFormat($g_hHeader, $iSortCol, $HDF_STRING + $g_aColAligns[$iSortCol] + $g_iSortDir)
        GUICtrlSendMsg($g_idListView, $LVM_SETSELECTEDCOLUMN, $iSortCol, 0)
        GUICtrlSendMsg($g_idListView, $LVM_SETITEMCOUNT, $g_bShowAllRows ? $g_iRows : $g_iSearch, 0)
        $iSortColPrev = $iSortCol
        ; Update $aSearch (eventually) + automatic display of 1st match (eventually too)
        If $g_bShowAllRows And $g_sSearch And $g_iSearch And $g_iSearch < $g_iRows Then
          _Update_aSearch($aSearch, $iSortCol, $idPrev, $idNext)
        EndIf


      Case $idNext
        $iStart = $g_iLvItem = - 1 ? 0 : $g_iLvItem < $g_iRows - 1 ? $g_iLvItem + 1 : 0
        While 1 ; The while loop is used to start all over when last row in the listview is reached
          For $i = $iStart To $g_iRows - 1
            If $aSearch[$i] And $i > $g_iLvItem Then ExitLoop
          Next
          If $i < $g_iRows Then ExitLoop
          $g_iLvItem = -1
          $iStart = 0
        Wend
        If $g_iLvItem > - 1 Then _GUICtrlListView_SetItemSelected($g_idListView, $g_iLvItem, False, False)
        _GUICtrlListView_ClickItem($g_idListView, $i)
        If $iStart = 0 Then
          Local $iGetTopIndex = _GUICtrlListView_GetTopIndex($g_idListView)
          _GUICtrlListView_Scroll($g_idListView, 0, ($i - $iGetTopIndex) * $iGetItemSpacingY)
        EndIf
        _GUICtrlListView_SetItemSelected($g_idListView, $i, True, True)


      Case $idPrev
        $iStart = $g_iLvItem = - 1 ? $g_iRows - 1 : $g_iLvItem > 0 ? $g_iLvItem - 1 : $g_iRows - 1
        While 1 ; The while loop is used to start all over when first row in the listview is reached
          For $i = $iStart To 0 Step -1
            If $aSearch[$i] And $i < $g_iLvItem Then ExitLoop
          Next
          If $i > -1 Then ExitLoop
          $g_iLvItem = $g_iRows
          $iStart = $g_iRows - 1
        Wend
        If $g_iLvItem > - 1 Then _GUICtrlListView_SetItemSelected($g_idListView, $g_iLvItem, False, False)
        _GUICtrlListView_ClickItem($g_idListView, $i)
        If $iStart = 0 Then
          Local $iGetTopIndex = _GUICtrlListView_GetTopIndex($g_idListView)
          _GUICtrlListView_Scroll($g_idListView, 0, ($i - $iGetTopIndex) * $iGetItemSpacingY)
        EndIf
        _GUICtrlListView_SetItemSelected($g_idListView, $i, True, True)


      Case $g_idContextDummy
        If $g_iItem > - 1 Then _GUICtrlListView_SetSelectedColumn($g_hListView, $g_iSubItem)
        GUICtrlSetState($idContext_GetText, $g_iItem = - 1 ? $GUI_DISABLE : $GUI_ENABLE)
        Local $iMenu_Choice = _TrackPopupMenu($g_hContextMenu, $g_hGui, MouseGetPos(0), MouseGetPos(1))
        If $iMenu_Choice Then
          Switch $iMenu_Choice
            Case $idContext_GetText ; Get text
              Local $sGetText = _GetText($g_iItem, $g_iSubItem)
              MsgBox($MB_TOPMOST, "Context menu", "Row = " & $g_iItem & "   Column = " & $g_iSubItem & "   Text = " & $sGetText & @lf)

            Case $idContext_ShowAll ; Show all rows ?
              If BitAND(GUICtrlRead($idContext_ShowAll), 4) = 4 Then ; $GUI_UNCHECKED = 4
                GUICtrlSetState($idContext_ShowAll, 1) ; $GUI_CHECKED = 1
                $g_bShowAllRows = True
                $g_aSubArray = $g_aArray
                ; Set $g_tIndex + eventual update $g_aIndex[$iSortCol]
                If $iSortCol > - 1 Then _UpdateIndex($g_aIndex, $iSortCol)
                ; Update $aSearch (eventually) + automatic display of 1st match (eventually too)
                If $g_sSearch And $g_iSearch And $g_iSearch < $g_iRows Then _Update_aSearch($aSearch, $iSortCol, $idPrev, $idNext)
              Else
                GUICtrlSetState($idContext_ShowAll, 4) ; $GUI_UNCHECKED = 4
                $g_bShowAllRows = False
                $g_aSubArray = $g_aSubArray2
                ; Set $g_tIndex + eventual update of $g_aIndexTemp[$iSortCol]
                If $iSortCol > - 1 Then _UpdateIndex($g_aIndexTemp, $iSortCol)
                ; No eventual update of $aSearch because $g_bShowAllRows = False (2 buttons Prev & Next are disable in this case)
              EndIf
              GUICtrlSetState($idPrev, ($g_bShowAllRows And $g_sSearch And $g_iSearch And $g_iSearch < $g_iRows) ? $GUI_ENABLE : $GUI_DISABLE)
              GUICtrlSetState($idNext, ($g_bShowAllRows And $g_sSearch And $g_iSearch And $g_iSearch < $g_iRows) ? $GUI_ENABLE : $GUI_DISABLE)
              GUICtrlSendMsg($g_idListView, $LVM_SETITEMCOUNT, $g_bShowAllRows ? $g_iRows : $g_iSearch, 0)

            Case $idContext_RightClickHeaders ; Activate headers right-click ?
              If BitAND(GUICtrlRead($idContext_RightClickHeaders), 4) = 4 Then ; $GUI_UNCHECKED = 4
                GUICtrlSetState($idContext_RightClickHeaders, 1) ; $GUI_CHECKED = 1
                $g_bRightClickHeaders = True
              Else
                GUICtrlSetState($idContext_RightClickHeaders, 4) ; $GUI_UNCHECKED = 4
                $g_bRightClickHeaders = False
              EndIf
          EndSwitch
        Endif


      Case $GUI_EVENT_RESTORE ; needed, or Marker goes back in 0, 0 after Restore (why ?)
        _MoveMarker($g_iSearchCol)


      Case $GUI_EVENT_CLOSE
        ExitLoop
    EndSwitch
  WEnd

  ; Cleanup
  GUIDelete($g_hGui)
EndFunc   ;==>Example

;========================================================================
Func WM_DRAWITEM($hWnd, $iMsg, $wParam, $lParam)
  ; Display items in an owner drawn ListView (called once per item : no separate messages for each subitem +++)

  Local Static $tRect = DllStructCreate( $tagRECT ), $pRect = DllStructGetPtr( $tRect ), $tSize = DllStructCreate( $tagSIZE )
  Local Static $hBrushYellow = _WinAPI_CreateSolidBrush( 0xFFFF00 ), $hBrushCyan = _WinAPI_CreateSolidBrush( 0x00FFFF ) ; Yellow and cyan, BGR
  Local Static $hBrushHighLight = _WinAPI_GetSysColorBrush( $COLOR_HIGHLIGHT ), $hBrushButtonFace = _WinAPI_GetSysColorBrush( $COLOR_BTNFACE )

  Local $tDrawItem = DllStructCreate( $tagDRAWITEM, $lParam ), $itemID = DllStructGetData( $tDrawItem, "itemID" ), $iState = DllStructGetData( $tDrawItem, "itemState" ), $hDC = DllStructGetData( $tDrawItem, "hDC" ), $sItemText

  ; Loop through columns ($i is the column index)
  For $i = 0 To $g_iCols - 1

    ; Subitem rectangle
    DllStructSetData( $tRect, 2, $i ) ; Top
    DllStructSetData( $tRect, 1, $LVIR_BOUNDS ) ; Left
    GUICtrlSendMsg( $g_idListView, $LVM_GETSUBITEMRECT, $itemID, $pRect )
    DllStructSetData( $tRect, 1, DllStructGetData( $tRect, 1 ) + 0 ) ; Left margin

    ; If $i = 0 (first column), the rectangle is calculated for the entire listview item.
    ; Compensate for this by setting the width of the rectangle to the width of the first column.
    ; Before that, if item is selected, fill the entire listview item with the highlight background color.
    If $i = 0 Then
      If BitAND( $iState, $ODS_SELECTED ) Then DllCall( "user32.dll", "int", "FillRect", "handle", $hDC, "struct*", $tRect, "handle", $fListViewHasFocus = 1 ? $hBrushHighLight : $hBrushButtonFace ) ; _WinAPI_FillRect
      DllStructSetData( $tRect, 3, DllStructGetData( $tRect, 1 ) + GUICtrlSendMsg( $g_idListView, $LVM_GETCOLUMNWIDTH, 0, 0 ) )
    EndIf

    ; Retrieve subitem text
    If $g_iSortDir = 0x0400 Then ; $HDF_SORTUP = 0x0400
      $sItemText = $g_aSubArray[$g_tIndex.arr($itemID + 1)][$i]
    Else
      $sItemText = $g_aSubArray[$g_tIndex.arr(($g_bShowAllRows ? $g_iRows : $g_iSearch) - $itemID)][$i]
    EndIf

    ; Subitem rectangle for right and center aligned columns
    If $g_aColAligns[$i] Then ; $HDF_LEFT = 0, $HDF_RIGHT = 1, $HDF_CENTER = 2
      DllCall( "gdi32.dll", "bool", "GetTextExtentPoint32W", "handle", $hDC, "wstr", $sItemText, "int", StringLen( $sItemText ), "struct*", $tSize ) ; _WinAPI_GetTextExtentPoint32
      Switch $g_aColAligns[$i]
        Case 1 ; $HDF_RIGHT
          DllStructSetData( $tRect, "Left", DllStructGetData( $tRect, "Right" ) - DllStructGetData( $tSize, "X" ) - 6 )
        Case 2 ; $HDF_CENTER
          DllStructSetData( $tRect, "Left", DllStructGetData( $tRect, "Left" ) + ( DllStructGetData( $tRect, "Right" ) - DllStructGetData( $tRect, "Left" ) - DllStructGetData( $tSize, "X" ) ) / 2 - 3 )
      EndSwitch
    EndIf

    ; Subitem text color
    DllCall( "gdi32.dll", "int", "SetTextColor", "handle", $hDC, "int", BitAND( $iState, $ODS_SELECTED ) ? $fListViewHasFocus = 1 ? 0xFFFFFF : 0x000000 : 0x000000 ) ; _WinAPI_SetTextColor

    ; Draw subitem text
    DllCall( "user32.dll", "int", "DrawTextW", "handle", $hDC, "wstr", $sItemText, "int", StringLen( $sItemText ), "struct*", $tRect, "uint", 0 ) ; _WinAPI_DrawText

    ; $i is column index, $g_iSearchCol is the search column
    ; Mark matching substring only if column index = search column
    If $i <> $g_iSearchCol Then ContinueLoop

    ; Matching substring?
    If $g_sSearch Then
      Local $sMatch = StringRegExp( $sItemText, "(?i)" & $g_sSearch, 1 )
      If Not @error Then ; match found
        Local $extended = @extended, $iLen = StringLen( $sMatch[0] )

        ; Rectangle for matching substring
        DllCall( "gdi32.dll", "bool", "GetTextExtentPoint32W", "handle", $hDC, "wstr", $sItemText, "int", $extended - $iLen - 1, "struct*", $tSize ) ; _WinAPI_GetTextExtentPoint32
        DllStructSetData( $tRect, "Left", DllStructGetData( $tRect, "Left" ) + DllStructGetData( $tSize, "X" ) )
        DllCall( "gdi32.dll", "bool", "GetTextExtentPoint32W", "handle", $hDC, "wstr", $sMatch[0], "int", $iLen, "struct*", $tSize ) ; _WinAPI_GetTextExtentPoint32
        DllStructSetData( $tRect, "Right", DllStructGetData( $tRect, "Left" ) + DllStructGetData( $tSize, "X" ) )

        ; Fill rectangle with yellow or cyan (selected) background color
        DllStructSetData( $tRect, 2, DllStructGetData( $tRect, 2 ) + 1 ) ; Top  margin
        DllCall( "user32.dll", "int", "FillRect", "handle", $hDC, "struct*", $tRect, "handle", BitAND( $iState, $ODS_SELECTED ) ? $hBrushCyan : $hBrushYellow ) ; _WinAPI_FillRect

        ; Draw matching substring in rectangle
        DllStructSetData( $tRect, 2, DllStructGetData( $tRect, 2 ) - 1 ) ; Top  margin
        DllCall( "gdi32.dll", "int", "SetTextColor", "handle", $hDC, "int", 0x000000 ) ; _WinAPI_SetTextColor
        DllCall( "user32.dll", "int", "DrawTextW", "handle", $hDC, "wstr", $sMatch[0], "int", $iLen, "struct*", $tRect, "uint", 0 ) ; _WinAPI_DrawText
      EndIf
    EndIf
  Next

  Return $GUI_RUNDEFMSG
  #forceref $hWnd, $iMsg, $wParam
EndFunc

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

  Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
  Switch HWnd(DllStructGetData($tNMHDR, "hWndFrom"))
    Case $g_hListView
      Switch DllStructGetData($tNMHDR, "Code")
        Case $LVN_ITEMCHANGED
          Local $tNMListView = DllStructCreate($tagNMLISTVIEW, $lParam)
          Local $iNewState = DllStructGetData($tNMListView, "NewState")
          If BitAND($iNewState, $LVIS_FOCUSED) Then $g_iLvItem = DllStructGetData($tNMListView, "Item")

        Case $NM_RCLICK
          Local $tInfo = DllStructCreate($tagNMITEMACTIVATE, $lParam)
          $g_iItem = DllStructGetData($tInfo, "Index")
;~        If $g_iItem > -1 Then ; valid row
              $g_iSubItem = DllStructGetData($tInfo, "SubItem")
              GUICtrlSendToDummy($g_idContextDummy)
;~        EndIf
      EndSwitch

    Case $g_hHeader
      Switch DllStructGetData($tNMHDR, "Code")
        Case $NM_RCLICK
          If $g_bRightClickHeaders Then
            Local $aHit = _GUICtrlListView_SubItemHitTest($g_hListView)
            ; $aHit[1] : 0-based index of the LV subitem... i.e. the column in our case (may be -1 if right click on empty part of header)
            If $aHit[1] > - 1 Then ; valid column
              GUICtrlSetData($g_idComboCol, $aHit[1])
              GUICtrlSendToDummy($g_idComboColDummy)
            EndIf
          EndIf

        Case $HDN_ENDTRACKW ; keep it, in case someone needs it
          _MoveMarker(GUICtrlRead($g_idComboCol))

        Case $HDN_DIVIDERDBLCLICKW ; keep it, in case someone needs it
          Local $tNMHEADER = DllStructCreate($tagNMHEADER, $lParam)
          Local $iCol = DllStructGetData($tNMHEADER, "Item")
          _GUICtrlListView_SetColumnWidth($g_idListView, $iCol, $g_aWidths[$iCol]) ; initial size
          _MoveMarker(GUICtrlRead($g_idComboCol))
      EndSwitch
  EndSwitch

  Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

;========================================================================
Func WM_COMMAND($hWnd, $iMsg, $wParam, $lParam)

  Local $hWndFrom = $lParam
  Local $iCode = BitShift($wParam, 16) ; High word
  Switch $hWndFrom
    Case $g_hEdit
      Switch $iCode
        Case $EN_CHANGE
          GUICtrlSendToDummy($g_idEditDummy)
      EndSwitch
  EndSwitch
  Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_COMMAND

;========================================================================
Func _Generate_All(ByRef $g_aArray)

  ConsoleWrite("$g_iRows = " & $g_iRows & "    $g_iCols = " & $g_iCols & @CRLF)
  $g_aArray = FAS_Random2DArrayAu3($g_iRows, "sifdtr", "abcdefghijklmnopqrstuvwxyz")
  ; $g_aArray = FAS_Random2DArrayAu3($g_iRows, "sifdtr", "abcdefghijklmnopqrstuvwxyz" & "\.^$|[({*+?#)") ; 12 + 1 RegEx metacharacters
  For $i = 0 To $g_iRows - 1
    $g_tIndex.arr($i + 1) = $i
  Next
EndFunc   ;==>_Generate_All

;========================================================================
Func _SortArrayStruct(Const ByRef $aArray, $iCol, $iRows)
  Local $tIndex = DllStructCreate("uint arr[" & $iRows & "]")
  Local $pIndex = DllStructGetPtr($tIndex)
  Local Static $hDll = DllOpen("kernel32.dll")
  Local Static $hDllComp = DllOpen("shlwapi.dll")

  Local $lo, $hi, $mi, $r

  ; Sorting by one column
  For $i = 1 To $iRows - 1
    $lo = 0
    $hi = $i - 1
    Do
      $mi = Int(($lo + $hi) / 2)
      $r = DllCall($hDllComp, 'int', 'StrCmpLogicalW', 'wstr', $aArray[$i][$iCol], 'wstr', $aArray[DllStructGetData($tIndex, 1, $mi + 1)][$iCol])[0]
      Switch $r
        Case -1
          $hi = $mi - 1
        Case 1
          $lo = $mi + 1
        Case 0
          ExitLoop
      EndSwitch
    Until $lo > $hi
    DllCall($hDll, "none", "RtlMoveMemory", "struct*", $pIndex + ($mi + 1) * 4, "struct*", $pIndex + $mi * 4, "ulong_ptr", ($i - $mi) * 4)
    DllStructSetData($tIndex, 1, $i, $mi + 1 + ($lo = $mi + 1))
  Next

  Return $tIndex
EndFunc   ;==>_SortArrayStruct

;========================================================================
Func _UpdateIndex(ByRef $aIndex, $iCol)

  If VarGetType($aIndex[$iCol]) = "DLLStruct" Then
    $g_tIndex = $aIndex[$iCol]
  Else
    $g_tIndex = _SortArrayStruct($g_aSubArray, $iCol, $g_bShowAllRows ? $g_iRows : $g_iSearch)
    $aIndex[$iCol] = $g_tIndex ; "DLLStruct" (or "Int32" when no match found +++)
  EndIf
EndFunc   ;==>_UpdateIndex

;========================================================================
Func _Update_aSearch(ByRef $aSearch, $iSortCol, $idPrev, $idNext)

  $g_iSearch = 0
  Dim $aSearch[$g_iRows]
  For $i = 0 To $g_iRows - 1
    If StringRegExp($g_aArray[$g_tIndex.arr($g_iSortDir = 0x0400 ? $i + 1 : $g_iRows - $i)][$g_iSearchCol], "(?i)" & $g_sSearch) Then
      $aSearch[$i] = 1 ; include row
      $g_iSearch += 1
    EndIf
  Next

  If $g_iSearch And $iSortCol = $g_iSearchCol Then
    GUICtrlSetState($idPrev, $GUI_ENABLE)
    GUICtrlSetState($idNext, $GUI_ENABLE)
    $g_iLvItem = - 1 ; comment this line if 1st match not to be displayed on 1st row
    ControlClick($g_hGui, "", $idNext)
  EndIf
EndFunc   ;==>_Update_aSearch

;========================================================================
Func _MoveMarker($iCol)

  Local $aRect = _GUICtrlHeader_GetItemRect($g_hHeader, $iCol)
  ControlMove($g_hGui, "", $g_idMarker,  10 + $aRect[0], 40 - 3, $aRect[2] - $aRect[0] + 1, 3) ; 10 / 40 are LV coords
EndFunc   ;==>_MoveMarker

;========================================================================
Func _TrackPopupMenu($hMenu, $hWnd, $iX, $iY)

  ; $TPM_RETURNCMD (0x0100) returns the menu item identifier of the user's selection in the return value.
  Return DllCall("user32.dll", "int", "TrackPopupMenuEx", "hwnd", $hMenu, "int", 0x0100, "int", $iX, "int", $iY, "hwnd", $hWnd, "ptr", 0)[0]
EndFunc   ;==>_TrackPopupMenu

;========================================================================
Func _GetText($iItem, $iSubItem)

  Local $sGetText
  If $g_iSortDir = 0x0400 Then ; $HDF_SORTUP = 0x0400
    $sGetText = $g_aSubArray[$g_tIndex.arr($iItem + 1)][$iSubItem]
  Else
    $sGetText = $g_aSubArray[$g_tIndex.arr(($g_bShowAllRows ? $g_iRows : $g_iSearch) - $iItem)][$iSubItem]
  EndIf
  Return $sGetText
EndFunc   ;==>_GetText

;========================================================================
Func _ComputeLV_Height($iNb_Rows, $bHeader = True, $bBorder = True)

  Local $hGui = GUICreate("", 170, 200) ; test values not to be changed
  Local $idListView = GUICtrlCreateListView("Col 0", 10, 10, 150, 180) ; border $WS_EX_CLIENTEDGE

  Local $iClient_Height = WinGetClientSize(GUICtrlGetHandle($idListView))[1]
  Local $iBorder_Height = 180 - $iClient_Height

  Local $hHeader = GUICtrlSendMsg($idListView, $LVM_GETHEADER, 0, 0)
  Local $iHeader_Height = _WinAPI_GetWindowHeight($hHeader)

  Local $iGetItemSpacingY = _GUICtrlListView_GetItemSpacingY($idListView, True)

  GUIDelete($hGui)
  Return ($iNb_Rows * $iGetItemSpacingY) + ($bHeader ? $iHeader_Height : 0) + ($bBorder ? $iBorder_Height : 0)
EndFunc   ;==>_ComputeLV_Height

Concerning a new function _ComputeLV_Height() found at the very end of the script, it should deserve explanations in a separate post. Basically, the function computes an accurate ListView height, which will be used during Gui and LV creation. For example, in the script, we want 40 full rows in LV, without having any additional vertical pixels (half rows) at the bottom of the LV. If I'm not mistaken, this is how LarsJ's "788" listview height (40 full rows) is computed :

LarsJ's 788 LV height =
=> 40 rows X 19  = 760
=> Header height =  24
=> 2 x 2 borders =   4 ($WS_EX_CLIENTEDGE)
760  +  24  +  4 = 788

When we run the script, each one of us will have a different ListView height because of our different OS's (or the modif. we did in Windows parameters). On my computer, it's not 788 but 624 which is returned by  _ComputeLV_Height (to have 40 full rows). Here is a pic of my last 40th row, without a single additional or missing pixel (1st row is 0, which explains "39" written at the end of the last row). Let's hope the function will work for any user.

1782416087_noncroppedrow.png.e02a6e3d863836527dd3fe3e9dc146b5.png

By the way, I got rid of the Combo "All" option in the script, as it is already complicated enough. Thanks for not asking new implementations as I won't be here often from now on. Good luck to you all and stay healthy :bye:

Edit #1 - update June 10th 2021 : disable 2 search buttons (Prev / Next) if number of matched rows  = number of initial array rows, when search field is not empty (a . with RegEx search for instance that matches all rows) and "Show all rows" is ticked in context menu.

Edited by pixelsearch
Updates when needed (details above)
Link to comment
Share on other sites

Hi pixelsearch

Thank you for your efforts and explanations.

Sorry if I was too impatient or persistent.

I understand you well as I have had similar problems lately myself.☹️

I don't know if this would be appropriate, but I wish you a speedy recovery...

P.S.

I was not very attentive, but after looking at my code with a fresh head, I found a solution for my question.

The solution turned out to be very simple.

Added two new lines and that's it.

Edited by Norm73
Link to comment
Share on other sites

Spoiler
50 minutes ago, Norm73 said:

Hi pixelsearch

Thank you for your efforts and explanations.

Sorry if I was too impatient or persistent.

I understand you well as I have had similar problems lately myself.☹️

I don't know if this would be appropriate, but I wish you a speedy recovery...

I join in ...

 

Link to comment
Share on other sites

@pixelsearch

Hi, thanks for your example!

is $LVS_EX_HEADERDRAGDROP style acceptable?  i found a problem about Zero column draged.

column 0 rectangle is not correct after dragged to other column position, especially horizontal direction listview croll. 

both _GUICtrlListView_GetSubItemRect  and  _GUICtrlListView_GetItemRect  can't get the correct positon of column zero, any other idea about this question?

994767353_Dragcolumnzero.thumb.png.a61259f82b031463549864d1a958f536.png

Edited by powerofos
Link to comment
Share on other sites

9 hours ago, Norm73 said:

I don't know if this would be appropriate, but I wish you a speedy recovery...

@Norm73 it's always appropriate when it comes directly from the heart. Thanks a lot :)
Concerning your last script, I found this kind of issue (pic below). Here is a way to reproduct it in 5 steps :

* Choose column 1 (combo) i.e Strings-2 got the orange marker above (searched column)
* Edit search : type an a
* Column string-2 : one left click to sort ascending
* Combo : switch from Filter to Scroll
* Column string-2 : one left click to sort descending. Display :

Norm73a.png.2c909cb70c66d001988ee434880047e1.png

Maybe the 2 new lines you added 1 hour ago fixed it ? Fingers crossed but I doubt it. I remember having had exactly the same issue sometimes, while scripting version "2s", before using the 2nd set of array $g_aSubArray2, plus the new array $aSearch. If you look at code in "2s" you'll find several additional tests when it comes to sorting or switching from "Filter" to "Scroll" as you name it, (which corresponds to my context menu option "Show all rows" when it's checked / unchecked).

Also, I remember this kind of line that fixed the issue. The index sort function Func _UpdateIndex() needs to be updated. In the precedent versions (before "2s") it was :

$g_tIndex = _SortArrayStruct($g_aSubArray, $iCol, $g_iSearch)

Now in "2s", it becomes :

$g_tIndex = _SortArrayStruct($g_aSubArray, $iCol, $g_bShowAllRows ? $g_iRows : $g_iSearch)

This kind of ternary test in now found in plenty of lines in "2s", this could explain why you're having the issue. Good luck.

7 hours ago, powerofos said:

is $LVS_EX_HEADERDRAGDROP style acceptable?

@powerofos it's an interesting question but as the data source is an array, then maybe it could work if the array was rearranged internally after you drag a header to another place, not forgetting the sub-arrays and related arrays like these ones :

Global $g_aCols = ["Strings", "Integers", "Floats", "Dates", "Times", "R/C"], $g_aWidths = [230, 61, 124, 70, 60, 60]
Global $g_aColAligns[$g_iCols] = [2, 1, 1, 1, 1, 1] ; $HDF_LEFT = 0, $HDF_RIGHT = 1, $HDF_CENTER = 2

But I'm not even sure of what I just wrote, it requires tests. I remember having used $LVS_EX_HEADERDRAGDROP in my CSV file editor and it worked really fine (though it wasn't a virtual LV). I wish the solution is to keep the arrays untouched but I don't remember exactly how it worked there !

As you're using the script, may I suggest you download the "2s" version just above because it's the version that could eventually be updated.

But please think twice before allowing the draggable header style : wouldn't it be better to create the LV with the headers correctly placed and keep them untouched ?

In this link, LarsJ wrote :

"A workaround for all these HDN_DIVIDERDBLCLICK issues is to set correct column widths from the start, and then disable column resize through dragging/double-clicking header dividers by adding the HDS_NOSIZING style to the header control"

I don't know what LarsJ thinks about the $LVS_EX_HEADERDRAGDROP style when it comes to Virtual ListViews. Maybe he'll tell us. I'll try to test it when I got some time.

Edited by pixelsearch
typo
Link to comment
Share on other sites

@pixelsearch @Larsj

LarsJ  's  solution in $NM_CUSTOMDRAW 

; Stage 4
                        Case BitOR( $CDDS_ITEMPOSTPAINT, $CDDS_SUBITEM ) ; After a subitem has been painted
                            If DllStructGetData( $tNMLVCustomDraw, "iSubItem" ) = $iSubItem Then
                                Local $hDC = DllStructGetData( $tNMLVCUSTOMDRAW, "hdc" )                   ; Device context
                                If $iSubItem Then
                                    $aRect = _GUICtrlListView_GetSubItemRect( $hListView, $iItem, $iSubItem ); Subitem rectangle
                                Else ; $iSubItem = 0, the listview item, first column
                                    ; This calculation of $aRect for the listview item is necessary to get
                                    ; things to work properly if $LVS_EX_HEADERDRAGDROP style is applied.
                                    $aRect = _GUICtrlListView_GetItemRect( $hListView, $iItem, 2 )           ; 2 - The bounding rectangle of the item text
                                    $aRect[0] -= 4
                                EndIf
                                If $bNotXP And $iSubItem = 0 And $aColumnOrder[1] <> 0 Then
                                    DllStructSetData( $tRect, "Left", $aRect[0] )                            ; Margin for first column if dragged to another position
                                Else
                                    DllStructSetData( $tRect, "Left", $aRect[0]+4 )
                                EndIf
                                DllStructSetData( $tRect, "Top",    $aRect[1] )
                                DllStructSetData( $tRect, "Right",  $aRect[2] )
                                DllStructSetData( $tRect, "Bottom", $aRect[3] )
                                _WinAPI_FillRect( $hDC, $tRect, $hBrush )                                  ; Fill subitem background
                                _WinAPI_SetBkMode( $hDC, $TRANSPARENT )                                    ; Transparent background
                                _WinAPI_SetTextColor( $hDC, 0x000000 )                                     ; Set black text color
                                DllStructSetData( $tRect, "Left", DllStructGetData( $tRect, "Left" ) + 2 ) ; Adjust rectangle
                                If $bNotXP Then DllStructSetData( $tRect, "Top", DllStructGetData( $tRect, "Top" ) + 2 )
                                _WinAPI_DrawText( $hDC, _GUICtrlListView_GetItemText( $hListView, $iItem, $iSubItem ), $tRect, $DT_WORD_ELLIPSIS ) ; Draw text
                            EndIf
                            Return $CDRF_NEWFONT                           ; $CDRF_NEWFONT must be returned after changing font or colors

 

used _GUICtrlListView_GetItemRect to get rectangle position if column index is 0

i can't used it in Itemdraw message , it crash

After several modifications, I use the position of a column to the left of column 0 to fix the position problem of column 0

Case $HDN_ENDDRAG
    GUICtrlSendToDummy($vColsDragDummy)-----------------------------------------------

Case $vColsDragDummy  
   $vColsOrder = _GUICtrlListView_GetColumnOrderArray($hVDListView)         
    If $vColsOrder[1] <> 0 Then         
      For $i = 1 To $vColsOrder[0] Step 1
            If $vColsOrder[$i] = 0 Then
                $vColsZero_LeftID = $vColsOrder[$i-1]
                ExitLoop
            EndIf
        Next
    EndIf
    
    
;ItemDraw message:------------------------------------
   Local $tSubRect = DllStructCreate("struct;long Left;long Top;long Right;long Bottom;endstruct");$tagRECT
    DllStructSetData($tSubRect,"Top",$iColsID)
    DllStructSetData($tSubRect,"Left",0);$LVIR_BOUNDS = 0
    GUICtrlSendMsg($VDListView,0x1000+56,$iRowsID,DllStructGetPtr($tSubRect))
   If $iColsID = 0 Then
        If $vColsOrder[1] <> 0 Then                                                 
            Local $tSub0Rect = DllStructCreate("struct;long Left;long Top;long Right;longBottom;endstruct");_GUICtrlListView_GetSubItemRect($hVDListView,$iRowsID,$vColsZero_LeftID)
            DllStructSetData($tSub0Rect,"Top",$vColsZero_LeftID)
            DllStructSetData($tSub0Rect,"Left",0);$LVIR_BOUNDS = 0
            GUICtrlSendMsg($VDListView,0x1000+56,$iRowsID,DllStructGetPtr($tSub0Rect))                              
            DllStructSetData($tSubRect,"Left",DllStructGetData($tSub0Rect,"Right"))
        EndIf                           
        DllStructSetData($tSubRect,"Right",DllStructGetData($tSubRect,"Left")+GUICtrlSendMsg($VDListView,0x1000+29,0,0));$LVM_GETCOLUMNWIDTH                        
    EndIf

 

Any other good idea ?

Edited by powerofos
Link to comment
Share on other sites

23 hours ago, pixelsearch said:

Now in "2s", it becomes :

$g_tIndex = _SortArrayStruct($g_aSubArray, $iCol, $g_bShowAllRows ? $g_iRows : $g_iSearch)

Thank you very much!  :yes:

I fixed everything.

I thought you weren't going to release your latest version so I decided to do the best I can myself.

Now I will deal with the latest version.

And I'm looking forward to your CSV editor. I hope one day you will post it too.

Edited by Norm73
Link to comment
Share on other sites

On 6/10/2021 at 3:44 PM, pixelsearch said:

Now in "2s", it becomes :

$g_tIndex = _SortArrayStruct($g_aSubArray, $iCol, $g_bShowAllRows ? $g_iRows : $g_iSearch)

Thank you very much!  :yes:
I fixed everything.

@Norm73 Could you send the hole code, please !!

Link to comment
Share on other sites

@Norm73 glad you made it :)

@powerofos I tried to script a version "2t" with draggable headers, but I'm not really convinced with it, we'll see...

254711954_Draggableheaders.png.bef0bd41af5e5452d1a562fed1343c49.png

#include <ComboConstants.au3>
#include <EditConstants.au3>
#include <GuiListView.au3>
#include <GUIConstantsEx.au3>
#include <StaticConstants.au3>
#include <WindowsConstants.au3>
#include "RandomArray.au3" ; LarsJ
#include "DrawItem.au3"    ;  "

Opt("MustDeclareVars", 1)

Global $g_iRows = 10000, $g_iCols = 6, $g_hGui, $g_hListView, $g_hHeader, $g_hEdit
Global $g_aCols = ["Strings", "Integers", "Floats", "Dates", "Times", "R/C"], $g_aWidths = [230, 61, 124, 70, 60, 60]
Global $g_aColAligns[$g_iCols] = [2, 1, 1, 1, 1, 1] ; $HDF_LEFT = 0, $HDF_RIGHT = 1, $HDF_CENTER = 2
Global $g_idListView, $g_idMarker, $g_idComboCol, $g_idComboColDummy, $g_idEditDummy
Global $g_sSearch, $g_iSearchCol, $g_iSortDir, $g_iSearch = $g_iRows, $g_iLvItem = -1
Global $g_aArray, $g_aSubArray, $g_aSubArray2, $g_tDefaultIndex, $g_tIndex = DllStructCreate("uint arr[" & $g_iRows & "]")
Global $g_aIndex[$g_iCols], $g_aIndexTemp[$g_iCols] ; VarGetType's : $g_aIndex => "Array", $g_aIndex[0] => "String"
Global $g_idContextDummy, $g_hContextmenu, $g_iItem = -1, $g_iSubItem = -1, $g_bShowAllRows = False, $g_bRightClickHeaders = True
Global $fListViewHasFocus = 1 ; trying this for now, we'll see...
Global $g_bHeaderDragged = False, $g_aColumnOrder[$g_iCols + 1] ; format of this array in help file topic _GUICtrlListView_GetColumnOrderArray
                                                                ; related to LV ex. style $LVS_EX_HEADERDRAGDROP and message $HDN_ENDDRAG

Global $g_bPreventHeaderChanges = False ; important on older OS (allow header changes at some moments in case $HDS_NOSIZING is not recognized)
                                        ; it's crucial as style $LVS_EX_HEADERDRAGDROP & owner drawn are present in the script : no column resizing !
Example()

Func Example()
  ; Generate array & one index
  _Generate_All($g_aArray)
  $g_aSubArray = $g_aArray
  $g_aSubArray2 = $g_aArray
  $g_tDefaultIndex = $g_tIndex

  ; Compute LV_Height based on 40 full visible rows, no additional vertical pixel.
  Local $iLV_Height = _ComputeLV_Height(40) ; only 1 param => LV got headers & border
  ConsoleWrite("Computed LV height = " & $iLV_Height & " pixels" & @crlf & @crlf)

  ; Create GUI
  $g_hGui = GUICreate("Virtual ListView (owner drawn) + Incr. Search + draggable Headers (2t)", 630 + 20, $iLV_Height + 30 + 20)

  ; Create Edit control (search) + dummy control
  Local $idEdit = GUICtrlCreateEdit("", 120, 10, 182, 20, BitXOR($GUI_SS_DEFAULT_EDIT, $WS_HSCROLL, $WS_VSCROLL))
  $g_hEdit = GUICtrlGetHandle($idEdit)
  $g_idEditDummy = GUICtrlCreateDummy()

  ; Create 2 Search Buttons (Prev / Next) & 2 Accelerator Keys (F3 / Shift+F3)
  Local $idPrev = GUICtrlCreateButton("Prev", 312, 10, 50, 20)
  Local $idNext = GUICtrlCreateButton("Next", 370, 10, 50, 20)
  GUICtrlSetState($idPrev, $GUI_DISABLE)
  GUICtrlSetState($idNext, $GUI_DISABLE)
  Local $aAccelKeys[2][2] = [ [ "{F3}", $idNext ], [ "+{F3}", $idPrev ] ]
  GUISetAccelerators($aAccelKeys)

  ; Create ComboBox control (how to search : RegEx or Normal ?)
  Local $idSearchHow = GUICtrlCreateCombo("RegEx search", 11, 9, 100, 20, BitOR($GUI_SS_DEFAULT_COMBO, $CBS_DROPDOWNLIST))
  Local $sSearchHow = "RegEx search", $sSearchHowPrev = $sSearchHow ; default way of searching (changeable)
  GUICtrlSetData($idSearchHow, "Normal search", $sSearchHow)

  ; Create ComboBox control (column where to search) + dummy control
  GUICtrlCreateLabel("Col", 429, 10, 20, 20, BitOR($SS_CENTERIMAGE, $SS_CENTER))
  $g_idComboCol = GUICtrlCreateCombo("0", 452, 9, 41, 20, BitOR($GUI_SS_DEFAULT_COMBO, $CBS_DROPDOWNLIST))
  $g_iSearchCol = 0 ; default column where to search (changeable)
  Local $iSearchColPrev = $g_iSearchCol
  For $i = 1 To $g_iCols - 1
    GUICtrlSetData($g_idComboCol, $i & "|", $g_iSearchCol)
  Next
  $g_idComboColDummy = GUICtrlCreateDummy()

  ; Create Label control (number of matching results)
  Local $idResult = GUICtrlCreateLabel(" " & $g_iRows & " rows (no pattern)", 501, 10, 135, 20, BitOR($SS_CENTERIMAGE, $SS_SUNKEN))

  ; Create ListView
  $g_idListView = GUICtrlCreateListView("", 10, 40, 630, $iLV_Height, BitOr($GUI_SS_DEFAULT_LISTVIEW, $LVS_OWNERDATA, $LVS_OWNERDRAWFIXED), $WS_EX_CLIENTEDGE)
  _GUICtrlListView_SetExtendedListViewStyle($g_idListView, BitOr($LVS_EX_DOUBLEBUFFER, $LVS_EX_FULLROWSELECT, $LVS_EX_HEADERDRAGDROP))
  $g_hListView = GUICtrlGetHandle($g_idListView)
  $g_hHeader = _GUICtrlListView_GetHeader($g_idListView)
  For $i = 0 To $g_iCols - 1
    _GUICtrlListView_AddColumn($g_idListView, $g_aCols[$i], $g_aWidths[$i])
    _GUICtrlHeader_SetItemFormat($g_hHeader, $i, $HDF_STRING + $g_aColAligns[$i])
    $g_aColumnOrder[$i + 1] = $i
  Next
  $g_aColumnOrder[0] = $g_iCols
  Local $iGetItemSpacingY = _GUICtrlListView_GetItemSpacingY($g_idListView, True)

  ; No ListView column resizing by dragging header dividers (LarsJ) . (Personal : it's nearly mandatory when $LVS_EX_HEADERDRAGDROP + owner drawn)
  ;_WinAPI_SetWindowLong( $hHeader, $GWL_STYLE, _WinAPI_GetWindowLong( $hHeader, $GWL_STYLE ) + $HDS_NOSIZING ) ; AutoIt 3.3.14.5 issue
  DllCall( "user32.dll", "long_ptr", @AutoItX64 ? "SetWindowLongPtrW" : "SetWindowLongW", "hwnd", $g_hHeader, "int", $GWL_STYLE, "long_ptr", _
  DllCall( "user32.dll", "long_ptr", @AutoItX64 ? "GetWindowLongPtrW" : "GetWindowLongW", "hwnd", $g_hHeader, "int", $GWL_STYLE )[0] + $HDS_NOSIZING )

  ; Create Marker (an orange line placed above the header of the column being searched)
  $g_idMarker = GUICtrlCreateLabel("", 0, 0)
  GUICtrlSetBkColor(-1, 0xFFA060) ; orange
  _MoveMarker($g_iSearchCol)
  _GUICtrlListView_SetSelectedColumn($g_idListView, $g_iSearchCol)

 ; Create Context menu
  $g_idContextDummy = GUICtrlCreateDummy()
  Local $idContextMenu = GUICtrlCreateContextMenu($g_idContextDummy)
  Local $idContext_GetText = GUICtrlCreateMenuItem("Get text", $idContextMenu)
  GUICtrlCreateMenuItem("", $idContextMenu)
  Local $idContext_ShowAll = GUICtrlCreateMenuItem("Show all rows", $idContextMenu)
  Local $idContext_RightClickHeaders = GUICtrlCreateMenuItem("Right-click Headers", $idContextMenu)
  GUICtrlSetState(-1, 1) ; $GUI_CHECKED = 1
  $g_hContextMenu = GuiCtrlGetHandle($idContextMenu)

  ; Sorting information
  $g_iSortDir = 0x0400 ; $HDF_SORTUP = 0x0400
  Local $iSortCol = -1, $iSortColPrev = -1

  ; Register message handlers
  GUIRegisterMsg($WM_DRAWITEM, "WM_DRAWITEM")
  GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")
  GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")

  ; Sets the virtual number of items in a virtual list-view control
  GUICtrlSendMsg($g_idListView, $LVM_SETITEMCOUNT, $g_iRows, 0)

  GUISetState(@SW_SHOW)

  ; Message loop
  $g_bPreventHeaderChanges = True ; prevent changes in headers (no resizing) until next _GUICtrlHeader_SetItemFormat instruction.
  Local $aSearch[$g_iRows], $iStart
  While 1
    Switch GUIGetMsg()
      Case $g_idComboCol, $g_idComboColDummy, $idSearchHow
        $g_iSearchCol = GUICtrlRead($g_idComboCol)
        $sSearchHow = GUICtrlRead($idSearchHow)
        Select
          Case $g_iSearchCol <> $iSearchColPrev
            _MoveMarker($g_iSearchCol) ; Search column will be selected below, after ContinueCase
            $iSearchColPrev = $g_iSearchCol
          Case $sSearchHow <> $sSearchHowPrev
            $sSearchHowPrev = $sSearchHow
          Case Else ; no change in both Combo controls (same search column, same search way)
            ContinueLoop
        EndSelect
        ContinueCase


      Case $g_idEditDummy
        $g_bPreventHeaderChanges = False ; surround next instruction that changes a header (allow it, then prevent it just after)
        _GUICtrlHeader_SetItemFormat($g_hHeader, $iSortCol, $HDF_STRING + ($iSortCol > -1 ? $g_aColAligns[$iSortCol] : 0))
        $g_bPreventHeaderChanges = True ; no more changes until next _GUICtrlHeader_SetItemFormat instruction.
        $g_sSearch = GUICtrlRead($idEdit)
        $g_tIndex = $g_tDefaultIndex
        ; Sorting information (same as 1st launch, to avoid descending displays when all unsorted rows are shown)
        $g_iSortDir = 0x0400 ; $HDF_SORTUP = 0x0400
        $iSortCol = -1
        $iSortColPrev = -1
        If $g_sSearch = "" Then
          ; Empty search string, display all rows
          $g_aSubArray = $g_aArray
          $g_aSubArray2 = $g_aArray
          $g_iSearch = $g_iRows
          GUICtrlSetState($idPrev, $GUI_DISABLE)
          GUICtrlSetState($idNext, $GUI_DISABLE)
        Else
          ; Find rows matching the search string
          $g_iSearch = 0
          If $sSearchHow = "Normal search" Then ; all searches use RegEx => escape 12 + 1 metacharacters
            $g_sSearch = StringRegExpReplace($g_sSearch, "(\\|\.|\^|\$|\||\[|\(|\{|\*|\+|\?|\#|\))" , "\\$1")
          EndIf
          ; ConsoleWrite("$sSearchHow = " & $sSearchHow & @TAB & "$g_sSearch = " & $g_sSearch & @lf)
          Dim $aSearch[$g_iRows]
          For $i = 0 To $g_iRows - 1
            If StringRegExp($g_aArray[$i][$g_iSearchCol], "(?i)" & $g_sSearch) Then
              For $j = 0 To $g_iCols - 1
                $g_aSubArray2[$g_iSearch][$j] = $g_aArray[$i][$j]
              Next
              $aSearch[$i] = 1 ; include row ($aSearch will serve if user wants to show all rows and click Next / Prec)
              $g_iSearch += 1
            EndIf
          Next
          ; Delete eventual temporary subindexes
          For $i = 0 To $g_iCols - 1
            If VarGetType($g_aIndexTemp[$i]) = "DLLStruct" Then $g_aIndexTemp[$i] = "" ; "String"
          Next
          $g_aSubArray = $g_bShowAllRows ? $g_aArray : $g_aSubArray2
          GUICtrlSetState($idPrev, ($g_bShowAllRows And $g_iSearch And $g_iSearch < $g_iRows) ? $GUI_ENABLE : $GUI_DISABLE)
          GUICtrlSetState($idNext, ($g_bShowAllRows And $g_iSearch And $g_iSearch < $g_iRows) ? $GUI_ENABLE : $GUI_DISABLE)
        EndIf
        GUICtrlSetData($idResult, " " & $g_iSearch & ($g_sSearch = "" ? " rows (no pattern)" : " matching rows"))
        GUICtrlSendMsg($g_idListView, $LVM_SETITEMCOUNT, $g_bShowAllRows ? $g_iRows : $g_iSearch, 0)
        _GUICtrlListView_SetSelectedColumn($g_hListView, $g_iSearchCol) ; seems ok here (after $LVM_SETITEMCOUNT)


      Case $g_idListView ; Sort
        $iSortCol = GUICtrlGetState($g_idListView)
        If $iSortCol <> $iSortColPrev Then
            $g_bPreventHeaderChanges = False
          _GUICtrlHeader_SetItemFormat($g_hHeader, $iSortColPrev, $HDF_STRING + ($iSortColPrev > -1 ? $g_aColAligns[$iSortColPrev] : 0))
          $g_bPreventHeaderChanges = True
        EndIf
        ; Set $g_tIndex + eventual update of $g_aIndex[$iSortCol] OR $g_aIndexTemp[$iSortCol]
        If $g_bShowAllRows Or $g_sSearch = "" Or $g_iSearch = $g_iRows Then
          _UpdateIndex($g_aIndex, $iSortCol)
        Else
          _UpdateIndex($g_aIndexTemp, $iSortCol)
        EndIf
        $g_iSortDir = (($iSortCol = $iSortColPrev) ? ($g_iSortDir = $HDF_SORTUP ? $HDF_SORTDOWN : $HDF_SORTUP) : ($HDF_SORTUP))
        $g_bPreventHeaderChanges = False
        _GUICtrlHeader_SetItemFormat($g_hHeader, $iSortCol, $HDF_STRING + $g_aColAligns[$iSortCol] + $g_iSortDir)
        $g_bPreventHeaderChanges = True
        GUICtrlSendMsg($g_idListView, $LVM_SETSELECTEDCOLUMN, $iSortCol, 0)
        GUICtrlSendMsg($g_idListView, $LVM_SETITEMCOUNT, $g_bShowAllRows ? $g_iRows : $g_iSearch, 0)
        $iSortColPrev = $iSortCol
        ; Update $aSearch (eventually) + automatic display of 1st match (eventually too)
        If $g_bShowAllRows And $g_sSearch And $g_iSearch And $g_iSearch < $g_iRows Then
          _Update_aSearch($aSearch, $iSortCol, $idPrev, $idNext)
        EndIf


      Case $idNext
        $iStart = $g_iLvItem = - 1 ? 0 : $g_iLvItem < $g_iRows - 1 ? $g_iLvItem + 1 : 0
        While 1 ; The while loop is used to start all over when last row in the listview is reached
          For $i = $iStart To $g_iRows - 1
            If $aSearch[$i] And $i > $g_iLvItem Then ExitLoop
          Next
          If $i < $g_iRows Then ExitLoop
          $g_iLvItem = -1
          $iStart = 0
        Wend
        If $g_iLvItem > - 1 Then _GUICtrlListView_SetItemSelected($g_idListView, $g_iLvItem, False, False)
        _GUICtrlListView_ClickItem($g_idListView, $i)
        If $iStart = 0 Then
          Local $iGetTopIndex = _GUICtrlListView_GetTopIndex($g_idListView)
          _GUICtrlListView_Scroll($g_idListView, 0, ($i - $iGetTopIndex) * $iGetItemSpacingY)
        EndIf
        _GUICtrlListView_SetItemSelected($g_idListView, $i, True, True)


      Case $idPrev
        $iStart = $g_iLvItem = - 1 ? $g_iRows - 1 : $g_iLvItem > 0 ? $g_iLvItem - 1 : $g_iRows - 1
        While 1 ; The while loop is used to start all over when first row in the listview is reached
          For $i = $iStart To 0 Step -1
            If $aSearch[$i] And $i < $g_iLvItem Then ExitLoop
          Next
          If $i > -1 Then ExitLoop
          $g_iLvItem = $g_iRows
          $iStart = $g_iRows - 1
        Wend
        If $g_iLvItem > - 1 Then _GUICtrlListView_SetItemSelected($g_idListView, $g_iLvItem, False, False)
        _GUICtrlListView_ClickItem($g_idListView, $i)
        If $iStart = 0 Then
          Local $iGetTopIndex = _GUICtrlListView_GetTopIndex($g_idListView)
          _GUICtrlListView_Scroll($g_idListView, 0, ($i - $iGetTopIndex) * $iGetItemSpacingY)
        EndIf
        _GUICtrlListView_SetItemSelected($g_idListView, $i, True, True)


      Case $g_idContextDummy
        If $g_iItem > - 1 Then _GUICtrlListView_SetSelectedColumn($g_hListView, $g_iSubItem)
        GUICtrlSetState($idContext_GetText, $g_iItem = - 1 ? $GUI_DISABLE : $GUI_ENABLE)
        Local $iMenu_Choice = _TrackPopupMenu($g_hContextMenu, $g_hGui, MouseGetPos(0), MouseGetPos(1))
        If $iMenu_Choice Then
          Switch $iMenu_Choice
          Case $idContext_GetText ; Get text
              Local $sGetText = _GetText($g_iItem, $g_iSubItem)
              MsgBox($MB_TOPMOST, "Context menu", "Row = " & $g_iItem & "   Column = " & $g_iSubItem & "   Text = " & $sGetText & @lf)

            Case $idContext_ShowAll ; Show all rows ?
              If BitAND(GUICtrlRead($idContext_ShowAll), 4) = 4 Then ; $GUI_UNCHECKED = 4
                GUICtrlSetState($idContext_ShowAll, 1) ; $GUI_CHECKED = 1
                $g_bShowAllRows = True
                $g_aSubArray = $g_aArray
                ; Set $g_tIndex + eventual update $g_aIndex[$iSortCol]
                If $iSortCol > - 1 Then _UpdateIndex($g_aIndex, $iSortCol)
                ; Update $aSearch (eventually) + automatic display of 1st match (eventually too)
                If $g_sSearch And $g_iSearch And $g_iSearch < $g_iRows Then _Update_aSearch($aSearch, $iSortCol, $idPrev, $idNext)
              Else
                GUICtrlSetState($idContext_ShowAll, 4) ; $GUI_UNCHECKED = 4
                $g_bShowAllRows = False
                $g_aSubArray = $g_aSubArray2
                ; Set $g_tIndex + eventual update of $g_aIndexTemp[$iSortCol]
                If $iSortCol > - 1 Then _UpdateIndex($g_aIndexTemp, $iSortCol)
                ; No eventual update of $aSearch because $g_bShowAllRows = False (2 buttons Prev & Next are disable in this case)
              EndIf
              GUICtrlSetState($idPrev, ($g_bShowAllRows And $g_sSearch And $g_iSearch And $g_iSearch < $g_iRows) ? $GUI_ENABLE : $GUI_DISABLE)
              GUICtrlSetState($idNext, ($g_bShowAllRows And $g_sSearch And $g_iSearch And $g_iSearch < $g_iRows) ? $GUI_ENABLE : $GUI_DISABLE)
              GUICtrlSendMsg($g_idListView, $LVM_SETITEMCOUNT, $g_bShowAllRows ? $g_iRows : $g_iSearch, 0)

            Case $idContext_RightClickHeaders ; Activate headers right-click ?
              If BitAND(GUICtrlRead($idContext_RightClickHeaders), 4) = 4 Then ; $GUI_UNCHECKED = 4
                GUICtrlSetState($idContext_RightClickHeaders, 1) ; $GUI_CHECKED = 1
                $g_bRightClickHeaders = True
              Else
                GUICtrlSetState($idContext_RightClickHeaders, 4) ; $GUI_UNCHECKED = 4
                $g_bRightClickHeaders = False
              EndIf
          EndSwitch
        Endif


      Case $GUI_EVENT_RESTORE ; needed, or Marker goes back in 0, 0 after Restore (why ?)
        _MoveMarker($g_iSearchCol)


      Case $GUI_EVENT_CLOSE
        ExitLoop
    EndSwitch
  WEnd

  ; Cleanup
  GUIDelete($g_hGui)
EndFunc   ;==>Example

;========================================================================
Func WM_DRAWITEM($hWnd, $iMsg, $wParam, $lParam)
  ; Display items in an owner drawn ListView (called once per item : no separate messages for each subitem +++)

  Local Static $tRect = DllStructCreate( $tagRECT ), $pRect = DllStructGetPtr( $tRect ), $tSize = DllStructCreate( $tagSIZE )
  Local Static $hBrushYellow = _WinAPI_CreateSolidBrush( 0xFFFF00 ), $hBrushCyan = _WinAPI_CreateSolidBrush( 0x00FFFF ) ; Yellow and cyan, BGR
  Local Static $hBrushHighLight = _WinAPI_GetSysColorBrush( $COLOR_HIGHLIGHT ), $hBrushButtonFace = _WinAPI_GetSysColorBrush( $COLOR_BTNFACE )

  Local $tDrawItem = DllStructCreate( $tagDRAWITEM, $lParam ), $itemID = DllStructGetData( $tDrawItem, "itemID" ), $iState = DllStructGetData( $tDrawItem, "itemState" ), $hDC = DllStructGetData( $tDrawItem, "hDC" ), $sItemText, $iSubItem

  If $g_bHeaderDragged Then
    $g_bHeaderDragged = False
    $g_aColumnOrder = _GUICtrlListView_GetColumnOrderArray($g_idListView) ; function inside WM_DRAWITEM (or too late to catch new order in time)
    For $iIdxHeader = 1 To $g_aColumnOrder[0]
      If $g_aColumnOrder[$iIdxHeader] = 0 Then ExitLoop ; subitem 0 always found (even if LV got 1 column only)
    Next
    Local $iSwapVal = $g_aColumnOrder[1]
    $g_aColumnOrder[1] = 0 ; eventual dragged subitem 0 index always in 1st position in loop to come => selected row background will be ok
    $g_aColumnOrder[$iIdxHeader] = $iSwapVal
    _MoveMarker(GUICtrlRead($g_idComboCol))
  EndIf

  ; Loop through columns ($i is the column index)
  ; As $LVS_EX_HEADERDRAGDROP extended style is in the script, replace here all $i with $g_aColumnOrder[$i + 1] , i.e $iSubItem
  For $i = 0 To $g_iCols - 1
    $iSubItem = $g_aColumnOrder[$i + 1] ; 1st one will be 0 (by design) to get a correct selected row background (columns not being all left aligned)

    ; Subitem rectangle
    DllStructSetData( $tRect, 2, $iSubItem ) ; Top
    DllStructSetData( $tRect, 1, $LVIR_BOUNDS ) ; Left
    GUICtrlSendMsg( $g_idListView, $LVM_GETSUBITEMRECT, $itemID, $pRect )
    ; DllStructSetData( $tRect, 1, DllStructGetData( $tRect, 1 ) + 0 ) ; Left margin

    ; If first subitem, the rectangle has been calculated for the entire listview item ($LVM_GETSUBITEMRECT)
    ; Compensate for this by setting the width of the rectangle to the width of the first column ($LVM_GETITEMRECT message and not $LVM_GETCOLUMNWIDTH ?)
    ; Before that, if item is selected, fill the entire listview item width with highlight background color (it will be too late if not done now)
    If $iSubItem = 0 Then
      If BitAND( $iState, $ODS_SELECTED ) Then DllCall( "user32.dll", "int", "FillRect", "handle", $hDC, "struct*", $tRect, "handle", $fListViewHasFocus = 1 ? $hBrushHighLight : $hBrushButtonFace ) ; _WinAPI_FillRect
      Local $aRect = _GUICtrlHeader_GetItemRect($g_hHeader, 0)
      DllStructSetData( $tRect, 1, $aRect[0]) ; left
      DllStructSetData( $tRect, 3, $aRect[2]) ; right
    EndIf

    ; Retrieve subitem text
    If $g_iSortDir = 0x0400 Then ; $HDF_SORTUP = 0x0400
      $sItemText = $g_aSubArray[$g_tIndex.arr($itemID + 1)][$iSubItem]
    Else
      $sItemText = $g_aSubArray[$g_tIndex.arr(($g_bShowAllRows ? $g_iRows : $g_iSearch) - $itemID)][$iSubItem]
    EndIf

    ; Subitem rectangle for right and center aligned columns
    If $g_aColAligns[$iSubItem] Then ; $HDF_LEFT = 0, $HDF_RIGHT = 1, $HDF_CENTER = 2
      DllCall( "gdi32.dll", "bool", "GetTextExtentPoint32W", "handle", $hDC, "wstr", $sItemText, "int", StringLen( $sItemText ), "struct*", $tSize ) ; _WinAPI_GetTextExtentPoint32
      Switch $g_aColAligns[$iSubItem]
        Case 1 ; $HDF_RIGHT
          DllStructSetData( $tRect, "Left", DllStructGetData( $tRect, "Right" ) - DllStructGetData( $tSize, "X" ) - 6 )
        Case 2 ; $HDF_CENTER
          DllStructSetData( $tRect, "Left", DllStructGetData( $tRect, "Left" ) + ( DllStructGetData( $tRect, "Right" ) - DllStructGetData( $tRect, "Left" ) - DllStructGetData( $tSize, "X" ) ) / 2 - 3 )
      EndSwitch
    EndIf

    ; Subitem text color
    DllCall( "gdi32.dll", "int", "SetTextColor", "handle", $hDC, "int", BitAND( $iState, $ODS_SELECTED ) ? $fListViewHasFocus = 1 ? 0xFFFFFF : 0x000000 : 0x000000 ) ; _WinAPI_SetTextColor

    ; Draw subitem text
    DllCall( "user32.dll", "int", "DrawTextW", "handle", $hDC, "wstr", $sItemText, "int", StringLen( $sItemText ), "struct*", $tRect, "uint", 0 ) ; _WinAPI_DrawText

    ; $iSubItem is column index, $g_iSearchCol is the search column
    ; Mark matching substring only if column index = search column
    If $iSubItem <> $g_iSearchCol Then ContinueLoop

    ; Matching substring?
    If $g_sSearch Then
      Local $sMatch = StringRegExp( $sItemText, "(?i)" & $g_sSearch, 1 )
      If Not @error Then ; match found
        Local $extended = @extended, $iLen = StringLen( $sMatch[0] )

        ; Rectangle for matching substring
        DllCall( "gdi32.dll", "bool", "GetTextExtentPoint32W", "handle", $hDC, "wstr", $sItemText, "int", $extended - $iLen - 1, "struct*", $tSize ) ; _WinAPI_GetTextExtentPoint32
        DllStructSetData( $tRect, "Left", DllStructGetData( $tRect, "Left" ) + DllStructGetData( $tSize, "X" ) )
        DllCall( "gdi32.dll", "bool", "GetTextExtentPoint32W", "handle", $hDC, "wstr", $sMatch[0], "int", $iLen, "struct*", $tSize ) ; _WinAPI_GetTextExtentPoint32
        DllStructSetData( $tRect, "Right", DllStructGetData( $tRect, "Left" ) + DllStructGetData( $tSize, "X" ) )

        ; Fill rectangle with yellow or cyan (selected) background color
        DllStructSetData( $tRect, 2, DllStructGetData( $tRect, 2 ) + 1 ) ; Top  margin
        DllCall( "user32.dll", "int", "FillRect", "handle", $hDC, "struct*", $tRect, "handle", BitAND( $iState, $ODS_SELECTED ) ? $hBrushCyan : $hBrushYellow ) ; _WinAPI_FillRect

        ; Draw matching substring in rectangle
        DllStructSetData( $tRect, 2, DllStructGetData( $tRect, 2 ) - 1 ) ; Top  margin
        DllCall( "gdi32.dll", "int", "SetTextColor", "handle", $hDC, "int", 0x000000 ) ; _WinAPI_SetTextColor
        DllCall( "user32.dll", "int", "DrawTextW", "handle", $hDC, "wstr", $sMatch[0], "int", $iLen, "struct*", $tRect, "uint", 0 ) ; _WinAPI_DrawText
      EndIf
    EndIf
  Next

  Return $GUI_RUNDEFMSG
  #forceref $hWnd, $iMsg, $wParam
EndFunc

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

  Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
  Switch HWnd(DllStructGetData($tNMHDR, "hWndFrom"))
    Case $g_hListView
      Switch DllStructGetData($tNMHDR, "Code")
        Case $LVN_ITEMCHANGED
          Local $tNMListView = DllStructCreate($tagNMLISTVIEW, $lParam)
          Local $iNewState = DllStructGetData($tNMListView, "NewState")
          If BitAND($iNewState, $LVIS_FOCUSED) Then $g_iLvItem = DllStructGetData($tNMListView, "Item")

        Case $NM_RCLICK
          Local $tInfo = DllStructCreate($tagNMITEMACTIVATE, $lParam)
          $g_iItem = DllStructGetData($tInfo, "Index")
;~        If $g_iItem > -1 Then ; valid row (no, accept also non-valid row for several reasons and check $g_iItem in context menu code)
              $g_iSubItem = DllStructGetData($tInfo, "SubItem")
              GUICtrlSendToDummy($g_idContextDummy)
;~        EndIf
      EndSwitch

    Case $g_hHeader
      Switch DllStructGetData($tNMHDR, "Code")
        Case $NM_RCLICK
          If $g_bRightClickHeaders Then
            Local $aHit = _GUICtrlListView_SubItemHitTest($g_hListView)
            ; $aHit[1] : 0-based index of the LV subitem... i.e. the column in our case (may be -1 if right click on empty part of header)
            If $aHit[1] > - 1 Then ; valid column
              GUICtrlSetData($g_idComboCol, $aHit[1])
              GUICtrlSendToDummy($g_idComboColDummy)
            EndIf
          EndIf

        Case $HDN_ENDDRAG ; related to LV extended style $LVS_EX_HEADERDRAGDROP
          $g_bHeaderDragged = True
          Return False ; allow the control to automatically place and reorder the item

        Case $HDN_BEGINTRACKW ; keep it here (older OS)
            Return True ; prevent tracking (especially when $LVS_EX_HEADERDRAGDROP + owner drawn)

        Case $HDN_ITEMCHANGINGW ; keep it here (older OS) . It takes care of $HDN_DIVIDERDBLCLICKW which is hard to prevent on older OS
            Return $g_bPreventHeaderChanges ? True : False ; True to prevent changes, False to allow them.
      EndSwitch
  EndSwitch

  Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

;========================================================================
Func WM_COMMAND($hWnd, $iMsg, $wParam, $lParam)

  Local $hWndFrom = $lParam
  Local $iCode = BitShift($wParam, 16) ; High word
  Switch $hWndFrom
    Case $g_hEdit
      Switch $iCode
        Case $EN_CHANGE
          GUICtrlSendToDummy($g_idEditDummy)
      EndSwitch
  EndSwitch
  Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_COMMAND

;========================================================================
Func _Generate_All(ByRef $g_aArray)

  ConsoleWrite("$g_iRows = " & $g_iRows & "    $g_iCols = " & $g_iCols & @CRLF)
  $g_aArray = FAS_Random2DArrayAu3($g_iRows, "sifdtr", "abcdefghijklmnopqrstuvwxyz")
  ; $g_aArray = FAS_Random2DArrayAu3($g_iRows, "sifdtr", "abcdefghijklmnopqrstuvwxyz" & "\.^$|[({*+?#)") ; 12 + 1 RegEx metacharacters
  For $i = 0 To $g_iRows - 1
    $g_tIndex.arr($i + 1) = $i
  Next
EndFunc   ;==>_Generate_All

;========================================================================
Func _SortArrayStruct(Const ByRef $aArray, $iCol, $iRows)

  Local $tIndex = DllStructCreate("uint arr[" & $iRows & "]")
  Local $pIndex = DllStructGetPtr($tIndex)
  Local Static $hDll = DllOpen("kernel32.dll")
  Local Static $hDllComp = DllOpen("shlwapi.dll")

  Local $lo, $hi, $mi, $r

  ; Sorting by one column
  For $i = 1 To $iRows - 1
    $lo = 0
    $hi = $i - 1
    Do
      $mi = Int(($lo + $hi) / 2)
      $r = DllCall($hDllComp, 'int', 'StrCmpLogicalW', 'wstr', $aArray[$i][$iCol], 'wstr', $aArray[DllStructGetData($tIndex, 1, $mi + 1)][$iCol])[0]
      Switch $r
        Case -1
          $hi = $mi - 1
        Case 1
          $lo = $mi + 1
        Case 0
          ExitLoop
      EndSwitch
    Until $lo > $hi
    DllCall($hDll, "none", "RtlMoveMemory", "struct*", $pIndex + ($mi + 1) * 4, "struct*", $pIndex + $mi * 4, "ulong_ptr", ($i - $mi) * 4)
    DllStructSetData($tIndex, 1, $i, $mi + 1 + ($lo = $mi + 1))
  Next

  Return $tIndex
EndFunc   ;==>_SortArrayStruct

;========================================================================
Func _UpdateIndex(ByRef $aIndex, $iCol)

  If VarGetType($aIndex[$iCol]) = "DLLStruct" Then
    $g_tIndex = $aIndex[$iCol]
  Else
    $g_tIndex = _SortArrayStruct($g_aSubArray, $iCol, $g_bShowAllRows ? $g_iRows : $g_iSearch)
    $aIndex[$iCol] = $g_tIndex ; "DLLStruct" (or "Int32" when no match found +++)
  EndIf
EndFunc   ;==>_UpdateIndex

;========================================================================
Func _Update_aSearch(ByRef $aSearch, $iSortCol, $idPrev, $idNext)

  $g_iSearch = 0
  Dim $aSearch[$g_iRows]
  For $i = 0 To $g_iRows - 1
    If StringRegExp($g_aArray[$g_tIndex.arr($g_iSortDir = 0x0400 ? $i + 1 : $g_iRows - $i)][$g_iSearchCol], "(?i)" & $g_sSearch) Then
      $aSearch[$i] = 1 ; include row
      $g_iSearch += 1
    EndIf
  Next

  If $g_iSearch And $iSortCol = $g_iSearchCol Then
    GUICtrlSetState($idPrev, $GUI_ENABLE)
    GUICtrlSetState($idNext, $GUI_ENABLE)
    $g_iLvItem = - 1 ; comment this line if 1st match not to be displayed on 1st row
    ControlClick($g_hGui, "", $idNext)
  EndIf
EndFunc   ;==>_Update_aSearch

;========================================================================
Func _MoveMarker($iCol)

  Local $aRect = _GUICtrlHeader_GetItemRect($g_hHeader, $iCol)
  ControlMove($g_hGui, "", $g_idMarker,  10 + $aRect[0], 40 - 3, $aRect[2] - $aRect[0] + 1, 3) ; 10 / 40 are LV coords
EndFunc   ;==>_MoveMarker

;========================================================================
Func _TrackPopupMenu($hMenu, $hWnd, $iX, $iY)

  ; $TPM_RETURNCMD (0x0100) returns the menu item identifier of the user's selection in the return value.
  Return DllCall("user32.dll", "int", "TrackPopupMenuEx", "hwnd", $hMenu, "int", 0x0100, "int", $iX, "int", $iY, "hwnd", $hWnd, "ptr", 0)[0]
EndFunc   ;==>_TrackPopupMenu

;========================================================================
Func _GetText($iItem, $iSubItem)

  Local $sGetText
  If $g_iSortDir = 0x0400 Then ; $HDF_SORTUP = 0x0400
    $sGetText = $g_aSubArray[$g_tIndex.arr($iItem + 1)][$iSubItem]
  Else
    $sGetText = $g_aSubArray[$g_tIndex.arr(($g_bShowAllRows ? $g_iRows : $g_iSearch) - $iItem)][$iSubItem]
  EndIf
  Return $sGetText
EndFunc   ;==>_GetText

;========================================================================
Func _ComputeLV_Height($iNb_Rows, $bHeader = True, $bBorder = True)

  Local $hGui = GUICreate("", 170, 200) ; test values not to be changed
  Local $idListView = GUICtrlCreateListView("Col 0", 10, 10, 150, 180) ; border $WS_EX_CLIENTEDGE

  Local $iClient_Height = WinGetClientSize(GUICtrlGetHandle($idListView))[1]
  Local $iBorder_Height = 180 - $iClient_Height

  Local $hHeader = GUICtrlSendMsg($idListView, $LVM_GETHEADER, 0, 0)
  Local $iHeader_Height = _WinAPI_GetWindowHeight($hHeader)

  Local $iGetItemSpacingY = _GUICtrlListView_GetItemSpacingY($idListView, True)

  GUIDelete($hGui)
  Return ($iNb_Rows * $iGetItemSpacingY) + ($bHeader ? $iHeader_Height : 0) + ($bBorder ? $iBorder_Height : 0)
EndFunc   ;==>_ComputeLV_Height

 

Edited by pixelsearch
replaced 3 lines 346 to 348, fingers crossed.
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...