Как сделать поле со списком с поддержкой автозаполнения полнотекстового поиска?

Я хочу, чтобы пользователь мог вводить второе или третье слово из TComboBox элемента и чтобы этот элемент отображался в раскрывающемся меню AutoSuggest

Например, поле со списком содержит элементы:

  • Г-н Джон Браун
  • Миссис Аманда Браун
  • Г-н Брайан Джонс
  • Миссис Саманта Смит

Когда пользователь вводит «Br», в раскрывающемся списке отображается:

  • Г-н Джон Браун
  • Миссис Аманда Браун
  • Г-н Брайан Джонс

и когда пользователь вводит «Джо», в раскрывающемся списке отображается:

  • Г-н Джон Браун
  • Г-н Брайан Джонс

Проблема в том, что функция AutoSuggest включает только элементы в раскрывающемся списке, которые начинаются с того, что ввел пользователь, поэтому в приведенных выше примерах в раскрывающемся списке ничего не отображается.

Можно ли использовать IAutoComplete интерфейс и / или другие связанные интерфейсы, чтобы обойти эту проблему?


person Brendanator    schedule 27.02.2012    source источник
comment
Хороший вопрос, я планирую использовать что-то подобное в будущем. (Для управления адресом электронной почты). Но я боюсь, что это невозможно со стандартным TComboBox.   -  person The_Fox    schedule 27.02.2012
comment
в чем разница между с самого начала и не с самого начала в данном случае (технически)?   -  person teran    schedule 27.02.2012
comment
Вам нужно что-то вроде этого: stackoverflow.com/questions/7696075 /   -  person Jens Mühlenhoff    schedule 27.02.2012
comment
Не думаю, что ссылка Йенса отвечает на этот вопрос. Ответ дает пример функции AutoAppend в средней строке в отличие от функции AutoSuggest, которая мне нужна.   -  person Brendanator    schedule 27.02.2012
comment
Мы решили эту проблему, используя набор данных и проанализировав его, и добавили результат в другой StringList, который был назначен. Требовались некоторые обходные пути, но они работают.   -  person RBA    schedule 27.02.2012
comment
Функция предложения средней строки была в связанной статье в ответе, предложенном @ JensMühlenhoff. К сожалению, сайт delphi3000.com (который был целью) сейчас кажется мертвым.   -  person Marjan Venema    schedule 27.02.2012
comment
Разве это не похоже? stackoverflow.com/questions/2012208 /   -  person EMBarbosa    schedule 29.02.2012
comment
@EMBarbosa, да, но IAutoComplete интерфейс не поддерживает полнотекстовый поиск, работает только с текстом с самого начала. Единственный способ - ИМХО реализовать самостоятельно. Даже такие сообщения, как CB_FINDSTRING, не учитываются при совпадении строк где-либо еще, кроме как с начала.   -  person TLama    schedule 29.02.2012
comment
@TLama Ой. Я думал, что изменив параметр IAutoComplete2 на включение ACO_AUTOSUGGEST, он сделает это ... спасибо.   -  person EMBarbosa    schedule 29.02.2012
comment
Предлагаю вам изучить реализацию TCnProcListComboBox от CnPack. В CnPack есть комбо, которое делает это, но показывает процедуры и функции открытого .pas файла. Я не уверен, где они это реализовали, поэтому я не ставлю это как ответ. Попробуйте это.   -  person EMBarbosa    schedule 29.02.2012
comment
@EMBarbosa, хороший улов, но вы увидите там настраиваемый элемент управления, а не TComboBox.   -  person TLama    schedule 01.03.2012


Ответы (5)


В следующем примере используется вставленный класс компонента TComboBox. Основное отличие от исходного класса заключается в том, что элементы хранятся в отдельном свойстве StoredItems вместо
_ 3_ как обычно (используется для простоты).

StoredItems отслеживаются событием OnChange и всякий раз, когда вы меняете их (например, добавляя или удаляя из этого списка строк), текущий фильтр будет отражать их, даже когда комбинированный список
выпадает.

