2012-02-27 64 views
28

我想用戶能夠從TComboBox項目,該項目在第二或第三個單詞鍵入出現在AutoSuggest下拉選項如何使用全文搜索自動完成支持製作組合框?

例如,組合框包含項目:

  • 約翰·布朗先生
  • 太太阿曼達·布朗
  • 先生布賴恩·瓊斯
  • 夫人薩曼莎·史密斯

當用戶鍵入 「BR」 下拉列表中顯示:

  • 先生約翰·布朗
  • 太太阿曼達·布朗
  • 先生布賴恩·瓊斯

,當用戶鍵入 「喬」下拉菜單顯示:

  • John Brown先生
  • 先生布賴恩·瓊斯

的問題是,AutoSuggest功能只包括在與什麼用戶輸入等上面什麼都不會出現在下拉列表中的例子開始下拉列表中的項目。

是否可以使用IAutoComplete接口和/或其他相關接口來解決此問題?

+0

好問題,我打算用類似的東西在未來。 (用於電子郵件地址控制)。但是恐怕這對標準的TComboBox來說是不可能的。 – 2012-02-27 14:25:15

+0

在這種情況下(技術上),「從一開始」和「不從一開始」之間有什麼區別? – teran 2012-02-27 14:46:40

+1

你需要這樣的東西:http://stackoverflow.com/questions/7696075/need-a-combobox-with-filtering – 2012-02-27 15:10:29

回答

28

以下示例使用TComboBox組件的插入類。與原始類別的主要區別在於,這些項目通常(由於簡單性而使用)存儲在單獨的StoredItems屬性中,而不是
Items中。

StoredItems正在由OnChange事件看,只要你(通過添加或從該字符串列表中刪除例如)改變他們,當組合
列表倒伏當前過濾器甚至會體現它。

這裏的要點是捕獲WM_COMMAND消息通知CBN_EDITUPDATE,每當組合編輯文本被更改但尚未呈現時,都會發送該消息。當它到達時,您只需在StoredItems列表中搜索您在組合編輯中輸入的內容,並用匹配填充Items屬性。

對於文本搜索使用ContainsText,因此搜索不區分大小寫。忘了提及,
AutoComplete功能必須關閉,因爲它有它自己的,不受歡迎的邏輯,爲此目的。

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. 
+4

這太棒了。感謝TLama – Brendanator 2012-03-05 16:45:28

0

在通過過濾處理OnDropDown事件設置TComboBox項目:

從外部全字符串列表。或者最好編寫你自己的TComboBox(TCustomComboBox)後代。

+3

OnDropDown事件與在組合框中鍵入有什麼關係? – 2012-02-28 08:09:03

2

謝謝你的心!稍加修改,我認爲這是對的。

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. 
+5

你應該解釋你改變了什麼,爲什麼。 – EMBarbosa 2012-12-04 11:55:08

+0

'FilterItems'已更改。 – 2016-02-14 16:07:04

1

此代碼是相當不錯其實,我只是修正了當組合體被掉下來處理消息,與TComboBox行爲一些小的互動,並使它成爲小用戶友好。要使用它,只需在填寫Items列表後調用InitSmartCombo。

TSmartComboBox是下降替代TComboBox,如果你調用InitSmartCombo它表現爲智能組合,否則它作爲標準TComboBox

unit SmartCombo; 

interface 

uses stdctrls,classes,messages,controls,windows,sysutils; 

type 
    TSmartComboBox = class(TComboBox) 
    // Usage: 
    // Same as TComboBox, just invoke InitSmartCombo after Items list is filled with data. 
    // After InitSmartCombo is invoked, StoredItems is assigned and combo starts to behave as a smart combo. 
    // If InitSmartCombo is not invoked it acts as standard TComboBox, it is safe to bulk replace all TComboBox in application with TSmartComboBox 
    private 
    FStoredItems: TStringList; 
    dofilter:boolean; 
    storeditemindex:integer; 
    procedure FilterItems; 
    procedure StoredItemsChange(Sender: TObject); 
    procedure SetStoredItems(const Value: TStringList); 
    procedure CNCommand(var AMessage: TWMCommand); message CN_COMMAND; 
    protected 
    procedure KeyPress(var Key: Char); override; 
    procedure CloseUp; override; 
    procedure Click; override; 
    public 
    constructor Create(AOwner: TComponent); override; 
    destructor Destroy; override; 
    property StoredItems: TStringList read FStoredItems write SetStoredItems; 
    procedure InitSmartCombo; 
    end; 

