Jump to content

Incremental search in owner and custom drawn ListViews


LarsJ
 Share

Recommended Posts

  • 2 weeks later...

Multi column search
In this example a ListView Combo control is used to define search filters. The main ListView is a custom drawn and virtual ListView. Searching and sorting is based on array indexes (1d arrays of integers).

Q7SLYKn.png

 

ListView Combo
In the ListView Combo control in the image, a search is defined for rows that matches search filters in 3 columns: 2 x's in the Strings column, dates in the range 2015 - 2019 in the Dates column, and 2 consecutive 5 digits in the Floats column.

Note that the rows in the ListView Combo corresponds to the columns in $g_aArray and in the main ListView.

There are 5 columns in the ListView Combo:

  • Col is the column index in $g_aArray/main ListView
    The Checkbox is used to enable/disable a search filter
  • Column is the column name in the main ListView
  • Ord is the order of the search filter. The order
    is filled in automatically when a search filter
    is enabled and a search string is entered. The
    order is cleared on a disabled search filter or
    an empty search string.
  • RegEx indicates RegEx or Normal search
    Click the Checkbox to switch
  • The Search string column contains search strings
    that are entered through an Edit control. Click
    to open the Edit control. Move the mouse out of
    the Edit control to close it.

Note the order of the 3 search filters in the image. The Strings column with order 0 is the first and most significant search filter. The Dates column with order 1 is the next search filter. The Floats column with order 2 is the last and least significant search filter.

Rule: For search filters provided with an order, you can only change the least significant search filter (with the highest order).

If you want to change the order of the Strings and Floats search filters so that Floats gets order 0 and Strings order 2, you must first disable all 3 search filters in this order: Floats, Dates, Strings. Click the Checkbox in first column to disable the search filters. Then you have to enable the 3 search filters again in this order: Floats, Dates, Strings.

Performance considerations are the reason for this rule. Because the search is an incremental search, it's expected to be very dynamic and responsive. There must be no delays if you e.g. adds or removes a character in a search string. Therefore, it's important that only the least significant search filter can be changed. If only the least significant search filter can be changed, only one search and one sort must be recalculated.

If it was possible to update the search string in the most significant search filter then both the search and the sort would have to be recalculated for all search filters. In this example with 6 columns in $g_aArray and main ListView, 6 search filters are possible. If all 6 search filters had to be recalculated for both the search and the sort, then it would certainly cause a significant delay.

Use the Reset button to completely reset all search filters.
 

Main ListView
The main ListView uses two different WM_NOTIFY message handlers: WM_NOTIFY_All is used to display all rows in $g_aArray when no search filter is applied. WM_NOTIFY_Search is used to display the matching rows in $g_aArray when one or more search filters are applied.

LVN_GETDISPINFO code in WM_NOTIFY_All:

Case $LVN_GETDISPINFOW
  Local $tNMLVDISPINFO = DllStructCreate( $tagNMLVDISPINFO, $lParam )
  Local Static $tText = DllStructCreate( "wchar Text[50]" ), $pText = DllStructGetPtr( $tText )
  $tText.Text = $g_aArray[$g_aIndex[($g_iSortDir=$HDF_SORTUP?$tNMLVDISPINFO.Item:$g_iRows-1-$tNMLVDISPINFO.Item)]][$tNMLVDISPINFO.SubItem]
  $tNMLVDISPINFO.Text = $pText
  Return

Note usage of the sort index $g_aIndex:

$g_aArray[$g_aIndex[($g_iSortDir=$HDF_SORTUP?$tNMLVDISPINFO.Item:$g_iRows-1-$tNMLVDISPINFO.Item)]]

LVN_GETDISPINFO code in WM_NOTIFY_Search:

Case $LVN_GETDISPINFOW
  Local $tNMLVDISPINFO = DllStructCreate( $tagNMLVDISPINFO, $lParam )
  If Not ( $g_aSearchCols[$tNMLVDISPINFO.SubItem] == "" ) Then Return ; Skip $g_aSearchCols
  Local Static $tText = DllStructCreate( "wchar Text[50]" ), $pText = DllStructGetPtr( $tText )
  $tText.Text = $g_aArray[$g_aSearchRows[$g_aSearchIndex[($g_iSortDir=$HDF_SORTUP?$tNMLVDISPINFO.Item:$g_iSearchRows-1-$tNMLVDISPINFO.Item)]]][$tNMLVDISPINFO.SubItem]
  $tNMLVDISPINFO.Text = $pText
  Return

