Jump to content

Incremental search in owner and custom drawn ListViews


LarsJ
 Share

Recommended Posts

An owner drawn listview supports multi-line items through the LVS_OWNERDRAWFIXED style. FIXED means that it's only possible to set a certain height for all listview items. This common height is set during creation of the listview.

You probably want to set individual heights for each listview item. But this is simply not possible for a standard listview control available in AutoIt.

But other controls supports individual heights on a per-item basis. These are comboboxes and listboxes through the CBS_OWNERDRAWVARIABLE and LBS_OWNERDRAWVARIABLE styles, respectively. VARIABLE means that you can set individual heights on a per-item basis.

If you want to use individual item heights then the only option is to rewrite the code to support e.g. a listbox control. But it'll complicate the owner draw code for multi-line items in that you'll need multiple text rectangles in the same listbox item corresponding to each subline.

A simpler solution to the problem could be to simulate multi-line listview items using one listview item for each subline. This could be done by simply adding an additional listview column with item numbers as shown in the figure:

pzW7zPA.png

For multi-line items, the item number is only filled in for the first subline. The advantage of this approach is that all existing owner draw code can be reused. You probably still need additional code to handle the multi-line items depending on what functionality you want. But this can in all likelihood be done through an array index to keep track of these multi-line items.

I've made a note about simulating multi-line listview items in this way because it might be interesting to investigate the technique. So I might be able to help with some code. But it probably won’t be until the Easter holidays.

Link to comment
Share on other sites

12 hours ago, LarsJ said:

simpler solution to the problem could be to simulate multi-line listview items using one listview item for each subline.

Thank you for your answer. Rather, this option will not suit me. But I implemented multiline items in a different your example, but I had to abandon the LVS_OWNERDRAWFIXED style.

Link to comment
Share on other sites

  • 1 month later...
On 5/8/2021 at 7:52 AM, Lion66 said:

...Inspired by your examples, I thought it was possible to connect one of your versions, including sorting, with marking the resulting yellow color, as in this thread [link to the current thread]

If you find it possible to please us with the solution of this task, it would be interesting to see such a modification.
Thank you.

Hi Lion66 :)
With the help of LarsJ's function WM_DRAWITEM in this post, I could combine my last script ("2h") and his function WM_DRAWITEM to display partially colored items (incremental search) in an owner drawn ListView. Here is what it looks like :

OwnerDraw.png.42fb660b1935c488a0afeb218553a28e.png

Changing the search column can be done through the combo control or via a right-click on a column header. Columns sorting indexes on full array are created when user left-click a column header, then they are preserved until the script ends. Temporary column indexes on sub array (i.e corresponding to one search) are created the same way (when user left-click on a column header) and they are preserved until the next search etc... all this already existed in preceding version "2h"

The script seems to work though some parts are missing (no hook and no Func WM_ACTIVATE, $fListViewHasFocus forced to 1) . Even with these missing parts, it worked for me without any crash.

Concerning your "script crash" dated July 2019 in this post (of the present thread) I hope you found the solution as you needed the non-sensitive mode. We'll talk about it if it's not solved as it worked for me after adding "(?i)" in both RegEx parts (you will see that in the script below too)

Meanwhile, could you please try this script "2i" and let's hope all will be good.

