• How to make a combo box with fulltext search autocomplete support?


    I would like a user to be able to type in the second or third word from a TComboBoxitem and for that item to appear in the AutoSuggest dropdown options

    For example, a combo box contains the items:

    • Mr John Brown
    • Mrs Amanda Brown
    • Mr Brian Jones
    • Mrs Samantha Smith

    When the user types "Br" the dropdown displays:

    • Mr John Brown
    • Mrs Amanda Brown
    • Mr Brian Jones

    and when the user types "Jo" the dropdown displays:

    • Mr John Brown
    • Mr Brian Jones

    The problem is that the AutoSuggest functionality only includes items in the dropdown list that begin with what the user has input and so in the examples above nothing will appear in the dropdown.

    Is it possible to use the IAutoComplete interface and/or other related interfaces to get around this issue?

    The following example uses the interposed class of the TComboBox component. The main difference from the original class is that the items are stored in the separate StoredItems property instead of
    the Items as usually (used because of simplicity).

    The StoredItems are being watched by the OnChange event and whenever you change them (for instance by adding or deleting from this string list), the current filter will reflect it even when the combo
    list is dropped down.

    The main point here is to catch the WM_COMMAND message notification CBN_EDITUPDATE which is being sent whenever the combo edit text is changed but not rendered yet. When it arrives, you just search through the StoredItems list for what you have typed in your combo edit and fill the Items property with matches.

    For text searching is used the ContainsText so the search is case insensitive. Forgot to mention,
    the AutoComplete feature has to be turned off because it has its own, unwelcomed, logic for this purpose.

    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, StrUtils, ExtCtrls;
    
    type
      TComboBox = class(StdCtrls.TComboBox)
      private
        FStoredItems: TStringList;
        procedure FilterItems;
        procedure StoredItemsChange(Sender: TObject);
        procedure SetStoredItems(const Value: TStringList);
        procedure CNCommand(var AMessage: TWMCommand); message CN_COMMAND;
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
        property StoredItems: TStringList read FStoredItems write SetStoredItems;
      end;
    
    type
      TForm1 = class(TForm)
        ComboBox1: TComboBox;
        procedure FormCreate(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    constructor TComboBox.Create(AOwner: TComponent);
    begin
      inherited;
      AutoComplete := False;
      FStoredItems := TStringList.Create;
      FStoredItems.OnChange := StoredItemsChange;
    end;
    
    destructor TComboBox.Destroy;
    begin
      FStoredItems.Free;
      inherited;
    end;
    
    procedure TComboBox.CNCommand(var AMessage: TWMCommand);
    begin
      // we have to process everything from our ancestor
      inherited;
      // if we received the CBN_EDITUPDATE notification
      if AMessage.NotifyCode = CBN_EDITUPDATE then
        // fill the items with the matches
        FilterItems;
    end;
    
    procedure TComboBox.FilterItems;
    var
      I: Integer;
      Selection: TSelection;
    begin
      // store the current combo edit selection
      SendMessage(Handle, CB_GETEDITSEL, WPARAM(@Selection.StartPos),
        LPARAM(@Selection.EndPos));
      // begin with the items update
      Items.BeginUpdate;
      try
        // if the combo edit is not empty, then clear the items
        // and search through the FStoredItems
        if Text <> '' then
        begin
          // clear all items
          Items.Clear;
          // iterate through all of them
          for I := 0 to FStoredItems.Count - 1 do
            // check if the current one contains the text in edit
            if ContainsText(FStoredItems[I], Text) then
              // and if so, then add it to the items
              Items.Add(FStoredItems[I]);
        end
        // else the combo edit is empty
        else
          // so then we'll use all what we have in the FStoredItems
          Items.Assign(FStoredItems)
      finally
        // finish the items update
        Items.EndUpdate;
      end;
      // and restore the last combo edit selection
      SendMessage(Handle, CB_SETEDITSEL, 0, MakeLParam(Selection.StartPos,
        Selection.EndPos));
    end;
    
    procedure TComboBox.StoredItemsChange(Sender: TObject);
    begin
      if Assigned(FStoredItems) then
        FilterItems;
    end;
    
    procedure TComboBox.SetStoredItems(const Value: TStringList);
    begin
      if Assigned(FStoredItems) then
        FStoredItems.Assign(Value)
      else
        FStoredItems := Value;
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    var
      ComboBox: TComboBox;
    begin
      // here's one combo created dynamically
      ComboBox := TComboBox.Create(Self);
      ComboBox.Parent := Self;
      ComboBox.Left := 10;
      ComboBox.Top := 10;
      ComboBox.Text := 'Br';
    
      // here's how to fill the StoredItems
      ComboBox.StoredItems.BeginUpdate;
      try
        ComboBox.StoredItems.Add('Mr John Brown');
        ComboBox.StoredItems.Add('Mrs Amanda Brown');
        ComboBox.StoredItems.Add('Mr Brian Jones');
        ComboBox.StoredItems.Add('Mrs Samantha Smith');
      finally
        ComboBox.StoredItems.EndUpdate;
      end;
    
      // and here's how to assign the Items of the combo box from the form 
      // to the StoredItems; note that if you'll use this, you have to do
      // it before you type something into the combo's edit, because typing 
      // may filter the Items, so they would get modified
      ComboBox1.StoredItems.Assign(ComboBox1.Items);
    end;    
    
    end.

    Thanks for the heart! With a little reworking, I think that is quite right.

    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, StrUtils, ExtCtrls;
    
    type
      TComboBox = class(StdCtrls.TComboBox)
      private
        FStoredItems: TStringList;
        procedure FilterItems;
        procedure StoredItemsChange(Sender: TObject);
        procedure SetStoredItems(const Value: TStringList);
        procedure CNCommand(var AMessage: TWMCommand); message CN_COMMAND;
      protected
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
        property StoredItems: TStringList read FStoredItems write SetStoredItems;
      end;
    
    type
      TForm1 = class(TForm)
        procedure FormCreate(Sender: TObject);
      private
      public
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    {}constructor TComboBox.Create(AOwner: TComponent);
        begin
          inherited;
          AutoComplete := False;
          FStoredItems := TStringList.Create;
          FStoredItems.OnChange := StoredItemsChange;
        end;
    
    {}destructor TComboBox.Destroy;
        begin
          FStoredItems.Free;
          inherited;
        end;
    
    {}procedure TComboBox.CNCommand(var AMessage: TWMCommand);
        begin
          // we have to process everything from our ancestor
          inherited;
          // if we received the CBN_EDITUPDATE notification
          if AMessage.NotifyCode = CBN_EDITUPDATE then begin
            // fill the items with the matches
            FilterItems;
          end;
        end;
    
    {}procedure TComboBox.FilterItems;
        type
          TSelection = record
            StartPos, EndPos: Integer;
          end;
        var
          I: Integer;
          Selection: TSelection;
          xText: string;
        begin
          // store the current combo edit selection
          SendMessage(Handle, CB_GETEDITSEL, WPARAM(@Selection.StartPos), LPARAM(@Selection.EndPos));
    
          // begin with the items update
          Items.BeginUpdate;
          try
            // if the combo edit is not empty, then clear the items
            // and search through the FStoredItems
            if Text <> '' then begin
              // clear all items
              Items.Clear;
              // iterate through all of them
              for I := 0 to FStoredItems.Count - 1 do begin
                // check if the current one contains the text in edit
        //      if ContainsText(FStoredItems[I], Text) then
                if Pos( Text, FStoredItems[I])>0 then begin
                  // and if so, then add it to the items
                  Items.Add(FStoredItems[I]);
                end;
              end;
            end else begin
              // else the combo edit is empty
              // so then we'll use all what we have in the FStoredItems
              Items.Assign(FStoredItems)
            end;
          finally
            // finish the items update
            Items.EndUpdate;
          end;
    
          // and restore the last combo edit selection
          xText := Text;
          SendMessage(Handle, CB_SHOWDROPDOWN, Integer(True), 0);
          if (Items<>nil) and (Items.Count>0) then begin
            ItemIndex := 0;
          end else begin
            ItemIndex := -1;
          end;
          Text := xText;
          SendMessage(Handle, CB_SETEDITSEL, 0, MakeLParam(Selection.StartPos, Selection.EndPos));
    
        end;
    
    {}procedure TComboBox.StoredItemsChange(Sender: TObject);
        begin
          if Assigned(FStoredItems) then
            FilterItems;
        end;
    
    {}procedure TComboBox.SetStoredItems(const Value: TStringList);
        begin
          if Assigned(FStoredItems) then
            FStoredItems.Assign(Value)
          else
            FStoredItems := Value;
        end;
    
    //=====================================================================
    
    {}procedure TForm1.FormCreate(Sender: TObject);
        var
          ComboBox: TComboBox;
          xList:TStringList;
        begin
    
          // here's one combo created dynamically
          ComboBox := TComboBox.Create(Self);
          ComboBox.Parent := Self;
          ComboBox.Left := 8;
          ComboBox.Top := 8;
          ComboBox.Width := Width-16;
    //    ComboBox.Style := csDropDownList;
    
          // here's how to fill the StoredItems
          ComboBox.StoredItems.BeginUpdate;
          try
            xList:=TStringList.Create;
            xList.LoadFromFile('list.txt');
            ComboBox.StoredItems.Assign( xList);
          finally
            ComboBox.StoredItems.EndUpdate;
          end;
    
          ComboBox.DropDownCount := 24;
    
          // and here's how to assign the Items of the combo box from the form
          // to the StoredItems; note that if you'll use this, you have to do
          // it before you type something into the combo's edit, because typing
          // may filter the Items, so they would get modified
          ComboBox.StoredItems.Assign(ComboBox.Items);
        end;
    
    end.
  • 相关阅读:
    [ 打败 IE 的葵花宝典 ] IE6中css常见BUG全集及解决方案
    js获取URl传递的参数
    几种C#框架提供的数据结构对单值查找的效率比较
    C#Hashtable与Dictionary性能
    Sql server 使用存储过程分页显示
    arrayList使用 与 foreach 使用
    LeetCode 047. 全排列 II DFS
    LeetCode 046. 全排列 dfs 和 dfs_swap
    LeetCode 048. 旋转图像
    LeetCode 040. 组合总和 II 非SET去重
  • 原文地址:https://www.cnblogs.com/shangdawei/p/4036227.html
Copyright © 2020-2023  润新知