AutoComplete for TEdits

I am sure you know about the useful controls TJvDirectoryEdit and TJvFilenameEdit from the JVCL. They come in handy whenever you need an edit field that should allow autocomplete for file or directory names. It always irked me that I had to include not only the JVCL but also the JCL in order to just have this autocompletion feature especially since I don’t want the selection buttons that are part of the controls. So, having some time on my hands this weekend I decided to check how it is done and whether it would be possible to get the feature without the JVCL. It turned out to be easy.

But first be warned: What I describe here is not what these controls do. I took the easiest route, losing some of the features along the way.

Windows offers basically out of the box what I want with the SHAutoComplete API function in the Shlwapi.dll. The function takes the handle of an edit control (it also supports the embedded edit control of a combobox) and adds autocompletion to it. Basically all you need to do is call

  SHAutoComplete(_ed.Handle, Options);

Where _ed is some TEdit control and Options is a DWORD containing several flags.

There are two ways of autocompletion:

  • Autosuggest
  • Autoappend

Autosuggest means that the control displays a list of matching items and the user can select one of these using either the mouse or the up/down arrow keys.

Autoappend means that the control automatically appends the first matching item to the user input and selects it, so that it gets overwritten if the user continues typing. The user can use TAB to switch focus to the next control in which case he will accept the suggestion.

Now, to suggest input, the function also needs to know where to get the entries. The function supplies three possible sources:

  • Filesystem
  • URL history
  • URL MRU (most recently used)

The first one is easy: The function tries to match the user’s input with files and directories in the file system, just as TJvFilenameEdit does.

I am not sure about the difference between URL history and URL MRU. I guess they are both about Internet Explorer URLs, but I haven’t tried them because I only wanted to get the lookup behaviour of the aforementioned controls.

I ended up with the following wrapper code for the function:

type
  TAutoCompleteSourceEnum = (acsFileSystem, acsUrlHistory, acsUrlMru);
  TAutoCompleteSourceEnumSet = set of TAutoCompleteSourceEnum;
type
  TAutoCompleteTypeEnum = (actSuggest, actAppend);
  TAutoCompleteTypeEnumSet = set of TAutoCompleteTypeEnum;
type
  TErrorHandlingEnum = (ehReturnFalse, ehRaiseException);
const
  // constants and descriptions from MSDN
  // http://msdn.microsoft.com/en-us/library/windows/desktop/bb759862(v=vs.85).aspx

  // Ignore the registry default and force the AutoAppend feature off.
  // This flag must be used in combination with one or more of the
  // SHACF_FILESYS* or SHACF_URL* flags.
  SHACF_AUTOAPPEND_FORCE_OFF = $80000000;

  // Ignore the registry value and force the AutoAppend feature on. The completed string will be
  // displayed in the edit box with the added characters highlighted.
  // This flag must be used in combination with one or more of the
  // SHACF_FILESYS* or SHACF_URL* flags.
  SHACF_AUTOAPPEND_FORCE_ON = $40000000;

  // Ignore the registry default and force the AutoSuggest feature off.
  // This flag must be used in combination with one or more of the
  // SHACF_FILESYS* or SHACF_URL* flags.
  SHACF_AUTOSUGGEST_FORCE_OFF = $20000000;

  // Ignore the registry value and force the AutoSuggest feature on.
  // A selection of possible completed strings will be displayed as a
  // drop-down list, below the edit box. This flag must be used in
  // combination with one or more of the
  // SHACF_FILESYS* or SHACF_URL* flags.
  SHACF_AUTOSUGGEST_FORCE_ON = $10000000;

  // The default setting, equivalent to
  // SHACF_FILESYSTEM | SHACF_URLALL.
  // SHACF_DEFAULT cannot be combined with any other flags.
  SHACF_DEFAULT = $00000000;

  // Include the file system only.
  SHACF_FILESYS_ONLY = $00000010;

  // Include the file system and directories, UNC servers, and UNC server shares.
  SHACF_FILESYS_DIRS = $00000020;

  // Include the file system and the rest of the Shell (Desktop, Computer, and Control Panel, for example).
  SHACF_FILESYSTEM = $00000001;

  // Include the URLs in the user's History list.
  SHACF_URLHISTORY = $00000002;

  // Include the URLs in the user's Recently Used list.
  SHACF_URLMRU = $00000004;

  // Include the URLs in the users History and Recently Used lists. Equivalent to
  // SHACF_URLHISTORY | SHACF_URLMRU.
  SHACF_URLALL = SHACF_URLHISTORY or SHACF_URLMRU;

  // Allow the user to select from the autosuggest list by pressing the TAB key.
  // If this flag is not set, pressing the TAB key will shift focus to the next
  // control and close the autosuggest list.
  // If SHACF_USETAB is set, pressing the TAB key will select the first item
  // in the list. Pressing TAB again will select the next item in the list,
  // and so on. When the user reaches the end of the list, the next TAB key
  // press will cycle the focus back to the edit control.
  // This flag must be used in combination with one or more of the
  // SHACF_FILESYS* or SHACF_URL*
  // flags
  SHACF_USETAB = $00000008;

  SHACF_VIRTUAL_NAMESPACE = $00000040;

function SHAutoComplete(hwndEdit: HWnd; dwFlags: DWORD): HResult; stdcall; external 'Shlwapi.dll';

function TEdit_SetAutocomplete(_ed: TCustomEdit; _Source: TAutoCompleteSourceEnumSet = [acsFileSystem];
  _Type: TAutoCompleteTypeEnumSet = []; _ErrorHandling: TErrorHandlingEnum = ehReturnFalse): boolean;
var
  Options: DWORD;
  Res: HRESULT;
begin
  Options := 0;
  if acsFileSystem in _Source then
    Options := Options or SHACF_FILESYSTEM;
  if acsUrlHistory in _Source then
    Options := Options or SHACF_URLHISTORY;
  if acsUrlMru in _Source then
    Options := Options or SHACF_URLMRU;
  if actSuggest in _Type then
    Options := Options or SHACF_AUTOSUGGEST_FORCE_ON;
  if actAppend in _Type then
    Options := Options or SHACF_AUTOAPPEND_FORCE_ON;

  Res := SHAutoComplete(_ed.Handle, Options);
  Result := (Res = S_OK);
  if not Result then
    raise EOleException.Create(_('Call to SHAutoComplete failed.'), Res, 'Shlwapi.dll', '', 0);
end;

And I call it like this:

  TEdit_SetAutocomplete(ed_Directory, [acsFileSystem], [actSuggest, actAppend]);

As you can see in the descriptions for the possible Option values, there apparently is a registry entry for setting the default for autocomplete, but I wasn’t able to find out where. Also, for some of the options it’s not obvious, what they do. I wonder what SHACF_VIRTUAL_NAMESPACE is supposed to mean?

Another thing to keep in mind, is that the function does not offer any filtering options, especially it does not distinguish between files and directories, so it is not possible to get the equivalent of TJvDirectoryEdit using this function. In order to get the full functionality of these two JVCL controls, you would have to do what the JVCL developers did (actually that functionality is much older than the JVCL, it originally came from rxlib), and use the IAutoComplete interface described in this answer on StackOverflow. But as I said above: I took the easiest route.