前言
增量搜索(又叫渐进搜索)是我比较喜欢的一种搜索方式,这种一边输入一边搜索的模式常常能更快的找到关键字,特别是在关键字记 得不全的时候。大部分代码编辑器都提供了增量搜索的功能,比如Delphi,VS。在Delphi,我用得最多的快捷键几乎就是Ctrl+E了。
这几天突然心血来潮,研究了一下增量搜索的原理,成果就是这篇文章,这大概不是最好的实现,不过从搜 索速度和结果来看,基本已经满足要求了,这一点从大文件(比如Windows.pas)的测试可以证实。
基本原理就是每输入一个字符就从前一个匹配的位置起搜索关键字,得到的匹配块压进一个栈中。在回退的 时候从栈中弹出一个匹配块,然后定位到这个匹配块所指的位置。
更加详细的实现请看下面的代码。
类结构设计
在实现之前,我认为一定有更快的方法,因此必须设计一个更具扩展性的类结构,如下图所示:
IIncreSearch定义了增量搜索的规范,任何增量搜索的类都可以实现该接口。TIncreSearch是一个默认的实现, 即上面所说的用栈结构来保存匹配块的方法。注意这里的搜索类并没有与任何控件界面关联,它是一个纯粹的类。
TIncreSearchHandler用于处理支持增量搜索的控件的一些消息,比如字符消息,搜索结束消息;同时它使用IncreSearch来实现控件的增量搜 索。
TEdtIncreSearchHandler实现了编辑类控件的增量搜索,关联的控件必须是TCustomEdit类型的。
代码
下面是代码,代码是设计的最佳说明:
{ 摘要: 编辑控件增量搜索实现 }
{ }
{ 作者: LinZhenqun }
{ 日期: 2007-10-11 }
{ 邮件: linzhenqun@gmail.net }
{ ********************************************************** }
unit IncreSearch;
(* ******************************************************************************
说明:
1、本单元实现了一个编辑控件的增量搜索。
2、IIncreSearch指定增量搜索的规范,任何增量搜索类都应该实现该接口;
3、TIncreSearch是IIncreSearch的一个默认实现,主要是用栈来保存搜索到的匹配块。
4、TInsreSearchHandler为搜索处理器的抽象类,它使用IIncreSearch来实现增量搜索,
同时处理增量搜索的操作逻辑。
5、TEditInsreSearchHandler为编辑控件的搜索处理器,与之关联的控件必须是TCustomEdit
类型的控件。
用法:
1、实例化一个处理器,比如TEditInsreSearchHandler,并在构造函数中传进将被搜索
的控件。
2、调用StarteSearch开始增量搜索。
3、调用Searching确定增量搜索是否已经结束。
扩展:
1、增量搜索类:TIncreSearch只是一个默认的搜索实现类,这不是一个最快速的类,
如果你知道如何更快速的实现增量搜索,请实现IIncreSearch接口,并在处理器
的构造函数中传进这个类,比如:
TSomeInsreSearchHandler.Create(TSomeControl, YourIncreSearch);
处理器面对的是搜索接口,因此要特别注意其生命周期的管理。
2、搜索处理器:TEditInsreSearchHandler只能处理TCustomEdit的编辑控件,对于一
些不是从TCustomEdit继承的控件,则必须写另外的处理器。
实现很简单,继承TInsreSearchHandler,并且必须覆盖GetSearchStr,GetSearchStart,
SetMatchBlock三个抽象方法,提供搜索必需的信息。
如想提供更多的扩展,可以覆盖TInsreSearchHandler其他的虚方法。
***************************************************************************** *)
interface
uses
Classes, Messages, Controls, Contnrs;
type
{ 匹配的块 }
PMatchBlock = ^TMatchBlock;
TMatchBlock = record
Start: Integer;
Len: Integer;
end ;
{ 增量搜索接口 }
IIncreSearch = interface
[ ' {A8037752-ED93-49F6-915C-6D8E82ADD631} ' ]
(* 开始搜索 *)
procedure StartSearch( const SearchStr: string ; Start: Integer = 0 ; MaxKey: Word = 80 );
(* 结束搜索 *)
procedure EndSearch;
(* 增加一个字符 *)
function IncreaseChar(C: Char): PMatchBlock;
(* 减少一个字符 *)
function DecreaseChar: PMatchBlock;
(* 是否正在搜索 *)
function GetSearching: Boolean;
property Searching: Boolean read GetSearching;
end ;
{ 增量搜索的默认实现:利用栈来保存匹配块 }
TIncreSearch = class (TInterfacedObject, IIncreSearch)
private
FSearching: Boolean;
FKeyIndex: Integer;
FStart: Integer;
FSearchKey: string ;
FSearchStr: string ;
FMatchBlockList: TStack;
protected
procedure ClearMatchBlockList;
public
procedure StartSearch( const SearchStr: string ; Start: Integer = 0 ; MaxKey: Word = 80 );
procedure EndSearch;
function IncreaseChar(C: Char): PMatchBlock;
function DecreaseChar: PMatchBlock;
function GetSearching: Boolean;
property Searching: Boolean read GetSearching;
constructor Create;
destructor Destroy; override ;
end ;
{ 增量搜索处理器,抽象类 }
TIncreSearchHandler = class
private
FIncreSearch: IIncreSearch;
protected
FCtrl: TControl;
FOldCtrlWndProc: TWndMethod;
procedure HookWndProc;
procedure UnHookWndProc;
(* 控件处理过程,处理增量搜索的字符 *)
procedure CtrlWndProc( var Message: TMessage); virtual ;
(* 导致增量搜索结束的消息 *)
function IsEndSearchMsg(Message: TMessage): Boolean; virtual ;
(* 取得等搜索的字符串,必须由子类给出 *)
function GetSearchStr: string ; virtual ; abstract ;
(* 取得搜索的起始位置,必须由子类给出 *)
function GetSearchStart: Integer; virtual ; abstract ;
(* 返回搜索关键字的最大长度 *)
function GetMaxKey: Integer; virtual ;
(* 设置搜到的匹配块,必须由子类实现 *)
procedure SetMatchBlock(MatchBlock: PMatchBlock); virtual ; abstract ;
(* 开始搜索,子类作额外处理 *)
procedure DoEndSearch; virtual ;
(* 结束搜索,子类作额外处理 *)
procedure DoStartSearch; virtual ;
public
procedure StarteSearch;
procedure EndSearch;
function Searching: Boolean;
property Ctrl: TControl read FCtrl;
constructor Create(ACtrl: TControl; IncreSearch: IIncreSearch = nil ); virtual ;
destructor Destroy; override ;
end ;
{ 编辑控件增量搜索处理器,编辑控件必须从TCustomEdit继承 }
TEdtIncreSearchHandler = class (TIncreSearchHandler)
protected
function GetSearchStr: string ; override ;
procedure SetMatchBlock(MatchBlock: PMatchBlock); override ;
function GetSearchStart: Integer; override ;
public
constructor Create(ACtrl: TControl; IncreSearch: IIncreSearch = nil ); override ;
end ;
implementation
uses
SysUtils, StdCtrls, Windows;
{ TIncreSearch }
procedure TIncreSearch.ClearMatchBlockList;
var
P: Pointer;
begin
if FMatchBlockList.Count > 0 then
begin
P : = FMatchBlockList.Pop;
Dispose(P);
end ;
while FMatchBlockList.Count > 0 do
begin
P : = FMatchBlockList.Pop;
Dispose(P);
end ;
end ;
constructor TIncreSearch.Create;
begin
FMatchBlockList : = TStack.Create;
end ;
function TIncreSearch.DecreaseChar: PMatchBlock;
begin
Result : = nil ;
if FKeyIndex = 0 then Exit;
FSearchKey[FKeyIndex] : = # 0 ;
Dec(FKeyIndex);
if FMatchBlockList.Count > 1 then
Dispose(FMatchBlockList.Pop);
if FMatchBlockList.Count > 0 then
Result : = PMatchBlock(FMatchBlockList.Peek);
end ;
destructor TIncreSearch.Destroy;
begin
ClearMatchBlockList;
FMatchBlockList.Free;
inherited ;
end ;
procedure TIncreSearch.EndSearch;
begin
FSearching : = False;
end ;
function TIncreSearch.GetSearching: Boolean;
begin
Result : = FSearching;
end ;
function TIncreSearch.IncreaseChar(C: Char): PMatchBlock;
var
nStart: Integer;
pwSearStr, pwMatch: PChar;
begin
Result : = nil ;
Inc(FKeyIndex);
if FKeyIndex > Length(FSearchKey) then Exit;
if (C >= ' A ' ) and (C <= ' Z ' ) then
C : = Chr(Ord(C) + $ 20 );
FSearchKey[FKeyIndex] : = C;
if FMatchBlockList.Count = 1 then
nStart : = FStart
else
nStart : = PMatchBlock(FMatchBlockList.Peek)^.Start;
pwSearStr : = PChar(FSearchStr);
Inc(pwSearStr, nStart);
pwMatch : = StrPos(pwSearStr, PChar(FSearchKey));
if pwMatch <> nil then
begin
New(Result);
Result^.Len : = FKeyIndex;
Result^.Start : = nStart + (pwMatch - pwSearStr);
FMatchBlockList.Push(Result);
end
else begin
FSearchKey[FKeyIndex] : = # 0 ;
Dec(FKeyIndex);
end ;
end ;
procedure TIncreSearch.StartSearch( const SearchStr: string ;
Start: Integer; MaxKey: Word);
var
SearBlock: PMatchBlock;
pStr: PChar;
begin
FSearching : = True;
// 初始化关键字
SetLength(FSearchKey, MaxKey);
FillChar(FSearchKey[ 1 ], MaxKey, 0 );
FKeyIndex : = 0 ;
FStart : = Start;
// 字符串流全部变成小写
FSearchStr : = SearchStr;
pStr : = PChar(FSearchStr);
StrLower(pStr);
// 压入一个起始位置的匹配块
ClearMatchBlockList;
New(SearBlock);
SearBlock^.Start : = FStart;
SearBlock^.Len : = 0 ;
FMatchBlockList.Push(SearBlock);
end ;
{ TIncreSearchHandler }
constructor TIncreSearchHandler.Create(ACtrl: TControl; IncreSearch: IIncreSearch = nil );
begin
FCtrl : = ACtrl;
HookWndProc;
if IncreSearch = nil then
FIncreSearch : = TIncreSearch.Create
else
FIncreSearch : = IncreSearch;
end ;
procedure TIncreSearchHandler.CtrlWndProc( var Message: TMessage);
var
MatchBlock: PMatchBlock;
C: Char;
Key: Word;
begin
if FIncreSearch.Searching then
begin
if IsEndSearchMsg(Message) then
begin
FIncreSearch.EndSearch;
DoEndSearch;
end
else begin
// 处理增量搜索的字符
case Message.Msg of
WM_CHAR:
begin
C : = Char(TWMChar(Message).CharCode);
if C <> # 8 then
begin
MatchBlock : = FIncreSearch.IncreaseChar(C);
if MatchBlock <> nil then
SetMatchBlock(MatchBlock);
end ;
TWMChar(Message).CharCode : = 0 ;
end ;
WM_KEYDOWN:
begin
Key : = TWMKey(Message).CharCode;
if Key = 8 then
begin
MatchBlock : = FIncreSearch.DecreaseChar;
TWMKey(Message).CharCode : = 0 ;
if MatchBlock <> nil then
SetMatchBlock(MatchBlock);
end ;
end ;
end ;
end ;
end ;
FOldCtrlWndProc(Message);
end ;
destructor TIncreSearchHandler.Destroy;
begin
UnHookWndProc;
FIncreSearch : = nil ;
inherited ;
end ;
procedure TIncreSearchHandler.DoEndSearch;
begin
end ;
procedure TIncreSearchHandler.DoStartSearch;
begin
end ;
procedure TIncreSearchHandler.EndSearch;
begin
FIncreSearch.EndSearch;
end ;
function TIncreSearchHandler.GetMaxKey: Integer;
begin
Result : = 80 ;
end ;
procedure TIncreSearchHandler.HookWndProc;
begin
FOldCtrlWndProc : = FCtrl.WindowProc;
FCtrl.WindowProc : = CtrlWndProc;
end ;
function TIncreSearchHandler.IsEndSearchMsg(Message: TMessage): Boolean;
function KeyInKeys(Key: Word): Boolean;
begin
case Key of
VK_ESCAPE, VK_RIGHT, VK_LEFT, VK_UP, VK_DOWN, VK_TAB,
VK_RETURN, VK_INSERT:
Result : = True
else
Result : = False;
end ;
end ;
begin
Result : = ((Message.Msg >= WM_MOUSEFIRST) and (Message.Msg <= WM_MOUSELAST) and
(Message.Msg <> WM_MOUSEMOVE)) or
((Message.Msg = WM_KEYDOWN) and KeyInKeys(TWMKey(Message).CharCode));
end ;
function TIncreSearchHandler.Searching: Boolean;
begin
Result : = FIncreSearch.Searching;
end ;
procedure TIncreSearchHandler.StarteSearch;
begin
FIncreSearch.StartSearch(GetSearchStr, GetSearchStart, GetMaxKey);
end ;
procedure TIncreSearchHandler.UnHookWndProc;
begin
FCtrl.WindowProc : = FOldCtrlWndProc;
end ;
{ TEdtIncreSearchHandler }
constructor TEdtIncreSearchHandler.Create(ACtrl: TControl;
IncreSearch: IIncreSearch);
begin
inherited ;
if not (ACtrl is TCustomEdit) then
raise Exception.Create( ' the control must be inherited from TCustomEdit ' );
end ;
function TEdtIncreSearchHandler.GetSearchStart: Integer;
begin
if (Ctrl is TCustomEdit) then
Result : = TCustomEdit(Ctrl).SelStart
else
Result : = 0 ;
end ;
function TEdtIncreSearchHandler.GetSearchStr: string ;
begin
if FCtrl is TCustomEdit then
Result : = TCustomEdit(Ctrl).Text
else
Result : = '' ;
end ;
procedure TEdtIncreSearchHandler.SetMatchBlock(MatchBlock: PMatchBlock);
begin
if (FCtrl is TCustomEdit) and (MatchBlock <> nil ) then
begin
TCustomEdit(Ctrl).SelStart : = MatchBlock^.Start;
TCustomEdit(Ctrl).SelLength : = MatchBlock^.Len;
end ;
end ;
end .
结束
为了说明这个单元的使用,这里有例子程序下载。
你也可以实现单元的接口或扩展某些类,以满足你的需求。