Главное здесь - поймать WM_COMMAND уведомление о сообщении CBN_EDITUPDATE, который отправляется всякий раз, когда текст комбинированного редактирования изменяется, но еще не отображается. Когда он поступит, вы просто выполните поиск в StoredItems списке того, что вы ввели при редактировании комбо, и заполните _ 9_ с совпадениями.

Для текстового поиска используется 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.
person TLama    schedule 27.02.2012
comment
Это фантастика. Спасибо TLama - person Brendanator; 05.03.2012
comment
Мне кажется, что у вашего кода есть проблема с правом собственности. TComboBox.SetStoredItems иногда становится владельцем Value, а иногда нет. Это делает его очень неудобным в использовании. (Но это не невозможно, поскольку вы можете определить, что произойдет в каждом конкретном случае.) - person Andreas Rejbrand; 22.08.2019

Этот код был на самом деле неплохим, я просто исправил ошибку с обработкой сообщений при выпадении комбо, некоторые незначительные взаимодействия с поведением TComboBox и сделал его немного более удобным для пользователя. Чтобы использовать его, просто вызовите 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.
person mprot    schedule 07.05.2017
comment
Я использую Delphi7. Где я могу найти объект TSelection? - person Ivan Z; 21.02.2018
comment
В Delphi7 нет объекта TSelection. Помимо его свойств, я использую два целых значения: wStartPos, wEndPos, которые я помещаю в SendMessage процедуры FilterItems. Как это: SendMessage(Handle, CB_GETEDITSEL, WPARAM(wStartPos), LPARAM(wEndPos)); - person Ivan Z; 21.02.2018

Спасибо за сердечко! Я думаю, что после небольшой доработки это вполне правильно.

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.
person NikB    schedule 02.12.2012
comment
Вам, вероятно, следует объяснить, что вы изменили и почему. - person EMBarbosa; 04.12.2012
comment
FilterItems был изменен. - person Ivan Z; 14.02.2016

В обработанной настройке события OnDropDown элементы TComboBox фильтруются по:

из внешнего полного списка строк. Или лучше написать своего собственного потомка TComboBox (TCustomComboBox).

person g2mk    schedule 27.02.2012
comment
Как событие OnDropDown связано с вводом текста в поле со списком? - person Martin Reiner; 28.02.2012

Добавлен код для пользователей Unicode.

