Introduction
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
the 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
got that.
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
interface. 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 Reset(void);
HRESULT Clone(IEnumString ** ppenum);
As you can see, exactly the same as IEnumVARIANT
, IEnumMoniker
and so on. Nothing special except that, obviously, the things being enumerated are
strings (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
like this:
class CCustomAutoComplete :
public CComObject<CComEnum<IEnumString,
&IID_IEnumString, LPOLESTR,
_Copy<LPOLESTR>>>
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
control the IUnknown
and 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.
Persistence Support
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 CSimpleArray
of CString
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:
m_pac->SetStorageSubkey(HKEY_CURRENT_USER,
_T("Software\\Widgets\\Blue Widgets"));
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 IAutoComplete
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 |
Kiwi | Kiwi |
Watermelon | Watermelon |
... 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 LoadFromStorage()
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.
Implementation
The methods provided by the CCustomAutoComplete
are as follows:
CCustomAutoComplete::CCustomAutoComplete()
Default constructor.
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)
Sets the 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_hWndEdit
.
The optional 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 p_lpszFormatString
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://'
VOID CCustomAutoComplete::Unbind()
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 Bind()
again.
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.
INT CCustomAutoComplete::GetItemCount()
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.
BOOL CCustomAutoComplete::Clear()
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.
BOOL CCustomAutoComplete::Disable()
Calls IAutoComplete::Enable(FALSE)
to turn off auto completion.
BOOL CCustomAutoComplete::Enable()
Calls 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.
Usage
Using 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 Bind()
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:
private:
CCustomAutoComplete* m_pac;
...
LRESULT OnInitDialog(UINT ,
WPARAM , LPARAM ,
BOOL& )
{
...
m_pac = new CCustomAutoComplete(HKEY_CURRENT_USER,
_T("Software\\VBBox.com\\
StgAutoCompleteDemo\\Recent"));
m_pac->Bind(GetDlgItem(IDC_TXTAUTOCOMPLETE),
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 IAutoComplete
itself.
- 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
the 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.
- Since
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.
Wrap-up
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
IAutoComplete
:
Revision History
20 Jun 2002 - Initial Revision
20 Jun 2002 - Reformatted Code Sections
20 Jun 2002 - Reformatted Text