Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

IAutoComplete and custom IEnumString implementation for WTL dialogs

0.00/5 (No votes)
19 Jun 2002 3  
A custom IEnumString implementation that works with IAutoComplete to provide autocomplete functionality for edit and combobox controls in WTL applications.

Sample Image - CustomAutocomplete_wtl.jpg

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:

    // Where m_pac is an instance of CCustomAutoComplete

    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 AppleRed Apple
KiwiKiwi
WatermelonWatermelon

... 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;

    ...


    // When the dialog initializes...

    LRESULT OnInitDialog(UINT /*uMsg*/,
                  WPARAM /*wParam*/, LPARAM /*lParam*/,
                                    BOOL& /*bHandled*/)
    {
        ...

        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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here