Home Home Archives About red box
Username:
Password:
 
rss

Filed Under Delphi
digg Auto Completion
visits: 1256 | score: 4.7 
posted by sysrpl on Friday May 12, 2006 11:27 AM

Visit my main Delphi page here

A common feature of Microsoft Windows is auto completion. Auto completion functions to expand text as you type, making suggestions to reduce repetitive tasks. With auto completion, partial words can be expanded into more complete phrases that match a stored list of choices. The end result is less typing and more accurate text entry.

At its most basic level, auto completion is a set of Microsoft COM interfaces. These interfaces allow you to associate auto completion with an edit window, attach a list of strings from which it can enumerate, and configure display options. In this article we'll show how you to create and use auto completion, explain how it works, and provide ideas to enhance your applications.

Reasons to use auto completion

There are plenty of reasons to include auto completion in your next project. You can use auto completion to save the time it takes to access frequently used features, or re-enter commonly typed names. Auto completion can tap into a system defined set of completion lists for working with Internet history, commonly run programs, and shell folder items. Also, you could theoretically use auto completion to draw suggestions from a database, or even a web service.

Setting up auto completion

Before attempting to use auto completion, you should have a unit that defines it's related interfaces and constants. These items are defined in Microsoft's Plaform SDK headers ShlObj.h and ShlGuid.h. A translation of those headers are included in the Codebot Controls Library ShlIntf unit.

Here is a table summarizing the common auto complete interfaces and coclasses.

CoClasses
CLSID_AutoComplete
CLSID_ACLHistory
CLSID_ACListISF
CLSID_ACLMRU
CLSID_ACLMulti
Interfaces
IAutoComplete
IAutoComplete2
IACList
IACList2
ICurrentWorkingDirectory
IObjMgr

Note: The simple definition of a CoClass (component class) is an COM object that can be created on demand typically through a call to CoCreateInstance.

To start using with auto completion, it's helpful to understand it's primary interface IAutoComplete.
type
  IAutoComplete = interface(IUnknown)
    [SID_IAutoComplete]
    function Init(hwndEdit: HWND; punkACL: IUnknown; pwszRegKeyPath: PWideChar;
      pwszQuickComplete: PWideChar): HResult; stdcall;
    function Enable(fEnable: Boolean): HResult; stdcall;
  end;

As you can see, IAutoComplete is a simple interface with has two methods: Init and Enable.

The Init method serves to associate an IAutoComplete interface with an edit window and an auto complete list (more on the auto complete list in a bit). The additional parameters pwszRegKeyPath and pwszQuickComplete can be used to allow the IAutoComplete interface to expand partial text into formatted strings. These parameters can optionally point to strings that determine a format to be used if the user enters partial text, then presses CTRL+ENTER. You might, for example, set pwszQuickComplete to "//www.%s.com/", then when a user enters "codebot" into the edit box and presses CTRL+ENTER, the text in the edit box would be updated to "//www.codebot.com/".

The Enable method simply activates or deactivates auto completion based on the value of the fEnable flag. Auto completion is enabled by default.

The figure above shows how IAutoComplete looks when it expands strings. As you begin typing in the edit window, auto completion will highlight a candidate that best approximates a match for the partial string. Again, by default, this is the way auto completion operates. Later we'll show how you can extend auto completion to use a drop down list.

Now let's look at the the punkACL parameter of the Init method. This required parameter points to an interface that exposes a string list. A string list is a COM object that exposes an IEnumString interface. The IEnumString interface is responsible for generating a list of candidates for completed strings. The punkACL parameter can expose additional interfaces such as IACList and IACList2.

The IACList and IACList2 interfaces are an auto complete list interfaces that can be used to improve efficiency by organizing candidate strings.

The IAutoComplete2 interface extends IAutoComplete by adding the ability to retrieve and set a number of options that affect the not only auto completion's visual representation, but also how it operates. Below is a listing of IAutoComplete2.
type
  IAutoComplete2 = interface(IAutoComplete)
    [SID_IAutoComplete]
    function SetOptions(dwFlag: DWORD): HResult; stdcall;
    function GetOptions(out dwFlag: DWORD): HResult; stdcall;
   end;