#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_iLeftLV = 10, $g_iTopLV = 40, $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_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 + Sort + Incremental search + Color (2i)", 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) + 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("", $g_iLeftLV, $g_iTopLV, 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])
  Next

  ; 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 = 0x0400 ; $HDF_SORTUP
  Local $iSortCol = -1, $iSortColPrev = -1

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

  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
        $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)
        $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)
          For $i = 0 To $g_iRows - 1
            ; If StringRegExp($g_aArray[$i][$g_iSearchCol], $g_sSearch) Then
            ; https://www.autoitscript.com/forum/topic/179112-incremental-search-in-owner-drawn-listview/?tab=comments#comment-1438067
            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
          ; 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"))
        ;------
        ; _GUICtrlListView_SetSelectedColumn($g_hListView, $g_iSearchCol) ; depending on search, crashes script when placed here
        ; _GUICtrlListView_SetSelectedColumn($g_idListView, $g_iSearchCol) ; strange behavior here
        ; GUICtrlSendMsg($g_idListView, $LVM_SETSELECTEDCOLUMN, $g_iSearchCol, 0) ; strange behavior here
        ;------
        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)
        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_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 ) + 6 ) ; Left margin
    DllStructSetData( $tRect, 2, DllStructGetData( $tRect, 2 ) + 2 ) ; Top  margin

    ; Subitem background and text color
    If BitAND( $iState, $ODS_SELECTED ) Then DllCall( "user32.dll", "int", "FillRect", "handle", $hDC, "struct*", $tRect, "handle", $fListViewHasFocus = 1 ? $hBrushHighLight : $hBrushButtonFace ) ; _WinAPI_FillRect
    DllCall( "gdi32.dll", "int", "SetTextColor", "handle", $hDC, "int", BitAND( $iState, $ODS_SELECTED ) ? $fListViewHasFocus = 1 ? 0xFFFFFF : 0x000000 : 0x000000 ) ; _WinAPI_SetTextColor

    ; Draw 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
    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, $g_sSearch, 1 ), $extended = @extended, $iLen = StringLen( $sMatch[0] )
      ; https://www.autoitscript.com/forum/topic/179112-incremental-search-in-owner-drawn-listview/?tab=comments#comment-1438067
      Local $sMatch = StringRegExp( $sItemText, "(?i)" & $g_sSearch, 1 ), $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
      DllCall( "user32.dll", "int", "FillRect", "handle", $hDC, "struct*", $tRect, "handle", BitAND( $iState, $ODS_SELECTED ) ? $hBrushCyan : $hBrushYellow ) ; _WinAPI_FillRect

      ; Draw matching substring in rectangle
      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
  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 $HDN_ENDTRACKW, $HDN_DIVIDERDBLCLICKW ; let's forget $HDN_TRACKW...
        ; _MoveMarker(GUICtrlRead($g_idComboCol))

        Case $HDN_ENDTRACKW
          _MoveMarker(GUICtrlRead($g_idComboCol))

        Case $HDN_DIVIDERDBLCLICKW ; notif. doesn't do its job when LV style is $LVS_OWNERDATA and $LVN_GETDISPINFOW not in code (?)
          Local $iCol = DllStructGetData($tNMHEADER, "Item")
          _GUICtrlListView_SetColumnWidth($g_idListView, $iCol, $g_aWidths[$iCol]) ; initial size
          _MoveMarker(GUICtrlRead($g_idComboCol))

        Case $NM_RCLICK
          Local $aHit = _GUICtrlListView_SubItemHitTest($g_hListView) ; $iCol won't do it (msdn) , $g_idListView won't work
          ; $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)

  Local $aRect = _GUICtrlHeader_GetItemRect($g_hHeader, $iCol)
  ControlMove($g_hGui, "", $g_idMarker, $g_iLeftLV + $aRect[0], $g_iTopLV - 3, $aRect[2] - $aRect[0] + 1, 3)
EndFunc   ;==>_MoveMarker

I attach below 2 include files from LarsJ, they're needed in the script.

RandomArray.au3DrawItem.au3

 

Edit 1 : I notice some display issues in the script above but LarsJ got the same ones in his example "2) Show matching rows only" in zip file (1st post), or in his separate example "2.2) Show matching rows only - multiple columns [Oct 3, 2019]" in another post of this thread.

For example, double clicking on a column divider (when the search field is empty and the column is filled) ... will reduce the column size to nearly 0, then incremental search will overlap on an eventual column on its right :

339397288_Displayissues.png.43ff08b15c45fbed2b94054ceba49c99.png

I don't know why it doesn't happen in his example "1) Incremental text search" where a double click on the column divider will correctly adjust the column width. LarsJ will surely have the correct answer :)

Edit 2 (a few hours after) : I just digged a bit into these different codes. This bad behavior seems to happen when the LV got a $LVS_OWNERDATA style but $LVN_GETDISPINFOW isn't used in the script. Then a double click on any header separator reduces immediately the column width to nearly 0 . It's just an hypothesis, we'll see if it's confirmed or refuted.

The script above has just been updated to prevent this bad behavior of $HDN_DIVIDERDBLCLICKW (i.e. after a double-click on a header separator, the column will now retrieve its original size, which is better than a 0 width column)

Edit 3 : Update May 26th, 2021
Fixed the search issue discussed in this post

Edited by pixelsearch
Update May 26th, 2021
Link to comment
Share on other sites

On 5/9/2021 at 10:35 AM, pixelsearch said:

I notice some display issues in the script above but LarsJ got the same ones in his example "2) Show matching rows only" in zip file (1st post), or in his separate example "2.2) Show matching rows only - multiple columns [Oct 3, 2019]" in another post of this thread.

For example, double clicking on a column divider (when the search field is empty and the column is filled) ... will reduce the column size to nearly 0, then incremental search will overlap on an eventual column on its right

In these 3 scripts, the listviews are created with the LVS_OWNERDATA style, making them true virtual listviews where no item/subitem texts are stored in the listview structures. There is simply no information to calculate the column width at an HDN_DIVIDERDBLCLICK notification.

 

On 5/9/2021 at 10:35 AM, pixelsearch said:

I don't know why it doesn't happen in his example "1) Incremental text search" where a double click on the column divider will correctly adjust the column width.

In this script without the LVS_OWNERDATA style, an owner drawn listview will store information about item/subitem texts in the listview structures so that it's possible to calculate the column width at an HDN_DIVIDERDBLCLICK notification.

Link to comment
Share on other sites

9 minutes ago, LarsJ said:

In these 3 scripts, the listviews are created with the LVS_OWNERDATA style, making them true virtual listviews where no item/subitem texts are stored in the listview structures. There is simply no information to calculate the column width at an HDN_DIVIDERDBLCLICK notification.

Thanks for the confirmation. I would like to add that even when the LV is created with a LVS_OWNERDATA style (virtual LV), then a HDN_DIVIDERDBLCLICK notification will retrieve correct informations about the column width if LVN_GETDISPINFOW is used to set the text. In this case, a double click on a header separator will behave nicely again (this is the case in my preceding script "2h")

On 12/7/2015 at 5:33 PM, LarsJ said:

(A custom drawn listview supports coloring of fields. However, the coloring works best for whole fields. This doesn't make it applicable to incremental search.)

I would like to ask. When you write that "the custom drawn [NM_CUSTOMDRAW] works best for whole fields", you don't definitely say that it doesn't work at all for partial fields. Do you think it would be possible to try it on an incremental search, or there is absolutely no way that it will work ?

Thanks.

Edited by pixelsearch
Link to comment
Share on other sites

Hi pixelsearch.

You as always on top!
Thank you for puzzled and answered.
I have not had time yet to see the code.

I'm wondering to dig and learn. But I fear it to use it in my scripts, because I do not understand much, and if necessary, i cant  to make changes.
The solution with the insensitive search mode present is also in your examples and work good.
The only thing I noticed in version 2i:

when you click on the separator between the first two columns, in the second column, the partial date of the first is displayed on the left.

 

Edited by Lion66
Link to comment
Share on other sites

5 hours ago, Lion66 said:

You as always on top!
...
The only thing I noticed in version 2i: when you click on the separator between the first two columns, in the second column, the partial data of the first is displayed on the left.

Hi Lion66 :)
Really the one "on top" is LarsJ.
Without his persuasion, detailed explanations and incredible examples, I would never have tried any Virtual listview. Now I like them !

Concerning version "2i" above and its overlapping data from 1 column in the other (when you resize columns), I noticed it too but I got no idea how to fix this (some refresh works but partially)

Is there a way to fix this when the items are displayed via WM_DRAWITEM in an owner drawn Virtual ListView ?
Let's hope anyone who reads this will have the answer to that question.

5 hours ago, Lion66 said:

I'm wondering to dig and learn. But I fear it to use it in my scripts, because I do not understand much, and if necessary, i cant  to make changes.

There's nothing to be afraid of. Just try a simple script with a single column (at least nothing will overlap) a few rows and no sorting. Maybe the non-coloured script "2h" would be a good choice too (as it doesn't use the complicated WM_DRAWITEM notification) . When you're confident with the result, then you can add some functionalities. And if you find problems in your script, there always will be nice people on this Forum to help, after you show them what you scripted.

Good luck :)

Link to comment
Share on other sites

6 hours ago, pixelsearch said:

Really the one "on top" is LarsJ.
Without his persuasion, detailed explanations and incredible examples, I would never have tried any Virtual listview. Now I like them !

I agree 100%, it was his absorbing tutorials that encouraged me to dive in & implement them successfully :graduated:

Link to comment
Share on other sites

On 5/11/2021 at 6:59 PM, pixelsearch said:

When you write that "the custom drawn [NM_CUSTOMDRAW] works best for whole fields", you don't definitely say that it doesn't work at all for partial fields. Do you think it would be possible to try it on an incremental search, or there is absolutely no way that it will work ?

It can probably easily be done. But because custom draw code is extremely message intensive, you really need to pay attention to the performance of your code.

The technique is basically the same as for the owner draw code. Also in the custom draw code you use GDI functions to draw the different visual elements.

When using GDI functions in custom draw code, the functions must be performed in the postpaint drawing stage i.e. after all default drawing is done. I think the code in the "Too much code" section here contains all the information you need.

 

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.

Link to comment
Share on other sites

22 hours ago, LarsJ said:

