实现编辑控件的增量搜索

前言

       增量搜索(又叫渐进搜索)是我比较喜欢的一种搜索方式,这种一边输入一边搜索的模式常常能更快的找到关键字,特别是在关键字记 得不全的时候。大部分代码编辑器都提供了增量搜索的功能,比如DelphiVS。在Delphi,我用得最多的快捷键几乎就是Ctrl+E了。

       这几天突然心血来潮,研究了一下增量搜索的原理,成果就是这篇文章,这大概不是最好的实现,不过从搜 索速度和结果来看,基本已经满足要求了,这一点从大文件(比如Windows.pas)的测试可以证实。

       基本原理就是每输入一个字符就从前一个匹配的位置起搜索关键字,得到的匹配块压进一个栈中。在回退的 时候从栈中弹出一个匹配块,然后定位到这个匹配块所指的位置。

       更加详细的实现请看下面的代码。

类结构设计

       在实现之前,我认为一定有更快的方法,因此必须设计一个更具扩展性的类结构,如下图所示:

       IIncreSearch定义了增量搜索的规范,任何增量搜索的类都可以实现该接口。TIncreSearch是一个默认的实现, 即上面所说的用栈结构来保存匹配块的方法。注意这里的搜索类并没有与任何控件界面关联,它是一个纯粹的类。

       TIncreSearchHandler用于处理支持增量搜索的控件的一些消息,比如字符消息,搜索结束消息;同时它使用IncreSearch来实现控件的增量搜 索。

       TEdtIncreSearchHandler实现了编辑类控件的增量搜索,关联的控件必须是TCustomEdit类型的。

代码

       下面是代码,代码是设计的最佳说明:


ExpandedBlockStart.gif 代码
{ ********************************************************** }
{  摘要: 编辑控件增量搜索实现                               }
{                                                            }
{  作者: 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 .

结束

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

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


 

转载于:https://www.cnblogs.com/onroad/archive/2010/03/30/1700899.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值