Note the If statement that skips columns, which are subsequently drawn with custom draw code.

Usage of the search index $g_aSearchRows (see below) and the sort index $g_aSearchIndex (see below):

$g_aArray[$g_aSearchRows[$g_aSearchIndex[($g_iSortDir=$HDF_SORTUP?$tNMLVDISPINFO.Item:$g_iSearchRows-1-$tNMLVDISPINFO.Item)]]]

The custom draw code in WM_NOTIFY_Search that shows the part of a subitem text that exactly matches the search string on a cyan or yellow background is similar to the custom draw code here.
 

Main Loop
The interesting messages in the main loop regarding multi column search are $g_idComboListViewSubItem0,3,4, $idExtractRowsBySearchFilter and $idListViewSort.

$g_idComboListViewSubItem0,3,4 handles changes to a search filter made through the ListView Combo control in columns 0, 3, and 4.

Rows that match the search filter are extracted from $g_aArray through a $idExtractRowsBySearchFilter message.

Finally, the rows are sorted through a $idListViewSort message.
 

Searching
Code to extract the rows that match a search filter is implemented this way for a RegEx search:

For $i = 0 To $iSearchRowsAll - 1
  If StringRegExp( $g_aArray[$aSearchRowsAll[$i]][$g_iCLVRow], $sSearch ) Then
    $g_aSearchRows[$g_iSearchRows] = $aSearchRowsAll[$i]
    $g_iSearchRows += 1
  EndIf
Next

For the first search filter with order 0 in the image $iSearchRowsAll = $g_iRows. And $aSearchRowsAll = $g_aIndex as calculated for the last column (the current sort column) in the main ListView. $g_iSearchRows is the number of matching rows and $g_aSearchRows is an index of the matching rows.

For the last search filter with order 2 in the image $iSearchRowsAll = $g_iSearchRows as calculated for the previous search filter with order 1. And $aSearchRowsAll = $g_aSearchIndex (see below) also as calculated for the previous search filter with order 1.
 

Sorting
Sorting the rows that match a search filter is performed this way:

$g_tIndex = FAS_Sort2DArrayAu3( $g_aArray, $aCompare, $g_aSearchRows, $g_iSearchRows )
AccessVariables01( IntStructToArraySearchMtd, $g_aSearchIndex )

$g_aSearchRows and $g_iSearchRows are passed to FAS_Sort2DArrayAu3() as parameters. FAS_Sort2DArrayAu3() returns a sort index as a DllStruct, which is converted to a 1d array of integers in $g_aSearchIndex. $g_aSearchIndex is the sort index for rows extracted through a search filter.

FAS_Sort2DArrayIndexFunc() performs the sorting when the two additional parameters are passed to FAS_Sort2DArrayAu3(). This is the first part of FAS_Sort2DArrayIndexFunc() to sort strings:

Func FAS_Sort2DArrayIndexFunc( $aArray, $aCompare, $aSearchRows, $iSearchRows )
  Local $iRows = $iSearchRows, $tIndex = DllStructCreate( "uint[" & $iRows & "]" ), $pIndex = DllStructGetPtr( $tIndex )
  Static $hDll = DllOpen( "kernel32.dll" )

  Local $c, $a, $lo, $hi, $mi

  If $aCompare[0][1] Then
    ; Sorting by one column of strings
    $c = $aCompare[0][0]
    $a = $aCompare[0][2]
    For $i = 1 To $iRows - 1
      $lo = 0
      $hi = $i - 1
      Do
        $mi = Int( ( $lo + $hi ) / 2 )
        Switch StringCompare( $aArray[$aSearchRows[$i]][$c], $aArray[$aSearchRows[DllStructGetData($tIndex,1,$mi+1)]][$c] ) * $a
          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

The very last code box in the Main ListView section above shows how to extract a row from $g_aArray, taking into account both $g_aSearchRows and $g_aSearchIndex.
 

Miscellaneous

On 6/10/2021 at 12:43 AM, pixelsearch said:

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)

That's not correct. Starting with Windows 7, all the different parts that make up a listview item or subitem  (checkbox, icon, subicon, label, sublabel, left and top margins) have the same size. Only older Windows versions use other sizes. Only very few Windows styles will change these sizes on Windows 7 and later.
 

Code
MultiColumnSearch.7z

I'll probably add a few more posts in relation to these examples. Then I'll include all the examples in the 7z-file at bottom of first post.

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...