IAutoComplete2 introduces two new methods. Both methods use the same parameter types, which is a single unsigned integer consisting of a set of logically combined flags. Here is a table of the auto completion options flags and their meanings.

Flag Description
ACO_NONE Don't use auto completion.
ACO_AUTOSUGGEST Use a drop down list.
ACO_SEARCH Add the text "Search for" to the end of the drop down list.
ACO_FILTERPREFIXES Prevents auto completion from matching common prefixes such as"www.", "//".
ACO_USETAB The TAB key is used to select an item from the drop down list.
ACF_UPDOWNKEYDROPSLIST The UP ARROW and DOWN ARROW keys to cause the drop down list to appear.
ACO_RTLREADING Used for right to left reading.

Notice that the TAB key option must be specifically enabled. This is because the TAB key is normally used to navigate between controls, not within a control. Setting ACO_USETAB as a flag allows the user to navigate to the drop down list by pressing TAB. When the drop down list is closed, the TAB key allows the user to navigate from normally between controls. When the list is dropped down, pressing the ESCAPE key closes the list.

Working with COM interfaces

Since auto completion is implemented as a set of COM interfaces, it helps to be familiar with some COM fundamentals.

For example, to create an instance of an IAutoComplete complete component class with COM you might use the following code:
  CoInitialize(nil);
  OleCheck(CoCreateInstance(CLSID_AutoComplete, nil, CLSCTX_INPROC_SERVER,
    IID_IAutoComplete2, FAutoComplete));
Thankfully Delphi's run time library makes working with COM easy. The ComObj unit takes care initializing and uninitializing the COM libraries for you, so you never have to call CoInitialize. It defines a function named CreateComObject which creates a single unknown interface of the class associated with it class id parameter. Internally CreateComObject calls CoCreateInstance, requesting and unknown interface from the CoClass.
  FAutoComplete := CreateComObject(CLSID_AutoComplete) as IAutoComplete2;
To convert an unknown interface returned from CreateComObject into something useful, use interface querying with the "as" operator. Interface querying with the "as" operator performs safe checked calls, so if the interface being queried for is not supported, an exception will be raised.

Sample application

We now have enough information assembled to create our first auto completion application. Below is a listing of the main unit. As you can see, it takes very little code to get auto completion up and running.
unit Main;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ShlIntf, ActiveX, ComObj, StdCtrls, StrTools;

type
  TAutoCompleteForm = class(TForm)
    CompletionEdit: TEdit;
    CompletionLabel: TLabel;
    SourceGroupBox: TGroupBox;
    SourceMemo: TMemo;
    procedure FormCreate(Sender: TObject);
  private
    FAutoComplete: IAutoComplete2;
    FStrings: IUnknown;
  end;


var
  AutoCompleteForm: TAutoCompleteForm;

implementation

{$R *.DFM}

{ TAutoCompleteForm }

procedure TAutoCompleteForm.FormCreate(Sender: TObject);
begin
  FAutoComplete := CreateComObject(CLSID_AutoComplete) as IAutoComplete2;
  FStrings := TEnumString.Create(SourceMemo.Lines) as IUnknown;
  OleCheck(FAutoComplete.SetOptions(ACO_AUTOSUGGEST or ACO_UPDOWNKEYDROPSLIST));
  OleCheck(FAutoComplete.Init(CompletionEdit.Handle, FStrings, nil, nil));

end;

end.
The first line of the event handler above creates an instance of the auto complete CoClass. It queries the unknown result form CreateComObject for an IAutoComplete2 interface. If your system does not support IAutoComplete2, this line will cause an EInvalidCastError exception to be raised. The next line creates an instance of the TEnumString class and queries it for IUnknown. This interface is used later as the punkACL parameter in the Init method of FAutoComplete. The TEnumString class is derived from TInterfacedObject and defined in the StrTools unit. It basic purpose is to make a TStrings object IEnumString compatible. Next we set the options of FAutoComplete to include the drop down list and auto suggest. Finally we initialize the FAutoComplete, associating it with the edit window, and providing an auto completion list from which it can enumerate candidate strings.

