Is there a way to use native tListBox AutoComplete navigation system but based on the items of other ListBox? So when ListBox1 focused when i type some chars items should be selected according to data from ListBox2. Both of them have same amount of items.
Well i tried to make it native but as far as i get it - it's impossible to combine different item-height functionality (Style=lbOwnerDrawVariable) with DataFind events handling. Ofcourse one can edit TCustomListBox.KeyPress
procedure in "VCL\StdCtrls", but i dont like to dig into vcl sources. So i decided to implement AutoComplete feature by myself from a scratch.
First of all i decided to index item-strings for faster search, but there are no need to make full graph (octotree... whatever...) cuz search in my case (and i have about 1k items) anyways performed nearly instantly - so cuz my list is lexicographically sorted i made indexing only for first letters...
//...
const
acceptCharz=[' '..'z'];
//...
type
twoWords=packed record a,b:word; end;
//...
var
alphs:array[' '..'z']of twoWords;// indexes of first letters
fs:tStringList;// list for searching in
fnd:string;// charbuffer for search string
tk:cardinal;// tickCounter for timing
//...
procedure Tform1.button1Click(Sender: TObject);// procedure where list filled with items
var
k,l:integer;
h,q:char;
begin
//... here list-content formed ...
if(fs<>nil)then fs.Free;
fs:=tStringList.Create;
// fltr is tListBox which data should be source of AutoCompletion (ListBox2)
fs.AddStrings(fltr.Items);
for h:=low(alphs)to high(alphs)do alphs[h].a:=$FFFF;// resetting index
h:=#0;
l:=fs.Count-1;
if(l<0)then exit;
for k:=0 to l do begin
s:=AnsiLowerCase(fs.Strings[k]);// for case-insensetivity
fs.Strings[k]:=s;
if(length(s)<0)then continue;
q:=s[1];
if(h<>q)and(q in acceptCharz)then begin
if(k>0)then alphs[h].b:=k-1;
h:=q;
alphs[h].a:=k;// this will work only with sorted data!
end;
end;
if(h<>#0)then alphs[h].b:=l;
end;
//...
// fl is tListBox with custom drawing and OwnerDrawVariable style (ListBox1)
// also fl has same amount of items as fltr
procedure Tform1.flKeyPress(Sender: TObject; var Key: Char);
var
n,i,k,e,l,u,m,a:integer;
s:string;
h:char;
function CharLowerr(h:char):char;// make char LowerCase
begin
Result:=char(LoWord(CharLower(Pointer(h))));
end;
begin
if(getTickCount-tk>=800)then fnd:='';// AutoComplete timeout
tk:=getTickCount;
h:=CharLowerr(key);// for case-insensetivity
if(h in ['a'..'z','0'..'9',' ','-','.'])then fnd:=fnd+h;// u can add all needed chars
if(fnd='')then exit;// if no string to find
h:=fnd[1];// obtain first letter of search-string
a:=alphs[h].a;// get index of first item starting with letter
l:=alphs[h].b;// get index of last item starting with letter
if(a=$FFFF)then exit;// if no such items
e:=length(fnd);// get length of search-string
u:=1;// current length of overlap
m:=1;// max overlap
i:=a;// index to select
if(e>1)then for k:=a to l do begin
s:=fs.Strings[k];
if(length(s)<e)then continue;
for n:=2 to e do begin// compare strings char-by-char
if(s[n]<>fnd[n])then begin// compare failed
u:=n-1;
break;
end else u:=n;
if(u>m)then begin// current overlap is max
m:=u;
i:=k;
end;
end;
if(u=e)or(u<m)then break;// if end of search reached
end;
// select needed index:
fl.ClearSelection;
SendMessage(fl.Handle, LB_SELITEMRANGE, 1, MakeLParam(i, i));
fl.ItemIndex:=i;
inherited;
end;
//...
Yeah this code is kinda ugly, but it works fine, as i said - nearly instantly, i just can see how selection jumps thru items while i type, and i type pretty fast...
So it was code which i wrote yesterday and all this could end here, but today i realized that it was absolutely dumb decision at whole: in my case, as i mentioned above, i have listbox with OwnerDrawVariable style, so i have custom MeasureItem and DrawItem procedures and the best decision in this kind of situation will be to turn AutoComplete property true and to fill ListBox1 with items from ListBox2. And strings needed to display could be shown anyways in DrawItem procedure. Also possible to remove ListBox2 and keep strings for displaying inside tStringList variable. So morale is - dont rush into writing boilerplates and try to think more before acting =))
P.S. but one can use this code for some custom AutoComplete handling...