tListBox 使用来自其他 ListBox 的自动完成导航
tListBox using AutoComplete navigation from other ListBox
有没有办法使用原生 tListBox 自动完成导航系统,但基于其他 ListBox 的项目?因此,当我键入某些字符项目时,当 ListBox1 聚焦时,应根据 ListBox2 中的数据选择。两人物品数量相同
Is there a way to use native tListBox AutoComplete navigation system but based on the items of other ListBox?
是,但前提是 TListBox.Style
属性 设置为 lbVirtual
或 lbVirtualOwnerDraw
。在这种情况下,您必须使用 TListBox.Count
属性 和 TListBox.OnData
事件向列表框提供字符串。然后,自动完成功能将触发 TListBox.OnDataFind
事件,要求您在从中获取字符串的任何来源中找到键入的字符。在该事件处理程序中,您可以根据需要搜索其他 TListBox
。只知道 OnDataFind
事件处理程序编辑的 Integer
return 必须是相对于用户正在输入的 TListBox
的索引,而不是 TListBox
你正在搜索。当 OnDataFind
事件处理程序退出时,将选择您 return 的索引,除非您 return -1 表示未找到字符。
好吧,我试图让它成为原生的,但据我所知 - 不可能将不同的项目高度功能 (Style=lbOwnerDrawVariable) 与 DataFind 事件处理结合起来。当然可以在“VCL\StdCtrls”中编辑 TCustomListBox.KeyPress
程序,但我不喜欢深入研究 vcl 源代码。所以我决定自己从头开始实现自动完成功能。
首先,我决定索引项目字符串以加快搜索速度,但不需要制作完整的图表(octotree ......无论如何......)因为在我的情况下搜索(我有大约 1k 项目)几乎立即执行- 因为我的列表是按字典顺序排序的,所以我只为第一个字母建立了索引...
//...
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;
//...
是的,这段代码有点难看,但它工作正常,正如我所说的 - 几乎是瞬间,我就可以在输入时看到选择是如何跳过项目的,而且我的输入速度非常快...
所以这是我昨天写的代码,所有这一切都可以在这里结束,但今天我意识到这完全是一个愚蠢的决定:在我的例子中,正如我上面提到的,我有 OwnerDrawVariable 样式的列表框,所以我有自定义 MeasureItem 和 DrawItem 过程,在这种情况下最好的决定是将 AutoComplete 属性 变为 true 并用 ListBox2 中的项目填充 ListBox1。并且无论如何都可以在 DrawItem 过程中显示需要显示的字符串。也可以删除 ListBox2 并保留用于在 tStringList 变量中显示的字符串。所以士气是——不要急于写样板,在行动之前多想想=))
P.S。但是可以使用此代码进行一些自定义自动完成处理...
有没有办法使用原生 tListBox 自动完成导航系统,但基于其他 ListBox 的项目?因此,当我键入某些字符项目时,当 ListBox1 聚焦时,应根据 ListBox2 中的数据选择。两人物品数量相同
Is there a way to use native tListBox AutoComplete navigation system but based on the items of other ListBox?
是,但前提是 TListBox.Style
属性 设置为 lbVirtual
或 lbVirtualOwnerDraw
。在这种情况下,您必须使用 TListBox.Count
属性 和 TListBox.OnData
事件向列表框提供字符串。然后,自动完成功能将触发 TListBox.OnDataFind
事件,要求您在从中获取字符串的任何来源中找到键入的字符。在该事件处理程序中,您可以根据需要搜索其他 TListBox
。只知道 OnDataFind
事件处理程序编辑的 Integer
return 必须是相对于用户正在输入的 TListBox
的索引,而不是 TListBox
你正在搜索。当 OnDataFind
事件处理程序退出时,将选择您 return 的索引,除非您 return -1 表示未找到字符。
好吧,我试图让它成为原生的,但据我所知 - 不可能将不同的项目高度功能 (Style=lbOwnerDrawVariable) 与 DataFind 事件处理结合起来。当然可以在“VCL\StdCtrls”中编辑 TCustomListBox.KeyPress
程序,但我不喜欢深入研究 vcl 源代码。所以我决定自己从头开始实现自动完成功能。
首先,我决定索引项目字符串以加快搜索速度,但不需要制作完整的图表(octotree ......无论如何......)因为在我的情况下搜索(我有大约 1k 项目)几乎立即执行- 因为我的列表是按字典顺序排序的,所以我只为第一个字母建立了索引...
//...
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;
//...
是的,这段代码有点难看,但它工作正常,正如我所说的 - 几乎是瞬间,我就可以在输入时看到选择是如何跳过项目的,而且我的输入速度非常快...
所以这是我昨天写的代码,所有这一切都可以在这里结束,但今天我意识到这完全是一个愚蠢的决定:在我的例子中,正如我上面提到的,我有 OwnerDrawVariable 样式的列表框,所以我有自定义 MeasureItem 和 DrawItem 过程,在这种情况下最好的决定是将 AutoComplete 属性 变为 true 并用 ListBox2 中的项目填充 ListBox1。并且无论如何都可以在 DrawItem 过程中显示需要显示的字符串。也可以删除 ListBox2 并保留用于在 tStringList 变量中显示的字符串。所以士气是——不要急于写样板,在行动之前多想想=))
P.S。但是可以使用此代码进行一些自定义自动完成处理...