Need a ComboBox with filtering

2020-07-18 07:18发布

问题:

I need some type of ComboBox which can load it's items from DB. While I type some text in to it, it should filter it's list, leaving only those items, that have my text somewhere (at the beginning, middle...). Not all my DataSet's have filtering capabilities, so it is not possible to use them. Is there any ready to use components with such abilities? I have tried to search in JVCL, but without luck.

回答1:

You could try customizing the autocomplete functionality of a regular ComboBox. Loading its items from a DB is easy:

    ComboBox1.Items.Clear;   
    while not Table1.Eof do begin
      ComboBox1.Items.AddObject( Table1.FieldByName('Company').AsString,  
       TObject(Table1.FieldByName('CustNo').AsInteger) );
      Table1.Next;   
    end;

As far as the auto-complete for middle-of-word matching, you might try adapting this code. The functionality that matches at the beginning of the text in the Items is enabled by setting AutoComplete to true, and needs to be turned off before you try writing your own OnChange event handler that does auto-complete. I suggest that you could more safely do the match and selection on the enter key, because attempting to do it on the fly makes things quite hairy, as the code below will show you:

Here's my basic version: Use a regular combobox with onKeyDown, and onChange events, and AutoComplete set to false, use above code to populate it, and these two events

procedure TForm2.ComboBox1Change(Sender: TObject);
var
  SearchStr,FullStr: string;
  i,retVal,FoundIndex: integer;
  ctrl:TComboBox;
begin
  if fLastKey=VK_BACK then
       exit;

  // copy search pattern
  ctrl := (Sender as TCombobox);
  SearchStr := UpperCase(ctrl.Text);
  FoundIndex := -1;
  if SearchStr<>'' then
  for i := 0 to ctrl.Items.Count-1 do begin
    if Pos(SearchStr, UpperCase(ctrl.Items[i]))>0 then
    begin
       FoundIndex := i;
       fsearchkeys := ctrl.Text;
       break;
    end;
  end;

  if (FoundIndex>=0) then
  begin
    retVal := ctrl.Perform(CB_SELECTSTRING, 0, LongInt(PChar(ctrl.Items[FoundIndex]))) ;

    if retVal > CB_Err then
    begin
      ctrl.ItemIndex := retVal;
      ctrl.SelStart := Pos(SearchStr,UpperCase(ctrl.Text))+Length(SearchStr)-1;
      ctrl.SelLength := (Length(ctrl.Text) - Length(SearchStr));
    end; // retVal > CB_Err

  end; // lastKey <> VK_BACK

end;

procedure TForm2.ComboBox1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  fLastKey := Key;
end;

Suppose the contents of the list are "David Smith", and "Mark Smithers". You type S and it matches the first letter of the last name, in David Smith. Now it shows David Smith with the "David S" part not selected, and the "mith" part selected (so that the next characters you type will replace the auto completed portion, a standard auto-complete technique). Note that the above code has had to prefix the S you typed with the "David " part you didn't type. If you are a lot more clever than me, you can find a way to remember that the user typed "s" and then, maybe an "m", followed by some more letters, and eventually having typed "Smithe", match Smithers, instead of always David smith. Also note that you can only set the SelStart and SelLength to select a continuous length of a string.

The code I have provided will only work when the list of items never contains any repeated substrings. There are good reasons why the Windows Common Control combobox "autocomplete" functionality only works with prefix matching, and not mid-string matching.

Since anything that would implement mid-string matching should probably draw the part you typed in not-selected, and since that not-selected part would be in mid-string, you would probably need to write your own control from scratch and not rely on the TComboBox base code, and its underlying MS Common Controls combobox functionality.



回答2:

DevExpress' "TcxExtLookupCombobox" has this capability - and more. Might be overkill though.