by adding the HDS_NOSIZING style to the header control.

I watched examples about HDS_NOSIZING in this topic.
As I understand it, a daughter window with a new header is superimposed on the header of Listview.
But then for the sort function I need to handle this new header in the WM_Notify.
Or I can somehow add a HDS_NOSIZING style to the header of the existing ListView?

Link to comment
Share on other sites

Add this code and you'll see that everything works normally except column resizing:

; No listview column resizing by dragging header dividers
Local $hHeader = _GUICtrlListView_GetHeader( $idListView )
;_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", $hHeader, "int", $GWL_STYLE, "long_ptr", _
DllCall( "user32.dll", "long_ptr", @AutoItX64 ? "GetWindowLongPtrW" : "GetWindowLongW", "hwnd", $hHeader, "int", $GWL_STYLE )[0] + $HDS_NOSIZING )

 

Link to comment
Share on other sites

Hi LarsJ :)
I tried to use the Custom draw for incremental search but it didn't work fine, could you please help ?

The only thing that "works" in the script below (named "2k") is this :
* Please type  .  or  .$  in the Search field, then hover over rows and you'll see the correct matching 1st (or last) character with cyan background in Col 0. Nothing appears if the rows aren't hovered over.

* Then change the search column (right-click on a new header or use the Combo) and the cyan background never appears in any searched column (it should appear on 1st or last character, no matter the searched column, as RegEx search is "." or ".$")

I checked the 4 values of the rectangle etc... and added some commented ConsoleWrite's during Case $NM_CUSTOMDRAW. Values seem correct, maybe hdc isn't correct when applied to Cols 1+ ?

I didn't use at all "clrTextBk" or "clrText". Their value is constantly 0xFF000000 ($CLR_DEFAULT) and if we're able to see a cyan background on matched characters while hovering over Col 0, then it should be ok. Also, as soon as I tried to use "clrTextBk" during a test, then the whole subitem got its background colored.

Finally, as the Owner draw script ("2i") worked fine in a precedent post , then we don't really need the Custom draw way (especially it's "extremely message intensive" as you indicated) , but I was just curious to see if it was doable or not.

Thanks if you got any idea on how to make Case $NM_CUSTOMDRAW work correctly in the script :

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

Opt("MustDeclareVars", 1)

Global $g_iRows = 1000, $g_iCols = 6, $g_iLeftLV = 10, $g_iTopLV = 40, $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_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"

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 + Sort + Incremental search + CustomDraw (2k)", 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) + 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("", $g_iLeftLV, $g_iTopLV, 630, 788, BitOr($GUI_SS_DEFAULT_LISTVIEW, $LVS_OWNERDATA), $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])
  Next

  ; 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 = 0x0400 ; $HDF_SORTUP
  Local $iSortCol = -1, $iSortColPrev = -1

  GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")
  GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")

  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
        $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)
        $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 = "RegEx search" Then
            For $i = 0 To $g_iRows - 1 ; duplicated For... Next for speed
              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
          Else ; "Normal search"
            For $i = 0 To $g_iRows - 1 ; duplicated For... Next for speed
              If StringInStr($g_aArray[$i][$g_iSearchCol], $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"))
        ;------
        ; _GUICtrlListView_SetSelectedColumn($g_hListView, $g_iSearchCol) ; depending on search, crashes script when placed here
        ; _GUICtrlListView_SetSelectedColumn($g_idListView, $g_iSearchCol) ; strange behavior here
        ; GUICtrlSendMsg($g_idListView, $LVM_SETSELECTEDCOLUMN, $g_iSearchCol, 0) ; strange behavior here
        ;------
        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)
        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_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_NOTIFY($hWnd, $iMsg, $wParam, $lParam)

  Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
  Switch HWnd(DllStructGetData($tNMHDR, "hWndFrom"))

    Case $g_hListView
      Switch DllStructGetData($tNMHDR, "Code")
        Case $NM_CUSTOMDRAW
          Local $tCustDraw = DllStructCreate($tagNMLVCUSTOMDRAW, $lParam)
          Local $iDrawStage = DllStructGetData($tCustDraw, "dwDrawStage")
          If $iDrawStage = $CDDS_PREPAINT Then Return $CDRF_NOTIFYITEMDRAW
          If $iDrawStage = $CDDS_ITEMPREPAINT Then Return $CDRF_NOTIFYSUBITEMDRAW
          Local $iItem = DllStructGetData($tCustDraw, "dwItemSpec")
          Local $iSubItem = DllStructGetData($tCustDraw, "iSubItem")