Собственно тестировалось только на корейском :(

Прикладная функция

  • Предотвращает ошибки, возникающие при возникновении OnExit или DropDown при входе в Unicode.
  • Предотвращает ошибки, возникающие при исправлении текста или дополнительном вводе после выбора элемента

Измененное содержание кода

  • @ Предотвращает ошибку - вводимые значения добавляются при выборе списка при вводе в Юникоде
  • @Filtering применяется к каждому набираемому Unicode, если он вводится после конца текста.
  • @ Обработка исключений при входе в Юникод после выделения нескольких текстов
  • @ Обработка исключений при дополнительном вводе Unicode, когда уже есть символ в редактировании и список закрыт

Источник...

unit SmartCombo;

interface

uses StdCtrls, Classes, Messages, Controls, Windows, SysUtils, StrUtils;

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
    FChar: Char; // @for UNICODE Filter
    FIgnoreChar: boolean; // @for UNICODE Edit
    FStoredItems: TStringList;
    doFilter: boolean;
    StoredItemIndex: Integer;

    procedure StoredItemsChange(Sender: TObject);
    procedure SetStoredItems(const Value: TStringList);
    procedure CNCommand(var AMessage: TWMCommand); message CN_COMMAND;
    function GetXText(var Key: Char): string;
    function GetXSelStart: Integer;
  protected
    procedure KeyPress(var Key: Char); override;

    // @Prevents a bug - typing values are appended when selecting a list while typing in Unicode
    procedure EditWndProc(var Message: TMessage); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure FilterItems;
    procedure InitSmartCombo;

    property StoredItems: TStringList read FStoredItems write SetStoredItems;
  end;

implementation

function TSmartComboBox.GetXText(var Key: Char): string;
var
  tmp: string;
begin
  if (Text = '') then // empty edit box
    result := ''
  else if SelLength > 0 then // has selection
  begin
    tmp := Copy(Text, SelStart + 1, SelLength);
    result := ReplaceStr(Text, tmp, '');
  end
  else // not empty edit box and no selection
  begin
    tmp := Copy(Text, 1, SelStart);
    result := tmp + Key;
    result := result + Copy(Text, SelStart + 1, Length(Text) - SelStart);
    Key := #0;
  end;
end;

function TSmartComboBox.GetXSelStart: Integer;
begin
  // empty edit box or has selection
  if (Text = '') or (SelLength > 0) then
    result := SelStart
  else // not empty edit box and no selection
    result := SelStart + 1;
end;

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
var
  xSelStart: Integer;
  xText: string;
begin
  inherited;

  if Ord(Key) = 8 then
    FChar := Key;

  if doFilter and not(Ord(Key) in [8, 13, 27]) then // BackSpace, Enter, ESC
  begin
    FChar := Key;

    if DroppedDown then
      Exit;

    if Items.Count = 0 then
      Exit;

    // backup
    xSelStart := GetXSelStart;
    xText := GetXText(Key);

    // dropdown
    SendMessage(Handle, CB_SHOWDROPDOWN, 1, 0);

    if xText.IsEmpty then
      Exit;

    // restore
    Text := xText;
    SelStart := xSelStart;
  end;
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;
  FIgnoreChar := False;
  doFilter := False;
end;

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

procedure TSmartComboBox.EditWndProc(var Message: TMessage);
var
  OldText: string;
begin
  case Message.Msg of
    WM_IME_ENDCOMPOSITION:
      begin
        OldText := Self.Text;
        inherited;
        FIgnoreChar := Self.Text = OldText;
      end;
    WM_CHAR:
      begin
        FIgnoreChar := False;
        inherited;
      end;
    WM_IME_CHAR:
      begin
        if FIgnoreChar then
          FIgnoreChar := False
        else
          inherited;
      end;
  else
    inherited;
  end;
end;

procedure TSmartComboBox.CNCommand(var AMessage: TWMCommand);
begin
  // we have to process everything from our ancestor
  inherited;

  // @Filtering is applied to each Unicode being typed if it is being entered after the end of the text.
  // @If you are typing in the middle of the text, do not apply filtering to the Unicode being typed
  // (filtering is applied in units of completed Unicode characters)
  if (SelStart < Length(Text)) and (FChar = #0) then
    Exit;

  // if we received the CBN_EDITUPDATE notification
  if (AMessage.NotifyCode = CBN_EDITUPDATE) and doFilter then
  begin
    // fill the items with the matches
    FilterItems;
  end;

  FChar := #0;
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 ContainsText(FStoredItems[I], Text) 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;

end.
person Jesse Lee    schedule 19.12.2020
comment
Спасибо, что поделились хорошим кодом. @TLama - person Jesse Lee; 19.12.2020
comment
Я получаю ошибки по необъявленным идентификаторам - SelLength, SelStart, Items, AutoComplete, BeginUpdate, Clear, Add, Assign, EndUpdate, DroppedDown и Count. Я не совсем уверен, относятся ли эти ошибки к TSelection, упомянутому @IvanZ выше stackoverflow.com/a/43826691/13810710. Помоги пожалуйста.. - person RickyBelmont; 01.02.2021
comment
Я исправил ошибки сейчас. Я забыл, что это единица, а не форма. Итак, я создал только юнит и сохранил его в проекте. Затем я создал другую форму, в которую я поместил свой Combobox со списком из моей БД. Я еще не пробовал это сделать, но у меня возникли проблемы с вызовом InitSmartCombo с этого устройства. Я уже применил юнит. Я не мог вызвать его с помощью этого - InitSmartCombo после загрузки из БД. Есть ли правильный способ вызвать модуль из другой формы? Помоги пожалуйста... - person RickyBelmont; 01.02.2021
comment
Вот что я сделал в форме, которую создал с помощью combobox, но безуспешно: procedure TForm1.FormActivate (Sender: TObject); begin cmb1.Items.Clear; cds1.First; пока не cds1.eof, начинайте cmb1.Items.Add (cds1.FieldByName ('Name'). DisplayText); cds1.Next; конец; InitSmartCombo; конец; - person RickyBelmont; 01.02.2021