首页 > 代码库 > How to make a combo box with fulltext search autocomplete support?

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;interfaceuses  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 well 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  // heres one combo created dynamically  ComboBox := TComboBox.Create(Self);  ComboBox.Parent := Self;  ComboBox.Left := 10;  ComboBox.Top := 10;  ComboBox.Text := Br;  // heres 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 heres how to assign the Items of the combo box from the form   // to the StoredItems; note that if youll use this, you have to do  // it before you type something into the combos 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;interfaceuses  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 well 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      // heres one combo created dynamically      ComboBox := TComboBox.Create(Self);      ComboBox.Parent := Self;      ComboBox.Left := 8;      ComboBox.Top := 8;      ComboBox.Width := Width-16;//    ComboBox.Style := csDropDownList;      // heres 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 heres how to assign the Items of the combo box from the form      // to the StoredItems; note that if youll use this, you have to do      // it before you type something into the combos edit, because typing      // may filter the Items, so they would get modified      ComboBox.StoredItems.Assign(ComboBox.Items);    end;end.

 

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