• 一些关于随着输入不断更新搜索结果的技巧


    最近发现很多人在做搜索功能的时候都喜欢实现随着输入更新搜索结果

    当然这样比以前的那种都输入完了点搜索按钮要直观的多, 同时也省了一次点击, 减少用户操作, 但是带来的一个问题就是不断的建立新的搜索导致性能的降低

    比如下面这种代码是最常见的:在数据量小, 数据库连接速度快的时候这样当然没什么问题, 但是一旦反应速度慢了, 就会造成很不好的使用感觉, 每输入一个字符都要有一些卡顿的感觉

    procedure TForm1.edtSearchChange(Sender: TObject);
    begin
      nQuery.Close;
      nQuery.SQL.Text := 'SELECT XXX FROM TABLE WHERE Field like ' + QuotedStr('%' + edtSearch.Text + '%');
      nQuery.Open;
      {显示搜索结果}
    end;

    现在可以优化一下这个方法: 并不是每个字符都需要等待结果的, 更多的时候用户都是知道几个确认的字符, 都输入以后才想看到搜索结果

    根据这个思考一下, 一般输入的时候对于自己确定要输入的内容都比较快, 而输入完毕或者不确定的内容都需要思考一下, 输入速度就慢, 所以我们使用TTimer根据输入速度设置一个查询间隔

    tmSearch: TTimer;
    
    
    
    procedure TfrmSearchTest.FormCreate(Sender: TObject);
    begin
      with tmSearch do
      begin
        Enabled := False;
        Interval := 500;
      end;
    end;
    
    procedure TfrmSearchTest.edtSearchChange(Sender: TObject);
    begin
      {每次内容变更都重新计算0.5秒以后开始查询, 这样就让连续输入的内容之间可以不再执行查询}
      with tmSearch do
      begin
        Enabled := True;
        OnTimer := OnTimer; {这样是为了执行UpdateTimer更新触发事件, 不明白的看OnTimer属性源码}
      end;
    end;
    
    procedure TfrmSearchTest.tmSearchTimer(Sender: TObject);
    begin
      tmSearch.Enabled := False;
      nQuery.Close;
      nQuery.SQL.Text := 'SELECT XXX FROM TABLE WHERE Field like ' + QuotedStr('%' + edtSearch.Text + '%');
      nQuery.Open;
      {显示搜索结果}
    end;

    OK, 现在用起来的感觉就比较好了但是一旦数据量大了以后, 执行SQL的速度会比较慢, 特别是用了like '%xxx%'这样的东西, 执行速度慢会造成查询时的卡顿

    所以, 我们再次改造: 吧SQL放到线程里去执行

    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, DB, ADODB, StdCtrls, ExtCtrls, Grids, DBGrids, ActiveX;
    
    const
      WM_USER_SEARCHED = WM_USER + $A0; {定义一个查询完毕通知消息}
    
    type
      TTSearch = class(TThread)
        FSQL: string;
        FConStr: string;
        FNotifyHandle: THandle;
      protected
        procedure Execute; override;
      public
        destructor Destroy; override;
        constructor Create(ASQL, AConStr: string; ANotifyHandle: THandle);
      end;
    
      TfrmSearchTest = class(TForm)
        edtSearch: TEdit;
        nQuery: TADOQuery;
        tmSearch: TTimer;
        DBGrid1: TDBGrid;
        DataSource1: TDataSource;
        procedure FormCreate(Sender: TObject);
        procedure edtSearchChange(Sender: TObject);
        procedure tmSearchTimer(Sender: TObject);
      private
        { Private declarations }
        procedure WMUSERSEARCHED(var Msg: TMessage); message WM_USER_SEARCHED;
      public
        { Public declarations }
      end;
    
    var
      frmSearchTest: TfrmSearchTest;
    
    implementation
    
    {$R *.dfm}
    
    procedure TfrmSearchTest.FormCreate(Sender: TObject);
    begin
      with tmSearch do
      begin
        Enabled := False;
        Interval := 500;
      end;
    end;
    
    procedure TfrmSearchTest.edtSearchChange(Sender: TObject);
    begin
      {每次内容变更都重新计算0.5秒以后开始查询, 这样就让连续输入的内容之间可以不再执行查询}
      with tmSearch do
      begin
        Enabled := True;
        OnTimer := OnTimer; {这样是为了执行UpdateTimer更新触发事件, 不明白的看OnTimer属性源码}
      end;
    end;
    
    procedure TfrmSearchTest.tmSearchTimer(Sender: TObject);
    var
      nSQL: string;
    begin
      tmSearch.Enabled := False;
      nSQL :=  'SELECT XXX FROM TABLE WHERE Field like ' + QuotedStr('%' + edtSearch.Text + '%');
      TTSearch.Create(nSQL, nQuery.ConnectionString, Handle);
    
    end;
    
    { TTSearch }
    
    constructor TTSearch.Create(ASQL, AConStr: string; ANotifyHandle: THandle);
    begin
      FSQL := ASQL;
      FConStr := AConStr;
      FNotifyHandle := ANotifyHandle;
      FreeOnTerminate := True;
      inherited Create(False);
    end;
    
    destructor TTSearch.Destroy;
    begin
    
      inherited;
    end;
    
    procedure TTSearch.Execute;
    var
      nQ: TADOQuery;
    begin
      inherited;
      CoInitialize(nil);
      nQ := TADOQuery.Create(nil);
      try
        nQ.ConnectionString := FConStr;
        nQ.SQL.Text := FSQL;
        nQ.Open;
        {通过消息吧执行结果发给主进程}
        nQ.Recordset._AddRef; {手动增加接口引用计数, 防止nQ.Free的时候接口被一起释放}
        {Integer(nQ.Recordset) 这样调用并不会增加接口的引用计数}
        PostMessage(FNotifyHandle, WM_USER_SEARCHED, 0, Integer(nQ.Recordset));
      finally
        nQ.Free;
      end;
      CoUninitialize;
    end;
    
    procedure TfrmSearchTest.WMUSERSEARCHED(var Msg: TMessage);
    begin
      with nQuery do
      begin
        Close;
        Recordset := _Recordset(Msg.LParam);
        Recordset._Release; {减少一个引用计数, 防止接口无法释放造成内存泄漏}
        {显示搜索结果}
      end;
    end;
    
    end.

    恩, 现在不卡顿了, 不过再仔细想想还有没有什么可以优化的地方(职业病又犯了)

    1. 每次查询都要从数据库中select, 这样会不会太浪费资源了

    2. 每次查询, 如果只是新输入的字母 那肯定是在原有已查到的信息中做二次查询, 而不可能包含新的查询结果

    恩 所以只有在搜索内容变短或者第一次输入的时候才做数据库查询, 其余的在本地做内存查询就够了

    代码就不帖了, 有点多, 各位看官要自己动脑喽

    {------------------------------------无敌分隔线--------------------------------------}

    下一步优化:

      可以增加一个数据库查询的阀值, 即大概在多少个字符或者多详细的关键词以内, 使用本地内存数据搜索, 其外使用数据库搜索

      如果关键词太笼统, 返回的数据太庞大, 占用内存太多, 关键词太详细, 返回的数据太少, 下一步内存搜索基本没什么用

      另外, 内存搜索也不是每次都循环数据库返回的数据, 而是在上一次搜索结果下再进行搜索, 同样只有不包含上一次的搜索内容时才开始从返回的数据库值进行搜索

      还有, 内存搜索可以灵活排序

    另外, 个人推荐别用数据感知控件, 使用listview的ownerdata模式, 显示速度会更快, 同样对数据显示的掌控能力也更强

  • 相关阅读:
    Spring国际化模块
    广告牌 循环 轮播 图片
    ImageLoader 网络加载图片
    ProgressBar 进度条 旋转
    UI处理 线程
    权限 动态 访问
    系统 状态栏 导航栏
    对话框 dialog 整理
    修改 字体
    获取 Activity中所有的View ViewGroup
  • 原文地址:https://www.cnblogs.com/lzl_17948876/p/3523583.html
Copyright © 2020-2023  润新知