前言

       增量搜索(又叫渐进搜索)是我比较喜欢的一种搜索方式,这种一边输入一边搜索的模式常常能更快的找到关键字,特别是在关键字记 得不全的时候。大部分代码编辑器都提供了增量搜索的功能,比如DelphiVS。在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: stringvirtualabstract;
    
(* 取得搜索的起始位置,必须由子类给出 *)
    
function GetSearchStart: Integer; virtualabstract;
    
(* 返回搜索关键字的最大长度 *)
    
function GetMaxKey: Integer; virtual;
    
(* 设置搜到的匹配块,必须由子类实现 *)
    
procedure SetMatchBlock(MatchBlock: PMatchBlock); virtualabstract;
    
(* 开始搜索,子类作额外处理 *)
    
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: stringoverride;
    
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 <> nilthen
  
begin
    TCustomEdit(Ctrl).SelStart :
= MatchBlock^.Start;
    TCustomEdit(Ctrl).SelLength :
= MatchBlock^.Len;
  
end;
end;

end.

结束

       为了说明这个单元的使用,这里有例子程序下载

       你也可以实现单元的接口或扩展某些类,以满足你的需求。


 

posted on 2010-03-30 21:12  on_road  阅读(1433)  评论(0编辑  收藏  举报