web-dev-qa-db-ja.com

全文検索オートコンプリートをサポートするコンボボックスを作成する方法

ユーザーがTComboBoxアイテムから2番目または3番目の単語を入力でき、そのアイテムがAutoSuggestドロップダウンオプションに表示されるようにしたい

たとえば、コンボボックスには次のアイテムが含まれています。

  • ジョン・ブラウン氏
  • アマンダ・ブラウン夫人
  • ブライアンジョーンズ氏
  • サマンサ・スミス夫人

ユーザーが「Br」と入力すると、ドロップダウンが表示されます。

  • ジョン・ブラウン氏
  • アマンダ・ブラウン夫人
  • ブライアンジョーンズ氏

ユーザーが「Jo」と入力すると、ドロップダウンが表示されます。

  • ジョン・ブラウン氏
  • ブライアンジョーンズ氏

問題は、AutoSuggest機能には、ユーザーが入力したもので始まるドロップダウンリストの項目のみが含まれるため、上記の例ではドロップダウンに何も表示されないということです。

この問題を回避するためにIAutoCompleteインターフェースやその他の関連インターフェースを使用することは可能ですか?

32
Brendanator

次の例では、挿入された TComboBox コンポーネントのクラスを使用しています。元のクラスとの主な違いは、アイテムが別のStoredItemsプロパティに格納されるのではなく、
Items 通常どおり(単純化のために使用)。

StoredItemsOnChange イベントによって監視されており、それらを変更するたびに(たとえば、この文字列リストに追加または削除することによって)、現在のフィルターはそれを反映しますコンボ時でも
リストはドロップダウンされます。

ここでの主なポイントは 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.
33
TLama

このコードは実際にはかなり良かったです。コンボがドロップされたときにメッセージを処理するバグを修正し、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.
3
mprot

心をありがとう!少し手直ししたので、それはまったく正しいと思います。

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.
2
NikB

処理されたOnDropDownイベントのセットアップで、次の項目でフィルターされたTComboBoxアイテム:

外部の完全な文字列リストから。または、独自のTComboBox(TCustomComboBox)の子孫を記述します。

0
g2mk