Как сделать поле со списком с поддержкой автозаполнения полнотекстового поиска? - PullRequest
32 голосов
/ 27 февраля 2012

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

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

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

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

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

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

  • Мистер Джон Браун
  • Мистер Брайан Джонс

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

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

Ответы [ 4 ]

33 голосов
/ 28 февраля 2012

В следующем примере используется вставленный класс компонента 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.
3 голосов
/ 07 мая 2017

На самом деле этот код был довольно хорошим, я просто исправил ошибку с обработкой сообщений, когда выпадающий список выпал, некоторые незначительные взаимодействия с поведением 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.
2 голосов
/ 02 декабря 2012

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

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.
0 голосов
/ 28 февраля 2012

При обработке события OnDropDown события TComboBox отфильтровываются по:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...