;~        ConsoleWrite("0x" & Hex($tCustDraw.clrText, 8) & "   0x" & Hex($tCustDraw.clrTextBk, 8) & @lf) ; both 0xFF000000 ($CLR_DEFAULT)

          If ($iSubItem = $g_iSearchCol) And $g_sSearch Then
            Local Static $tRect = DllStructCreate( $tagRECT ), $pRect = DllStructGetPtr( $tRect ), $tSize = DllStructCreate( $tagSIZE )
            Local Static $hBrushYellow = _WinAPI_CreateSolidBrush( 0x00FFFF ), $hBrushCyan = _WinAPI_CreateSolidBrush( 0xFFFF00 ) ; Yellow and cyan, BGR

            Local $hDC = DllStructGetData( $tCustDraw, "hDC" )
;~          ConsoleWrite("$hDC = " & $hDC & @lf)

            If $g_iSortDir = 0x0400 Then ; 0x0400 = $HDF_SORTUP
              Local $sItemText = $g_aSubArray[$g_tIndex.arr($iItem + 1)][$iSubItem]
            Else
              Local $sItemText = $g_aSubArray[$g_tIndex.arr($g_iSearch - $iItem)][$iSubItem]
            EndIf
;~          ConsoleWrite("$sItemText = " & $sItemText & @lf)

            Local $sMatch = StringRegExp( $sItemText, "(?i)" & $g_sSearch, 1 ), $extended = @extended, $iLen = StringLen( $sMatch[0] )

            ; Subitem rectangle
            DllStructSetData( $tRect, 2, $g_iSearchCol ) ; Top
            DllStructSetData( $tRect, 1, $LVIR_BOUNDS ) ; Left
            GUICtrlSendMsg( $g_idListView, $LVM_GETSUBITEMRECT, $iItem, $pRect )
            DllStructSetData( $tRect, 1, DllStructGetData( $tRect, 1 ) + 6 ) ; Left margin
;~          DllStructSetData( $tRect, 2, DllStructGetData( $tRect, 2 ) + 2 ) ; Top  margin
;~          ConsoleWrite("Top = " & $tRect.Top & "   " & "Bottom = " & $tRect.Bottom & "   " & _
;~            "Left = " & $tRect.Left & "   " & "Right = " & $tRect.Right & @lf)

            ; 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" ) )
;~          ConsoleWrite("Top = " & $tRect.Top & "   " & "Bottom = " & $tRect.Bottom & "   " & _
;~            "Left = " & $tRect.Left & "   " & "Right = " & $tRect.Right & @lf)

            ; Fill rectangle with cyan background color
            DllCall( "user32.dll", "int", "FillRect", "handle", $hDC, "struct*", $tRect, "handle", $hBrushCyan) ; _WinAPI_FillRect

            ; Draw matching substring in rectangle
            ; DllCall( "gdi32.dll", "int", "SetTextColor", "handle", $hDC, "int", 0x000000 ) ; _WinAPI_SetTextColor
            DllCall( "gdi32.dll", "int", "SetBkColor", "handle", $hDC, "int", 0xFFFF00 ) ; _WinAPI_SetBkColor (cyan)
            DllCall( "user32.dll", "int", "DrawTextW", "handle", $hDC, "wstr", $sMatch[0], "int", $iLen, "struct*", $tRect, "uint", 0 ) ; _WinAPI_DrawText
          EndIf
          Return $CDRF_NEWFONT

        Case $LVN_GETDISPINFOW
          Local $tNMLVDISPINFO = DllStructCreate($tagNMLVDISPINFO, $lParam)
          Local Static $tText = DllStructCreate("wchar[50]"), $pText = DllStructGetPtr($tText)
          If $g_iSortDir = 0x0400 Then ; 0x0400 = $HDF_SORTUP
            DllStructSetData($tText, 1, $g_aSubArray[$g_tIndex.arr($tNMLVDISPINFO.Item + 1)][$tNMLVDISPINFO.SubItem])
          Else
            DllStructSetData($tText, 1, $g_aSubArray[$g_tIndex.arr($g_iSearch - $tNMLVDISPINFO.Item)][$tNMLVDISPINFO.SubItem])
          EndIf
          DllStructSetData($tNMLVDISPINFO, "Text", $pText)
      EndSwitch

    Case $g_hHeader
      Switch DllStructGetData($tNMHDR, "Code")
        Case $HDN_ENDTRACKW, $HDN_DIVIDERDBLCLICKW ; let's forget $HDN_TRACKW...
          _MoveMarker(GUICtrlRead($g_idComboCol))

        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")
  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)

  Local $aRect = _GUICtrlHeader_GetItemRect($g_hHeader, $iCol)
  ControlMove($g_hGui, "", $g_idMarker, $g_iLeftLV + $aRect[0], $g_iTopLV - 3, $aRect[2] - $aRect[0] + 1, 3)