Figure one at the top of this article show a screen shot of the TAutoCompleteForm class in action.

Standard auto completion lists

In addition to creating a control and managing string candidates for you, Windows also provides access to a set standard auto completion lists. These mostly undocumented interfaces are exposed through a set of CoClasses summarized in the table below.
CoClass Description
CLSID_History The history class provides access to the items in your history folder.
CLSID_ACListISF The shell folder class lets you work with to the contents of the shell's namespace.
CLSID_MRU The most recently used class contains a list of strings of recently run programs.

The CoClasses above can be used to retrieve an unknown interface from Windows. To link the unknown interface to an instance of IAutoComplete, pass it to IAutoComplete.Init as the punkACL parameter. Each unknown interface will support both IEnumString, and IACList, and quite possibly a few others. For example, the shell folder unknown interface exposes both ICurrentWorkingDirectory and IPersistFolder. Using this information, you can dynamically change the candidate list of auto completion to reflect the contents of any container in the shell namespace. The difference between ICurrentWorkingDirectory and IPersistFolder is that IPersistFolder uses item lists, rather than strings. Items lists can be useful if you wish to navigate to virtual folders such as the control panel.

Here is an example showing how to use auto completion to select candidates from the contents of the root drive.
var
   WorkingDirectory: ICurrentWorkingDirectory;
 begin

   FAutoComplete := CreateComObject(CLSID_AutoComplete) as IAutoComplete2;
   FStrings := CreateComObject(CLSID_ACListISF);
   WorkingDirectory := FStrings as ICurrentWorkingDirectory;
   OleCheck(WorkingDirectory.SetDirectory('C:\'));
   OleCheck(FAutoComplete.SetOptions(ACO_AUTOSUGGEST or ACO_UPDOWNKEYDROPSLIST));
   OleCheck(FAutoComplete.Init(CompletionEdit.Handle, FStrings, nil, nil));
 end;
Improving auto completion

Although we have seen that auto completion is relatively easy to work with, there are a few changes that can be made to make it fit better into Delphi's object framework. The most obvious improvement to the auto completion interfaces would be to create a friendly component wrapper around them, encapsulating their various aspects into a single class. Another improvement would be to allow auto completion to work with controls that contain an embedded edit window. In figure four below, a TAutoComplete component is linked to a TTreeView control.

Conclusion

By now, you are probably thinking of all the good use you can get out of auto completion in your own programs. Use your imagination, and experiment with the source code in this article.

To summarize, auto completion is an elegant way to access frequently used text. As we have seen from the sample application, adding auto completion to a Delphi program can be accomplished using few lines of code. By allowing users to pick items from an auto completion list, you can give your applications a very polished look.

Download Auto Completion Example

Posted on 05/12/2006


print send topic Rate this article  

Need a little help...

Hey

This article is great, and just what I was looking for, but unfortunately I am unable to get the source code to work in Delphi 2010. The provided Sample.exe works perfectly, but when I compile the project myself, I get an error once I start typing in the TEdit field:
 
Debug Output:
Invalid Address specified to RtlFreeHeap( 00140000, 0015EA04)
 

Any help is greatly appreciated!
reply quote
perm
arthurdent said on Thursday January 14, 2010 3:01 AM

Improvements

Hello can be improved with the ability to look for a partial string?

example:

Items Numer One
Item number two
Another number item

if type "number"

i need to show all the items containing "number".

It's possible?

vcorp@vcorp.it
Last modified on 4/11/2016 1:05 PM
reply quote
perm
vcorp said on Monday April 11, 2016 1:04 PM

Title:

image link indent align right align middle align left quote underline bold code quote
Comment:

page generated in 0.095 seconds | last modified 2/06/2011 9:35 PM
none  none