implementation 

procedure TSmartComboBox.KeyPress(var Key: Char); // combo dropdown must be done in keypress, if its done on CBN_EDITUPDATE it messes up whole message processing mumbo-jumbo 
    begin 
     inherited; 
     if dofilter and not (ord(key) in [13,27]) then begin 
     if (items.Count<>0) and not droppeddown then SendMessage(Handle, CB_SHOWDROPDOWN, 1, 0) // something matched -> dropdown combo to display results 
     end; 
    end; 

procedure TSmartComboBox.CloseUp;  // ugly workaround for some wierd combobox/modified code interactions 
var x:string; 
    begin 
     if dofilter then begin 
     if (items.count=1) and (itemindex=0) then text:=items[itemindex] 
     else if ((text<>'') and (itemindex<>-1) and (text<>items[itemindex])) or ((text='') and(itemindex=0)) then begin 
      storeditemindex:=itemindex; 
      x:=text; 
      itemindex:=items.indexof(text); 
      if itemindex=-1 then text:=x; 
     end 
     else storeditemindex:=-1; 
     end; 
     inherited; 
    end; 

procedure TSmartComboBox.Click;  // ugly workaround for some weird combobox/modified code interactions 
    begin 
     if dofilter then begin 
     if storeditemindex<>-1 then itemindex:=storeditemindex; 
     storeditemindex:=-1; 
     end; 
     inherited; 
    end; 

procedure TSmartComboBox.InitSmartCombo; 
    begin 
     FStoredItems.OnChange:=nil; 
     StoredItems.Assign(Items); 
     AutoComplete := False; 
     FStoredItems.OnChange := StoredItemsChange; 
     dofilter:=true; 
     storeditemindex:=-1; 
    end; 

constructor TSmartComboBox.Create(AOwner: TComponent); 
    begin 
     inherited; 
     FStoredItems := TStringList.Create; 
     dofilter:=false; 
    end; 

destructor TSmartComboBox.Destroy; 
    begin 
     FStoredItems.Free; 
     inherited; 
    end; 

procedure TSmartComboBox.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) and dofilter then begin 
     // fill the items with the matches 
     FilterItems; 
     end; 
    end; 

procedure TSmartComboBox.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 begin 
      // check if the current one contains the text in edit, case insensitive 
      if (Pos(uppercase(Text), uppercase(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 

     SendMessage(Handle, CB_SETEDITSEL, 0, MakeLParam(Selection.StartPos, Selection.EndPos)); 
    end; 

procedure TSmartComboBox.StoredItemsChange(Sender: TObject); 
    begin 
     if Assigned(FStoredItems) then 
     FilterItems; 
    end; 

procedure TSmartComboBox.SetStoredItems(const Value: TStringList); 
    begin 
     if Assigned(FStoredItems) then 
     FStoredItems.Assign(Value) 
     else 
     FStoredItems := Value; 
    end; 

procedure Register; 
begin 
    RegisterComponents('Standard', [TSmartComboBox]); 
end; 

end. 
+0

我使用Delphi7。我在哪裏可以找到TSelection對象? – 2018-02-21 19:40:07

+0

Delphi7中沒有'TSelection'對象。由於它的屬性,我使用了兩個整數值:'wStartPos','wEndPos',並將其放入'FilterItems'過程的SendMessage中。像這樣: SendMessage(Handle,CB_GETEDITSEL,WPARAM(wStartPos),LPARAM(wEndPos));'' – 2018-02-21 19:50:25