2012-02-27 15 views
28

Vorrei un utente sia in grado di digitare nella seconda o terza parola da una voce TComboBox e per questo oggetto ad apparire nei AutoSuggest opzioni discesaCome creare una casella combinata con supporto per il completamento automatico della ricerca di testo completo?

Per esempio, una casella combinata contiene gli elementi:

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

Quando l'utente digita "BR" i display a discesa:

  • Mr John Brown
  • signora Amanda Brown
  • Brian Jones

e quando l'utente digita "Jo" il menu a discesa visualizza:

  • Mr John Brown
  • Brian Jones

Il problema è che la funzionalità AutoSuggest include solo gli elementi nell'elenco a discesa che iniziano con ciò che l'utente ha di ingresso e così negli esempi precedenti non viene visualizzato nel menu a discesa.

È possibile utilizzare l'interfaccia IAutoComplete e/o altre interfacce correlate per ovviare a questo problema?

+0

Buona domanda, sto pensando di usare qualcosa di simile in futuro. (Per un controllo indirizzo email). Ma temo che non sia possibile con un TComboBox standard. –

+0

qual è la differenza tra "dall'inizio" e "non dall'inizio" in questo caso (tecnicamente)? – teran

+1

Hai bisogno di qualcosa di simile: http://stackoverflow.com/questions/7696075/need-a-combobox-with-filtering –

risposta

28

L'esempio seguente utilizza la classe interposta del componente TComboBox. La differenza principale rispetto alla classe originale è che gli articoli sono archiviati nella proprietà StoredItems separata anziché Items di Items come di consueto (utilizzata per semplicità).

Il StoredItems sono osservati dall'evento OnChange e ogni volta che li si cambia (per esempio aggiungendo o eliminando da questa lista di stringhe), il filtro corrente rifletterà anche quando la lista combinata
è caduto giù.

Il punto principale qui è prendere la notifica del messaggio WM_COMMANDCBN_EDITUPDATE che viene inviata ogni volta che il testo di modifica combinato viene modificato ma non ancora visualizzato.Quando arriva, cerca semplicemente nell'elenco StoredItems per quello che hai digitato nella tua modifica combinata e riempi la proprietà Items con le corrispondenze.

Per la ricerca del testo viene utilizzato il ContainsText in modo che la ricerca sia senza distinzione tra maiuscole e minuscole. Dimenticato di menzionare,
la funzione AutoComplete deve essere disattivata perché ha una logica propria, non accettata, per questo scopo.

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

Questo è fantastico. Grazie TLama – Brendanator

0

nell'evento OnDropDown articoli configurazione TComboBox trattati filtrati per:

dall'esterno lista stringa completa. O meglio scrivi il tuo discendente TComboBox (TCustomComboBox).

+3

In che modo l'evento OnDropDown ha qualcosa a che fare con la digitazione nella casella combinata? –

2

Grazie per il cuore! Con un po 'di rielaborazione, penso che sia giusto.

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

Probabilmente dovresti spiegare cosa hai cambiato e perché. – EMBarbosa

+0

'FilterItems' è stato modificato. –

1

Questo codice era abbastanza buono bug in realtà, ho appena riparato con la gestione dei messaggi quando combinata è caduto giù, alcune interazioni minori con comportamenti TComboBox e ne ha fatto un po 'user-friendly. Per usarlo basta richiamare InitSmartCombo dopo aver riempito l'elenco degli articoli.

TSmartComboBox è caduta in sostituzione di TComboBox, se si richiama InitSmartCombo si comporta come intelligente combo, altrimenti si comporta come standard 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

Sto usando Delphi7. Dove posso trovare l'oggetto TSelection? –

+0

Non esiste un oggetto 'TSelection' in Delphi7. Istan delle sue proprietà, io uso due valori interi: 'wStartPos',' wEndPos', che metto in 'SendMessage' della procedura' FilterItems'. Così: 'SendMessage (Handle, CB_GETEDITSEL, WPARAM (wStartPos), LPARAM (wEndPos));' –

Problemi correlati