How to add text completion capability to a TComboBox #110

Answer 1

The Netscape Communicator location box, The Windows 98 Run dialog, and other programs, have implemented a very user friendly feature known commonly as text completion. This document describes how to add similar functionality to a TComboBox. The most elegant and reusable way to add this functionality is by descending from TComboBox and overriding the ComboWndProc to handle the WM_KEYUP message. By adding a new property TextCompletion, the functionality can be toggled to act like a regular TComboBox. Below is the component unit that implements text completion in a TComboBox. This unit can be installed as is.

unit CompletingComboBox;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TCompletingComboBox = class(TComboBox)
  private
    FTextCompletion: Boolean;
    function GetTextCompletion: Boolean;
    procedure SetTextCompletion(const Value: Boolean);
  protected
    {override the WndProc() so that we can trap KeyUp events}
    procedure ComboWndProc(var Message: TMessage; ComboWnd: HWnd;
      ComboProc: Pointer); override;
  public
    {Public declarations}
  published
    property TextCompletion: Boolean
      read GetTextCompletion write SetTextCompletion;
end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Standard', [TCompletingComboBox]);
end;

{TCompletingComboBox}

function TCompletingComboBox.GetTextCompletion: Boolean;
begin
  Result := fTextCompletion;
end;

procedure TCompletingComboBox.SetTextCompletion(const Value: Boolean);
begin
  fTextCompletion := Value;
end;

procedure TCompletingComboBox.ComboWndProc(var Message:TMessage;
ComboWnd: HWnd; ComboProc: Pointer);
var
  rc, len: Integer;
begin
  inherited;
  case Message.Msg of
    WM_KEYUP:
    begin
      {test to see if its a character that should not be processed}
      if (Message.WParam <> 8) and (Message.WParam <> VK_DELETE) and
      (Message.WParam <> VK_SHIFT) and (FTextCompletion = True) then
      begin
        {Use CB_FINDSTRING to locate the string in the Items property}
        rc := Perform(CB_FINDSTRING, - 1, Integer(PChar(Caption)));
        {if its in there then add the new string to the Text and select
        the portion that wasn't typed in by the user}
        if rc <> CB_ERR then
        begin
          {store the length of the current string}
          len := Length(Text);
          {set the new string}
          ItemIndex := rc;
          {highlight the rest of the text that was added}
          SelStart := len;
          SelLength := Length(Text) - len;
          {return 0 to signify that the message has been handled}
          Message.Result := 0;
        end;
      end;
    end;
  end;
end;

end.

Answer 2

Performing autocompletion in a combobox:

procedure TForm1.ComboBox1Change(Sender: TObject);
var
  oldpos: Integer;
  item: Integer;
begin
  with Sender as TComboBox do
  begin
    oldpos := selstart;
    item := Perform( CB_FINDSTRING, - 1, lparam( Pchar( text )));
    if item >= 0 then
    begin
      onchange := nil;
      text := items[item];
      selstart := oldpos;
      sellength := gettextlen - selstart;
      onchange := combobox1change;
    end;
  end;
end;

procedure TForm1.ComboBox1KeyPress(Sender: TObject; var Key: Char);
var
  oldlen: Integer;
begin
  if key = #8 then
    with sender as TComboBox do
    begin
      oldlen := sellength;
      if selstart > 0 then
      begin
        selstart := selstart - 1;
        sellength := oldlen + 1;
      end;  
    end;
end;

Tip by Peter Below

Original resource: The Delphi Pool
Author: Anon & Peter Below
Added: 2009/10/26
Last updated: 2009/10/26