Ever since IE started providing that nifty little "look-ahead" feature in the address bar and form fields, a lot of applications have incorporated that
functionality into their GUI design. Auto completion edit controls and combo lists are a valuable tool for users
(when used correctly) because they
cut down on the time required to provide feedback to the app. Most of the code
that I found around the net that deals with implementing something
like this was either too customized (alternatives to what the shell provides) or
was specific to MFC. Paul DiLascia in MSJ (June '99 I think) explores
the first scenario, and an article by Cassio Goldschmidt in the November 2000 issue of VCDJ deals with the second (links to both at the end of this article).
And that's about all one can find in Google - not even the PSDK includes samples about this. However, I needed something that wasn't as "powerful" - in the eye
of the beholder - and worked with WTL/ATL without touching MFC at all. So, against my best judgement, I decided to write one, and here we are.
Auto complete functionality in the Windows shell
Before we get down to the code, let's go over some of the options available from the Win32 shell as far as providing autocompletion functionality
for applications. The main COM interface provided by the shell (actually exported from BROWSEUI.DLL) is called
IAutoComplete. There's also
another interface called
IAutoComplete2 which is not important unless you need to customize the behavior of the auto complete, but we'll go into
that in a moment.
IAutoComplete has three implementations, identified by three matching CLSIDs that provide different types of autocompletion lists:
CLSID_ACLHistory - Provides matches against the current user's History list, managed by the IE shell
CLSID_ACLMRU - Provides matches against the current user's Recent Document list, also managed by the IE shell
CLSID_ACListISF - Provides matches against the shell namespace (which includes the file system)
Each of these implementations are useful on their own if you specifically need what they provide, and in many cases they're all you need. For
example, Windows 2000 and XP use
CLSID_ACListISF in the filename edit box of Open and Save common dialogs so you get a look-ahead list
of all files and subfolders in any given folder. Ditto for the Run dialog and the address bar (if active) in the shell taskbar.
By the way, most of this information is included in the MSDN, which contains an "IAutoComplete Tutorial". The problem is basically that the issue of
creating and using a custom string enumerator coupled with IAutoComplete is not really covered there either, except for the basic steps. In some
cases, you don't want to enumerate favourites, files or recent documents, but rather zip codes, birds, telephone numbers or types of cheese. Or whatever.
If you are looking for that, and you're using WTL, then this sample will help.
One final note before we continue: The three
IAutoComplete implementations mentioned above can be enabled for any given edit control by using
SHAutoComplete function, exported from SHLWAPI.DLL. Simply pass the
HWND of an edit control and a couple of flags, and ta-da. Automagic
auto completion without having to deal with COM at all. Probably the only times when you'd stay away from this function
are when you require compound auto completion - more than one list bound to the same edit control managed through a
IObjMgr object - or finer control over
CLSID_ACListISF, which can be done via
IPersistFolder. The MSDN tutorial mentioned also briefly covers this (link at the end
of the article).
Still with me? That probably means that you are interested in creating a custom list enumerator for your WTL dialogs or doc/view applications. Yeah, we've
IAutoComplete and IEnumString
In order to create an object that manages arbitrary lists of arbitrary things and give it to
IAutoComplete, you need to implement the
IEnumString is, in case you're wondering, exactly the same as most other
IEnumXXXX interfaces defined by COM:
HRESULT Next(ULONG celt, LPOLESTR * rgelt, ULONG * pceltFetched);
HRESULT Skip(ULONG celt);
HRESULT Clone(IEnumString ** ppenum);
As you can see, exactly the same as
IEnumMoniker and so on. Nothing special except that, obviously, the things being enumerated are
OLESTRs to be exact) instead of variants or monikers.
If you've ever implemented an enumerator you'll know that it's relatively
straightforward to do so, so I'm not going to list the code here. However,
I'd like to make a small clarification about it. When I started writing the code, the prototype for the
IEnumString implementation looked
class CCustomAutoComplete :
I later found said article in VCDJ that defined exactly the same prototype (as a typedef, but still), so I decided not to use the ATL templates at all to
IEnumString implementations, but just code them. It had been a long while since I coded even the most
minimal raw COM wrapper, so it was kind of fun. But in any case, that shouldn't make a difference as far as the calling code goes.
The code centres around a single class,
CCustomAutoComplete, which is designed to be not only the object that provides strings to
IAutoComplete, but also to serve as the "manager" for the
IAutoComplete object itself, as well as providing control over the
individual string elements and persistence if desired. Internally,
CCustomAutoComplete uses a
objects (this is the WTL
CString, in atlmisc.h) to maintain its list of elements.
Other than actually serving up strings to
IAutoComplete, which is simple enough, the second consideration about implementing the custom
enumerator was storage. In most cases you want a way to store these strings somewhere and persist them from one session of your application to the next.
My solution to this was to use the registry. Using the standard registry functions (specifically the enumeration ones) loading and saving lists is relatively
straightforward. Although the class itself is capable of managing persistence, using the registry is not required. You can still pass an array of strings to
it or simply add items one by one and have them show up in the list just fine. Of course, you'll have to get the strings from somewhere.
If you do decide to use the registry, you must use a subkey of your application's main key that is not used for anything else. If you're using multiple types of
lists, store each in its own subkey. For example, if your application's main registry key is at HKEY_CURRENT_USER\Software\Widgets and you have a list of
Blue Widgets you want to have appear as a dropdown list for the Select Blue Widget dialog, call the
SetStorageSubkey() like this:
// Where m_pac is an instance of CCustomAutoComplete
This way, if you have a list of Red Widgets you can use a separate subkey, and so on. Keep in mind that you cannot bind the same
instance to two different edit controls, so although you can use the same subkey for two different controls (not generally recommended!), you'll still have to use two different
CCustomAutoComplete instances, one for each edit control.
The actual storage format is pretty simple. The class will store one identical key-value pair in the specified subkey for each unique entry in the auto complete list. It
doesn't look terribly advanced, but it gets the job done since it makes it easier to quickly locate items when needed using simple, straight API calls. So, let's say
that you're storing fruits for enumeration in a given subkey. The registry editor would show something like this:
|Red Apple||Red Apple|
... and so on. Very simple stuff. If you feel this isn't making the cut for your application, you can always modify the code. The private
function and friends are isolated enough that you can change them and still fill the
CString array as the rest of the class expects.
One last thing about the storage functionality. The class assumes that it has complete ownership of the subkey specified by your application. So don't store anything else
in there or the next time you bind to the same key the class will load whatever happens to be in there and the list the user sees will be, well, strange.
The methods provided by the
CCustomAutoComplete are as follows:
CCustomAutoComplete::CCustomAutoComplete(const HKEY p_hRootKey, const CString& p_sSubKey)
Initializes the object with a registry key to use as storage. See the
SetStorageSubkey() function for more information.
CCustomAutoComplete::CCustomAutoComplete(const CSimpleArray<CString>& p_sItemList)
Constructor. Initializes the object with an array of
CString to use as storage.
BOOL CCustomAutoComplete::SetList(const CSimpleArray<CString>& p_sItemList)
CString array to be used for enumeration.
BOOL CCustomAutoComplete::SetStorageSubkey(HKEY p_hRootKey, const CString& p_sSubKey)
BOOL CCustomAutoComplete::SetStorageSubkey(LPCTSTR p_lpszSubKey, HKEY p_hRootKey = HKEY_CURRENT_USER)
Sets the registry root where the class will load and save items. Set the
p_hRootKey argument to one of the HKEY_* constants.
You may also pass an open HKEY handle, but to be honest I didn't try that. The class will attempt to open the key with
KEY_READ | KEY_WRITE privileges
before returning (and will create it if its not there). If something bad happens (registry permissions or whatever), the return will be FALSE. The HKEY handle
will be opened until the instance of the class is destroyed.
BOOL CCustomAutoComplete::Bind(HWND p_hWndEdit, DWORD p_dwOptions = 0, LPCTSTR p_lpszFormatString = NULL)
Initializes the internal
IAutoComplete pointer, sets options (if necessary) and binds it to the edit control identified by
p_dwOptions argument is a mask with one or more values that correspond to the ACO_* flags that can be passed to
IAutoComplete2::SetOptions(). I won't list them here since they can be pulled from the MSDN reference. The
argument is there to support the last argument in the
IAutoComplete::Init() function, which states that if you specify
a mask that looks like this 'http://www.%s.com/', and the user enters 'codeproject' and then CTRL+ENTER, the final string applied to
the bound edit control will be 'http://www.codeproject.com/'. However, I could not get this to work on Windows 2000. Note that if
you do get it to work, the
ACO_FILTERPREFIXES flag will come in handy since you can filter common string fragments such
as 'www' and 'http://'
Releases the internal
IAutoComplete pointer, but does not clear the item list. Aside from calling this when you no longer need the
class instance, it can be used to tie the same list (in the registry or otherwise) to another edit control by calling
BOOL CCustomAutoComplete::AddItem(CString& p_sItem)
BOOL CCustomAutoComplete::AddItem(LPCTSTR p_lpszItem)
Adds a new item to the list. If the item is added successfully, the return is TRUE. If the item was already in the list, the
and/or if registry persistence is being used and the item could not be removed, the return is FALSE.
BOOL CCustomAutoComplete::RemoveItem(CString& p_sItem)
BOOL CCustomAutoComplete::RemoveItem(LPCTSTR p_lpszItem)
Removes the specified item from the list. If the item is removed successfully, the return is TRUE. If the item wasn't in the list
and/or if registry persistence is being used and the item could not be removed, the return is FALSE.
Simply returns the number of items currently held by the class instance.
BOOL SetRecentItem(const CString& p_sItem)
BOOL GetRecentItem(CString& p_sItem)
Sets/returns the most 'recent' item added to or used from the auto complete list. This method is convenient if you want
to set an edit control to the most recent value for a given auto complete list when your dialog loads. You must set
and retrieve the string every time it's appropriate to do so - the class has no way of doing this automatically.
Clears the internal item list and if registry persistence is being used, all items from the root key. It does not however destroy the internal
IAutoComplete pointer or unbinds it from the edit control.
IAutoComplete::Enable(FALSE) to turn off auto completion.
IAutoComplete::Enable(TRUE) to turn on auto completion. You only need to call this if you called
Disable() at some
point since once
Bind() is called auto completion is enabled by default.
As you can see, the interface to the class is pretty straightforward. Next, some tips on how to use it.
CCustomAutoComplete is pretty simple. First, decide whether or not you need storage through the registry. Create an instance of the
class on the heap with the appropriate constructor (or initialize it after creation), add the item(s) if necessary, then call the
function to tie it to an EDIT control of your choice. Then insert or remove new items depending on your needs - for example, an
auto complete textbox
that lists URLs might add a new item after the user types it in and your program can successfully ping the domain, or remove it from the list if the
site is unreachable. Here's part of the code that's included in the demo project:
// When the dialog initializes...
LRESULT OnInitDialog(UINT /*uMsg*/,
WPARAM /*wParam*/, LPARAM /*lParam*/,
m_pac = new CCustomAutoComplete(HKEY_CURRENT_USER,
ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST);
The demo also includes the other few calls needed to remove/add items to the list at runtime. Once you don't need the class anymore, simply call
Unbind() to release it (don't use
delete on the pointer).
Other than that, here are a couple of things to remember:
- The code assumes you included <atlmisc.h> at some point since CString is defined in there. Most WTL code I've seen (and done) uses
CString anyway so this may not be a big deal. You can always change the internal array to use something else, of course.
- You can't use a single instance of
CCustomAutoComplete and bind it to more than one edit control. Use one instance per control. This
is not so much a limitation of the class but rather of
- Be careful about the registry keys you use, or you might find yourself presenting your users with the wrong lists (see the discussion about
storage earlier for more information).
- You can switch the same instance of
CCustomAutoComplete between different storage subkeys once it is bound to an edit control using
SetStorageSubkey() function, or use a different set of items with the the
SetList() function. This is very handy when
a single edit control must display different lists depending on the situation or user input.
IAutoComplete is only implemented in Windows 2000 and XP
(says MSDN), the class assumes Unicode throughout. Something to think about.
- The class contains no functionality to "uninstall" its own subkey since it knows nothing about the parent key. This shouldn't be an issue if your
application deletes its entire hive when a user uninstalls it.
I hope someone finds this class useful. The code has been relatively well tested in existing tools and applications that are in use, but I certainly didn't test it
exhaustively. I'd love to hear about any bugs or problems! Below are a few links to more information about
20 Jun 2002 - Initial Revision
20 Jun 2002 - Reformatted Code Sections
20 Jun 2002 - Reformatted Text