EndFunc   ;==>_MoveMarker

 

Edited by pixelsearch
Link to comment
Share on other sites

I noticed that when using the $LVS_OWNERDRAWFIXED style, the standard functions stop working.

Such as the:

_GUICtrlListView_JustifyColumn

_GUICtrlListView_GetItemText

Possibly a finished version "2k" will solve that.

Or how to solve this in early version "2i" ?

Link to comment
Share on other sites

Hi Lion66 :)
* Concerning _GUICtrlListView_GetItemText (LVM_GETITEMTEXT message) it doesn't work in a Virtual Listview (LVS_OWNERDATA style) as stipulated in this MS link (just search LVM_GETITEMTEXT in the web page, it will lead you to 4 chapters concerning Virtual Listview, all interesting to read) 

Edit #1 : _GUICtrlListView_GetItemText seems to work fine if LVN_GETDISPINFO is used (just tested on "2h")

* Concerning _GUICtrlListView_JustifyColumn (LVM_SETCOLUMN message) I think you're right. It works fine when the Virtual ListView is populated via LVN_GETDISPINFO, but when it's populated via WM_DRAWITEM (when LV got the LVS_OWNERDRAWFIXED style) then it doesn't work because coords applied by user's code in WM_DRAWITEM are all related to a left string justification.

Here is a silly test to show what could work. In version "2i", you can add 1 line to center headers :

For $i = 0 To $g_iCols - 1
    _GUICtrlListView_AddColumn($g_idListView, $g_aCols[$i], $g_aWidths[$i])
    _GUICtrlListView_JustifyColumn($g_idListView, $i, 2) ; <======= added line
Next

