Komponenta pro inkrementální vyhledávání

V dnešním článku vám představím jednu svou komponentu, kterou lze zpříjemnit uživatelské prostředí v programech, obsahujících jakýkoli seznam.

Jedná se o hledací políčko s názvem TGenIncSearch, které se zobrazí nad seznamem a umožňuje uživateli zadat hledaný text. Text je vyhledáván se stiskem každé klávesy, a při stisku šipky nahoru nebo dolu vyhledá další resp. předchozí výskyt. Narozdíl od vestavěného (skrytého) vyhledávání v ListBoxech apod. je v tomto se možné o pár znaků vrátit a hledaný text poopravit, před novým hledáním není třeba čekat na time-out předchozího hledání a je možné hledat i pod-řetězec (substring).

Hledací políčko je žluté, ale přebarví se do červena, nebyl-li zadaný text nalezen. Při vyhledávání dalšího výskytu šipkou dolu to na posledním výskytu krátce červeně blikne, ale opět zežloutne, pokud právě označený prvek odpovídá hledanému textu. Políčko je "plovoucí" nad formulářem se seznamem, který se snaží nepřekrýt, aby nebyl třeba nález schován pod ním...
(Žluté políčko uživatel asi nepřehlédne, jako by se mohlo stát, kdyby bylo clWindow bílé jako zbytek programu...)

Hledací políčko je vyvoláno stiskem klávesy (defaultně F7), popřípadě přímo psaním hledaného textu, pokud není předpokládáno, že klávesy zpracuje komponenta seznamu, což se nastaví v property CtrlReadOnly - např. textový editor by měl CtrlReadOnly=False, aby dostal KeyPress událost, kdežto třeba ListBox nebo TreeView asi žádné KeyPress nepotřebuje a měl by nastaveno CtrlReadOnly=True...

Hledání je ukončeno:
Z uživatelského hlediska to je asi všechno, je to jasné a přehledné - "chcete-li vyhledat text, prostě začněte psát...", na ukončení hledání většinou přijdou sami...




Pro použití v programu:
Další vlastnosti, které je většinou možné nechat, jak jsou:
Události:


Událost OnGenLocate

Událost má celkem 4 parametry: Hledací procedura by měla v případě nálezu také vybrat nalezený prvek nastavením ItemIndex, SelectedNode nebo podobně... Pokud nebyl text nalezen, měl by list-box zůstat tam kde byl...

Parametr Dir má tyto 4 možnosti:
Pro hledání v komponentách, které mají Items: TStrings, je tam připravena statická funkce ListLocate, kterou lze použít takto:

procedure TForm1.IncSearchGenLocate(Sender: TObject; const Text: string;
                                    Dir: TGenLocateDir; var Found: Boolean);
var Index: integer;
begin
  Index:=ListBox1.ItemIndex;
  //
  if ListLocate(ListBox1.Items,Text,Index,Dir,True) then begin
    ListBox1.ItemIndex:=Index;
    Found:=True;
  end else
    Found:=False;
end;
V implementaci této funkce si můžete prohlédnout, jak je použit parametr Dir...
Poslední "True" znamená "hledat i sub-string"...

Pokud je zapnuta vlastnost MultiSelect na ListBoxu, je potřeba ještě doplnit za nastavení ItemIndex volání funkce, která označí vyhledaný Item:

    ...
    ListBox1.ItemIndex:=Index;
    if ListBox1.MultiSelect then
      ListBoxSelectOnly(ListBox1,Index);
    ...

a funkce, která označí v ListBoxu s MultiSelect jen jeden prvek vypadá takto:

procedure ListBoxSelectOnly(ListBox: TListBox; Index: integer);
var i: integer;
begin
  ListBox.Items.BeginUpdate;
  try
    if ListBox.MultiSelect then
      for i:=0 to ListBox.Items.Count-1 do begin
        ListBox.Selected[i]:=(i=Index);
      end;
  finally
    ListBox.Items.EndUpdate;
  end;
end;

(Ovšem konkrétně pro TListBox je tam připraven descendant TListIncSearch, který si tuto událost obsluhuje sám... Ale nejsou jenom TListBoxy...)

Hledání v TreeView je poněkud složitější, protože funkce, která by vrátila uzel (Node) nad a pod vybraným, není triviální...
Hledání v Datasetu je úplně jiné - gloFirst se řeší pomocí Table.Locate(...,[loPartialKey,loCaseInsensitive]), ale pro gloNext a gloPrev je potřeba opět napsat speciální funkce pro cestování tabulkou...

V případě zájmu bych je mohl uvést v dalším pokračování...


Ve zdrojovém kódu komponenty najdete mírně pokročilejší ukázku, jak v Delphi
A poznámka pro ty, kdo by chtěli namítnout, že toto lze udělat i obsluhou OnChange atd. na TEditu, který posadíte na formulář: