Search code examples
delphidelphi-xe2tbuttonededittcombobox

Is it possible to add a history list dropdown to Delphi's TButtonedEdit?


I'm using Delphi XE2's TButtonedEdit but I would like to add a dropdown list for the history (like TComboBox). I know that TComboBox is a glorified TEdit, so is there a message I can send to TButtonedEdit to add this functionality please? Thanks.


Solution

  • You can use the IAutoComplete2 interface to accomplish this task.

    Check this sample code (adapted for TButtonedEdit and Delphi XE2), based on this answer (Auto append/complete from text file to an edit box delphi) from Ken White

    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,  Vcl.ExtCtrls, Winapi.ActiveX,  Winapi.ShlObj,
      Vcl.Mask, Vcl.ImgList;
    
    
    type
      IACList = interface(IUnknown)
      ['{77A130B0-94FD-11D0-A544-00C04FD7d062}']
        function Expand(pszExpand : POLESTR) : HResult; stdcall;
      end;
    
    const
      //options for IACList2
      ACLO_NONE = 0;          // don't enumerate anything
      ACLO_CURRENTDIR = 1;    // enumerate current directory
      ACLO_MYCOMPUTER = 2;    // enumerate MyComputer
      ACLO_DESKTOP = 4;       // enumerate Desktop Folder
      ACLO_FAVORITES = 8;     // enumerate Favorites Folder
      ACLO_FILESYSONLY = 16;  // enumerate only the file system
    
    type
      IACList2 = interface(IACList)
      ['{470141a0-5186-11d2-bbb6-0060977b464c}']
        function SetOptions(dwFlag: DWORD): HResult; stdcall;
        function GetOptions(var pdwFlag: DWORD): HResult; stdcall;
      end;
    
      IAutoComplete = interface(IUnknown)
      ['{00bb2762-6a77-11d0-a535-00c04fd7d062}']
        function Init(hwndEdit: HWND; const punkACL: IUnknown;
          pwszRegKeyPath, pwszQuickComplete: POLESTR): HResult; stdcall;
        function Enable(fEnable: BOOL): HResult; stdcall;
      end;
    
    const
      //options for IAutoComplete2
      ACO_NONE = 0;
      ACO_AUTOSUGGEST = $1;
      ACO_AUTOAPPEND = $2;
      ACO_SEARCH = $4;
      ACO_FILTERPREFIXES = $8;
      ACO_USETAB = $10;
      ACO_UPDOWNKEYDROPSLIST = $20;
      ACO_RTLREADING = $40;
    
    type
      IAutoComplete2 = interface(IAutoComplete)
      ['{EAC04BC0-3791-11d2-BB95-0060977B464C}']
        function SetOptions(dwFlag: DWORD): HResult; stdcall;
        function GetOptions(out pdwFlag: DWORD): HResult; stdcall;
      end;
    
      TEnumString = class(TInterfacedObject, IEnumString)
      private
        type
         TPointerList = array[0..0] of Pointer; //avoid bug of Classes.pas declaration TPointerList = array of Pointer;
        var
        FStrings: TStringList;
        FCurrIndex: integer;
      public
        //IEnumString
        function Next(celt: Longint; out elt;
            pceltFetched: PLongint): HResult; stdcall;
        function Skip(celt: Longint): HResult; stdcall;
        function Reset: HResult; stdcall;
        function Clone(out enm: IEnumString): HResult; stdcall;
        //VCL
        constructor Create;
        destructor Destroy;override;
      end;
    
      TACOption = (acAutoAppend, acAutoSuggest, acUseArrowKey);
      TACOptions = set of TACOption;
    
      TACSource = (acsList, acsHistory, acsMRU, acsShell);
    
    
      TButtonedEdit = class(Vcl.ExtCtrls.TButtonedEdit)
      private
    
        FACList: TEnumString;
        FAutoComplete: IAutoComplete;
        FACEnabled: boolean;
        FACOptions: TACOptions;
        FACSource: TACSource;
        function GetACStrings: TStringList;
        procedure SetACEnabled(const Value: boolean);
        procedure SetACOptions(const Value: TACOptions);
        procedure SetACSource(const Value: TACSource);
        procedure SetACStrings(const Value: TStringList);
      protected
        procedure CreateWnd; override;
        procedure DestroyWnd; override;
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
      published
        property ACEnabled: boolean read FACEnabled write SetACEnabled;
        property ACOptions: TACOptions read FACOptions write SetACOptions;
        property ACSource: TACSource read FACSource write SetACSource;
        property ACStrings: TStringList read GetACStrings write SetACStrings;
      end;
    
    
      TForm1 = class(TForm)
        ButtonedEdit1: TButtonedEdit;
        ImageList1: TImageList;
        procedure FormCreate(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm5;
    
    implementation
    
    {$R *.dfm}
    
    
    uses
      System.Win.ComObj;
    
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      ButtonedEdit1.ACEnabled:=True;
      ButtonedEdit1.ACOptions:=[acAutoAppend, acAutoSuggest, acUseArrowKey];
      ButtonedEdit1.ACSource:=acsList;
      ButtonedEdit1.ACStrings.Add('string 1');
      ButtonedEdit1.ACStrings.Add('string 2');
      ButtonedEdit1.ACStrings.Add('string 3');
      ButtonedEdit1.ACStrings.Add('string 4');
    end;
    
    { TEnumString }
    
    function TEnumString.Clone(out enm: IEnumString): HResult;
    begin
      Result := E_NOTIMPL;
      Pointer(enm) := nil;
    end;
    
    constructor TEnumString.Create;
    begin
      inherited Create;
      FStrings := TStringList.Create;
      FCurrIndex := 0;
    end;
    
    destructor TEnumString.Destroy;
    begin
      FStrings.Free;
      inherited;
    end;
    
    function TEnumString.Next(celt: Integer; out elt;
      pceltFetched: PLongint): HResult;
    var
      I: Integer;
      wStr: WideString;
    begin
      I := 0;
      while (I < celt) and (FCurrIndex < FStrings.Count) do
      begin
        wStr := FStrings[FCurrIndex];
        TPointerList(elt)[I] := CoTaskMemAlloc(2 * (Length(wStr) + 1));
        StringToWideChar(wStr, TPointerList(elt)[I], 2 * (Length(wStr) + 1));
        Inc(I);
        Inc(FCurrIndex);
      end;
      if pceltFetched <> nil then
        pceltFetched^ := I;
      if I = celt then
        Result := S_OK
      else
        Result := S_FALSE;
    end;
    
    function TEnumString.Reset: HResult;
    begin
      FCurrIndex := 0;
      Result := S_OK;
    end;
    
    function TEnumString.Skip(celt: Integer): HResult;
    begin
      if (FCurrIndex + celt) <= FStrings.Count then
      begin
        Inc(FCurrIndex, celt);
        Result := S_OK;
      end
      else
      begin
        FCurrIndex := FStrings.Count;
        Result := S_FALSE;
      end;
    end;
    
    { TACEdit }
    
    constructor TButtonedEdit.Create(AOwner: TComponent);
    begin
      inherited;
      FACList := TEnumString.Create;
      FACEnabled := True;
      FACOptions := [acAutoAppend, acAutoSuggest, acUseArrowKey];
    end;
    
    procedure TButtonedEdit.CreateWnd;
    var
      Dummy: IUnknown;
      Strings: IEnumString;
    begin
      inherited;
      if HandleAllocated then
      begin
        try
          Dummy := CreateComObject(CLSID_AutoComplete);
          if (Dummy <> nil) and
             (Dummy.QueryInterface(IID_IAutoComplete, FAutoComplete) = S_OK) then
          begin
            case FACSource of
              acsHistory: Strings := CreateComObject(CLSID_ACLHistory) as
                IEnumString;
              acsMRU: Strings := CreateComObject(CLSID_ACLMRU) as
                IEnumString;
              acsShell: Strings := CreateComObject(CLSID_ACListISF) as
                IEnumString;
            else
              Strings := FACList as IEnumString;
            end;
            if S_OK = FAutoComplete.Init(Handle, Strings, nil, nil) then
            begin
              SetACEnabled(FACEnabled);
              SetACOptions(FACOptions);
            end;
          end;
        except
          //CLSID_IAutoComplete is not available
        end;
      end;
    end;
    
    destructor TButtonedEdit.Destroy;
    begin
      FACList := nil;
      inherited;
    end;
    
    procedure TButtonedEdit.DestroyWnd;
    begin
      if (FAutoComplete <> nil) then
      begin
        FAutoComplete.Enable(False);
        FAutoComplete := nil;
      end;
      inherited;
    end;
    
    function TButtonedEdit.GetACStrings: TStringList;
    begin
      Result := FACList.FStrings;
    end;
    
    procedure TButtonedEdit.SetACEnabled(const Value: Boolean);
    begin
      if (FAutoComplete <> nil) then
      begin
        FAutoComplete.Enable(FACEnabled);
      end;
      FACEnabled := Value;
    end;
    
    procedure TButtonedEdit.SetACOptions(const Value: TACOptions);
    const
      Options : array[TACOption] of integer = (ACO_AUTOAPPEND,
                                               ACO_AUTOSUGGEST,
                                               ACO_UPDOWNKEYDROPSLIST);
    var
      Option:TACOption;
      Opt: DWORD;
      AC2: IAutoComplete2;
    begin
      if (FAutoComplete <> nil) then
      begin
        if S_OK = FAutoComplete.QueryInterface(IID_IAutoComplete2, AC2) then
        begin
          Opt := ACO_NONE;
          for Option := Low(Options) to High(Options) do
          begin
            if (Option in FACOptions) then
              Opt := Opt or DWORD(Options[Option]);
          end;
          AC2.SetOptions(Opt);
        end;
      end;
      FACOptions := Value;
    end;
    
    procedure TButtonedEdit.SetACSource(const Value: TACSource);
    begin
      if FACSource <> Value then
      begin
        FACSource := Value;
        RecreateWnd;
      end;
    end;
    
    procedure TButtonedEdit.SetACStrings(const Value: TStringList);
    begin
      if Value <> FACList.FStrings then
        FACList.FStrings.Assign(Value);
    end;
    
    end.
    

    And this is the result.

    enter image description here