Then you can center all items (starting from col 1 in the example below, by modifying 1 line :

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

changed to :
DllCall( "user32.dll", "int", "DrawTextW", "handle", $hDC, "wstr", $sItemText, "int", StringLen( $sItemText ), "struct*", $tRect, "uint", $i > 0 ? $DT_CENTER : 0) ; _WinAPI_DrawText

centered.png.02ba14d25f70d5908ae6f5b6905d4cf1.png

Unfortunately it's not enough : what looks great in the precedent pic (all cols centered starting from col 1+) will display bad coords when search (a "." in search field) is applied to other columns, for example :

335451835_badcoords.png.a1b54b5768b4ac39eee1d3f8a6960fb1.png

Maybe if we knew how to change the matched rectangle coords, then center justification could be done when using WM_DRAWITEM ?

Edit #2 : @Lion66
To retrieve the text of any subitem in a virtual listview having LVS_OWNERDRAWFIXED style, you need to retrieve it from the array that populated the listview.
As you can't use _GUICtrlListView_GetItemText, then try this code in "2i", it will give you an idea on how to retrieve subitem texts in your script. This code is just an example and has to be added inside the While... Wend Loop . Then right click on an item, it will be selected and retrieve the text of column 0 (because of column [0] in code) no matter the columns are sorted or not.

Case $GUI_EVENT_SECONDARYDOWN
    Local $iSelected = GUICtrlSendMsg($g_idListView, $LVM_GETNEXTITEM, -1, $LVNI_SELECTED)
    If $iSelected  > - 1 Then
        If $g_iSortDir = 0x0400 Then ; 0x0400 = $HDF_SORTUP
            Local $sGetText = $g_aSubArray[$g_tIndex.arr($iSelected + 1)][0]
        Else
            Local $sGetText = $g_aSubArray[$g_tIndex.arr($g_iSearch - $iSelected)][0]
        EndIf
        MsgBox($MB_TOPMOST, "Item = " & $iSelected, "Text col 0 = " & $sGetText)
    EndIf

It's easy to create a function for this, so it's reusable anywhere in the script :

Case $GUI_EVENT_SECONDARYDOWN
        Local $iSelected = GUICtrlSendMsg($g_idListView, $LVM_GETNEXTITEM, -1, $LVNI_SELECTED)
        If $iSelected  > - 1 Then ; an item is selected
          Local $sGetText = _GetText($iSelected, 0)
          MsgBox($MB_TOPMost, "Item = " & $iSelected, "Text col 0 = " & $sGetText)
        EndIf
...

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

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

It's great that global variables all start with  $g_  in the script, so it's quicker to prepare a function (we don't spend time on checking  $g_  variables to add them as parameters)

Edited by pixelsearch
Added Edit #1 and Edit #2
Link to comment
Share on other sites

The second question is solved, the first is not.
It's Complicated. Tomorrow will need something else, and again It will be necessary ask for help on the forum.
I hope that you will be able to solve the version "2k", and there will be no such problems in it.

Link to comment
Share on other sites

Virtual listview

On 5/18/2021 at 5:54 PM, pixelsearch said:

I was just curious to see if it was doable or not.

It's certainly doable:

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

Opt("MustDeclareVars", 1)

Global $g_iRows = 1000, $g_iCols = 6, $g_iLeftLV = 10, $g_iTopLV = 40, $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_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"

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 + Sort + Incremental search + CustomDraw (2k)", 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) + 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("", $g_iLeftLV, $g_iTopLV, 630, 788, BitOr($GUI_SS_DEFAULT_LISTVIEW, $LVS_OWNERDATA), $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])
  Next

  ; 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 = 0x0400 ; $HDF_SORTUP
  Local $iSortCol = -1, $iSortColPrev = -1

  GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")
  GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")

  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
        $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)
        $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 = "RegEx search" Then
            For $i = 0 To $g_iRows - 1 ; duplicated For... Next for speed
              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
          Else ; "Normal search"
            For $i = 0 To $g_iRows - 1 ; duplicated For... Next for speed
              If StringInStr($g_aArray[$i][$g_iSearchCol], $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"))
        ;------
        ; _GUICtrlListView_SetSelectedColumn($g_hListView, $g_iSearchCol) ; depending on search, crashes script when placed here
        ; _GUICtrlListView_SetSelectedColumn($g_idListView, $g_iSearchCol) ; strange behavior here
        ; GUICtrlSendMsg($g_idListView, $LVM_SETSELECTEDCOLUMN, $g_iSearchCol, 0) ; strange behavior here
        ;------
        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)
        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_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_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
  Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)

  Switch HWnd(DllStructGetData($tNMHDR, "hWndFrom"))
    Case $g_hListView
      Switch DllStructGetData($tNMHDR, "Code")
        Case $NM_CUSTOMDRAW
          Local $tCustDraw = DllStructCreate($tagNMLVCUSTOMDRAW, $lParam)
          Switch DllStructGetData($tCustDraw, "dwDrawStage") ; Holds a value that specifies the drawing stage
            Case $CDDS_PREPAINT                      ; Before the paint cycle begins
              Return $CDRF_NOTIFYITEMDRAW            ; Notify the parent window of any item-related drawing operations
            Case $CDDS_ITEMPREPAINT                  ; Before painting an item
              Return $CDRF_NOTIFYSUBITEMDRAW         ; Notify the parent window of any subitem-related drawing operations
            Case $CDDS_ITEMPREPAINT + $CDDS_SUBITEM  ; Before painting a subitem
              If (DllStructGetData($tCustDraw, "iSubItem") = $g_iSearchCol) And $g_sSearch Then Return $CDRF_NOTIFYPOSTPAINT ; If $g_sSearch then paint $g_iSearchCol in postpaint stage
              Return $CDRF_NEWFONT                   ; $CDRF_NEWFONT must be returned after changing font or colors          ; Paint all other columns in prepaint stage with default code
            Case $CDDS_ITEMPOSTPAINT + $CDDS_SUBITEM ; After painting a subitem                                              ; Paints $g_iSearchCol ($g_iSearchCol only) if $g_sSearch
              Local Static $tRect = DllStructCreate( $tagRECT ), $pRect = DllStructGetPtr( $tRect ), $tSize = DllStructCreate( $tagSIZE )
              Local Static $hBrushYellow = _WinAPI_CreateSolidBrush( 0x00FFFF ), $hBrushCyan = _WinAPI_CreateSolidBrush( 0xFFFF00 ) ; Yellow and cyan, BGR
              Local $iItem = DllStructGetData($tCustDraw, "dwItemSpec"), $hDC = DllStructGetData( $tCustDraw, "hDC" )

              ; Subitem text and matching text
              Local $sSubItemText = $g_iSortDir = 0x0400 ? $g_aSubArray[$g_tIndex.arr($iItem + 1)][$g_iSearchCol] _ ; 0x0400 = $HDF_SORTUP
                                                         : $g_aSubArray[$g_tIndex.arr($g_iSearch - $iItem)][$g_iSearchCol]
              Local $sMatch = StringRegExp( $sSubItemText, "(?i)" & $g_sSearch, 1 ), $extended = @extended, $iLen = StringLen( $sMatch[0] )

              ; Entire subitem rectangle
              DllStructSetData( $tRect, 2, $g_iSearchCol ) ; Top
              DllStructSetData( $tRect, 1, $LVIR_BOUNDS )  ; Left
              GUICtrlSendMsg( $g_idListView, $LVM_GETSUBITEMRECT, $iItem, $pRect )
              DllStructSetData( $tRect, 1, DllStructGetData( $tRect, 1 ) + ( $g_iSearchCol ? 6 : 3 ) ) ; Left margin

              ; Set transparent background for the subitem
              DllCall( "gdi32.dll", "int", "SetBkMode", "handle", $hDC, "int", $TRANSPARENT ) ; _WinAPI_SetBkMode

              ; Draw entire subitem text
              DllStructSetData( $tRect, 2, DllStructGetData( $tRect, 2 ) + 2 ) ; Top  margin
              DllCall( "user32.dll", "int", "DrawTextW", "handle", $hDC, "wstr", $sSubItemText, "int", StringLen( $sSubItemText ), "struct*", $tRect, "uint", 0 ) ; _WinAPI_DrawText
              DllStructSetData( $tRect, 2, DllStructGetData( $tRect, 2 ) - 2 ) ; Top  margin

              ; Rectangle for matching substring
              DllCall( "gdi32.dll", "bool", "GetTextExtentPoint32W", "handle", $hDC, "wstr", $sSubItemText, "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 matching rectangle with yellow or cyan background color
              DllCall( "user32.dll", "int", "FillRect", "handle", $hDC, "struct*", $tRect, "handle", GUICtrlSendMsg( $g_idListView, $LVM_GETITEMSTATE, $iItem, $LVIS_SELECTED ) ? $hBrushYellow : $hBrushCyan ) ; _WinAPI_FillRect

              ; Redraw matching text on the yellow or cyan background
              DllStructSetData( $tRect, 2, DllStructGetData( $tRect, 2 ) + 2 ) ; 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
              Return $CDRF_NEWFONT                   ; $CDRF_NEWFONT must be returned after changing font or colors
          EndSwitch

        Case $LVN_GETDISPINFOW
          Local $tNMLVDISPINFO = DllStructCreate($tagNMLVDISPINFO, $lParam)
          If ($tNMLVDISPINFO.SubItem = $g_iSearchCol) And $g_sSearch Then Return ; Don't paint $g_iSearchCol if $g_sSearch
          Local Static $tText = DllStructCreate("wchar[50]"), $pText = DllStructGetPtr($tText)
          Local $s = $g_iSortDir = 0x0400 ? DllStructSetData($tText, 1, $g_aSubArray[$g_tIndex.arr($tNMLVDISPINFO.Item + 1)][$tNMLVDISPINFO.SubItem]) _ ; 0x0400 = $HDF_SORTUP
                                          : DllStructSetData($tText, 1, $g_aSubArray[$g_tIndex.arr($g_iSearch - $tNMLVDISPINFO.Item)][$tNMLVDISPINFO.SubItem])
          DllStructSetData($tNMLVDISPINFO, "Text", $pText)
      EndSwitch

    Case $g_hHeader
      Switch DllStructGetData($tNMHDR, "Code")
        Case $HDN_ENDTRACKW, $HDN_DIVIDERDBLCLICKW ; let's forget $HDN_TRACKW...
          _MoveMarker(GUICtrlRead($g_idComboCol))

        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")
  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)

  Local $aRect = _GUICtrlHeader_GetItemRect($g_hHeader, $iCol)
  ControlMove($g_hGui, "", $g_idMarker, $g_iLeftLV + $aRect[0], $g_iTopLV - 3, $aRect[2] - $aRect[0] + 1, 3)
EndFunc   ;==>_MoveMarker

And the custom draw code is much more effective than the owner draw code. In the custom draw code, only the column provided with a search filter is drawn with GDI functions. Otherwise, all columns are drawn with default code in the Windows dll-files at the speed of real compiled code. This also applies when there is no search filter.

In the owner draw code, all columns are drawn with GDI functions. It ends up being quite a lot of code that's executed as pure interpreted AutoIt code.

In an example like this, it's a really good idea to use custom draw code.

The code has been tested on Windows 7. From Windows 7 and newer Windows versions, the subitem rectangles appear to be implemented in a consistent manner. But this isn't the case on older Windows versions. Eg. on Windows XP, the size and location of subitem rectangles may vary by a few pixels. An example is seen in Multi-line ListView items (search for Windows XP).

Custom draw documentation

Column alignment

Edited by LarsJ
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...