Click here to Skip to main content
Email Password   helpLost your password?

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:

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:

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

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
QuestionUse Treeview instead of listview
shawn_b
13:27 7 Nov '08  
Hi, I was wondering if there was a way to use a treeview look for the list? in case the item that you are searching for has sub items that you would like to display, thanks
GeneralIf I tried to open Resource Error :Rc1015 Cannot Open Include file "atlres.h"
G Haranadh
20:36 16 Mar '06  
The demo project is not working why it is not working as it is.
Please let me know any one. Is this demo project works as it is. I am getting an error after compileing file not found.

Nice talking to you. Red faced

If you judge people, you have no time to love them. -- Mother Teresa
GeneralRe: If I tried to open Resource Error :Rc1015 Cannot Open Include file "atlres.h"
laura_glow
8:58 3 Nov '06  
same problem hereFrown
GeneralRe: If I tried to open Resource Error :Rc1015 Cannot Open Include file "atlres.h"
G Haranadh
19:21 3 Nov '06  
Hi friend,
What is the error you got na.
Could you please let me know that error?

Nice talking to you. Red faced

If you judge people, you have no time to love them. -- Mother Teresa

GeneralRe: If I tried to open Resource Error :Rc1015 Cannot Open Include file "atlres.h"
IAF
0:16 27 Jul '07  
When you are looking into WTL section and want to compile WTL code, maybe it is good idea to install WTL Smile ?
AnswerRe: If I tried to open Resource Error :Rc1015 Cannot Open Include file "atlres.h"
rkfoster485
3:13 13 Jul '08  
You need to add wtl to the include path of the resources as well.
This is done in project properties, under Resources.
Rick
GeneralCCustomAutoComplete & SWIG (for Python)
Robert Gasparotto
5:04 17 Dec '05  
Hi,

Your work is great !

I am a programmer who uses Python / VB / Java / C++

I have developed a quick type tool using SCT (based on the Scintilla editor control wrapped for Python as the StyledTextControl). I use the Win32api wrapper library for Python. My application uses autocompletion with regular expressions (this is the good bit) which allows very fast typing - you just put in the letters you know. It also uses frequency based ordering which changes dynamically as you change words. It stores the words and the frequencies in memory and upon exit in a dictionary file.

My application works pretty well as a standalone application with a hook dll that launches and exits the application with an automatic paste into the target application. I would like to have an autocomplete on any textbox I want by using your class.

I am not that skilled in C++ and I would prefer that the class was wrapped for Python using something like SWIG. My SWIG is not that great either.

Have you ever looked at doing something like this ?

Cheers,
Robert.
GeneralIAutoComplete does not work
Nerd-o-rama
19:33 27 Mar '05  
I have a project in which I implement IEnumString and pass it to IAutoComplete::Init(), which returns S_OK. However, my code is never called - the IEnumString functions are never entered - and my edit box never displays any text. I made sure the IUnknown ptr. will return a IEnumString if it is QI()'d (it does). I also downloaded Klaus' program and ran it and it shows the same behaviour - no strings, but it does persist stuff into the registry as expected.

I have previously called SHAutoComplete() and it worked, but I want to use my own strings.

Any idea what is happening here? I am running XP w/IE 6.0.

Eric

Reply to: efowler at seanet dot com
GeneralIntercept enter key during selection
ahmad_ali
2:03 5 May '04  
Hi!

Does anyone know how to intercept the enter key when the autocomplete dropdown is visible and an item has been selected and then prevent the enter keypress from being passed on to the parent window?

E.g. in the MS Windows "Search for Files and Folders" window, the enter press does not result in the default "Search Now" button being pressed when an autocomplete item is selected from the dropdown using the enter key.

/ Z
GeneralRe: Intercept enter key during selection
ahmad_ali
5:44 5 May '04  
Actually I found a solution. Not a nice one, but still a solution.
Use PreTranslateMessage to intercept VK_RETURN. Then use EnumThreadWindows to check for a visible window with class name "Auto-Suggest Dropdown".

/ AA

BOOL CALLBACK EnumWndProc(HWND hWnd, LPARAM lParam) {
char classname[100]="";
GetClassName(hWnd,classname,100);
if(!strcmp(classname,"Auto-Suggest Dropdown") && IsWindowVisible(hWnd)) {
*(BOOL *)lParam=TRUE; // autocomplete box found
return FALSE;
}
return TRUE;
}

BOOL CMyFormView::PreTranslateMessage(MSG* pMsg)
{
if(pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN) {
BOOL autocomplete=FALSE;
EnumThreadWindows(GetCurrentThreadId(), EnumWndProc, (long)&autocomplete);
if(autocomplete) {
::SetFocus(::SetFocus(NULL)); // close autocomplete box
return 0; // stop message here
}
}
return CFormView::PreTranslateMessage(pMsg);
}
GeneralRefcount problems
rabihs
9:18 28 Oct '03  
It's a greate article, Thanks...
But I'm having problems with the Reference Count, can you help me please.

First senereo:

1-Try to bind to an Edit Control, Unbind.(Reference count = 2)
2-Try to bind again to the Control, Unbind...(Reference count = 4)
2-Try to bind again to the Control, Unbind...(Reference count = 6)
...
When we exit the Dialog, the Reference count is only decreased by (2) Confused

Second Senereo:

Try to have 2 differents pointer of CutomAutoComplete in you dialog;

1-Bind the First one to the first Edit Control(First Reference count = 2)
2-Bind the Second one to the Second Edit Control(Second Reference count = 2)
3-Unbind first pointer
4-Unbind second pointer
...
When we exit the Dialog the First RefCount is decreased by (2) which is ok, the second RefCount is only decreased to (1)Confused

I think both Senereo are related


Rabih Sabra
GeneralRe: Refcount problems
KirkMan
9:58 20 Mar '07  
Same problem here.

When I bind it to an existing Edit control in dialog, memory leak is detected.



GeneralRe: Refcount problems
KirkMan
10:04 20 Mar '07  
If the CCustomAutoComplete obj is created on heap, no refcount problem is encountered.

If the obj is created on stack, refcount problem occurs when attached to an existing edit control.

GeneralAbout threading model
Zhuofei Wang
18:05 2 Sep '03  
It can be binded succesfully only when your app uses Apartment-Thread Model.

I'm a student in China, and have been learning PC for 7 years. But till now, I'm still a learner.
Generalnicley done - also - non unicode conversions
SGarratt
11:13 20 Aug '03  
used in wtl app, (tweaked a bit to make it work with non unicode, as noted below.) Plugged it in and it works great. kudos. 5***** Poke tongue

@ line 162:
USES_CONVERSION;
hr = m_pac->Init(p_hWndEdit, this, NULL, A2W(p_lpszFormatString));

///

@ line 390
USES_CONVERSION;
rgelt[i] = (LPWSTR)::CoTaskMemAlloc((ULONG) sizeof(WCHAR) * (m_asList[m_nCurrentElement].GetLength() + 1));
wcscpy(rgelt[i], A2W(m_asList[m_nCurrentElement]));


SGarratt
GeneralHow can you remove item from the dropdown list using "delete" key?
johnthan
11:48 17 Jun '03  
just like the other one does.
AnswerRe: How can you remove item from the dropdown list using "delete" key?
Zinkyu
5:21 3 Mar '08  
I've had some success in getting this to work using a combination of ahmad_ali's EnumWindow[^] code and my own. It's not a great implementation, but it works. And that's what's important, right?

First, you need to have some procedure in your code to intercept the delete key. Most people will have this in PreTranslateMessage. Then, when you detect VK_DELETE, test to see if the auto-complete window is open. If it is, then capture the text in the combobox using GetWindowText. This will be the parameter you pass to RemoveItem.

However, this presents the following limitations:

User cannot put their mouse over the term they wish to remove and press delete to remove the highlighted string. However, I don't think you can do that with Explorer's own address bar, so this is something that can be left in the enhancement category.

Autocomplete will not "refresh" until new text has been searched for by the autocomplete control. In otherwords, if you delete a string, you'll see it in the autocomplete popup for a little while until Windows decides to ask CCustomAutocomplete for new strings. I find this annoying and I wish I knew how to make it do this when *I* want it to happen rather than rely on Windows to know when the appropriate time to do so.
GeneralWhere does the History gets Stored?
Rohit Rawat
17:55 21 Apr '03  
Hi all,

I wanted to thanxs the author who wrote this article and shared this information.

I just wanted to know where this history of autocomplete gets stored in Windows so that I could delete the same when I prefer. Just a little security.

Thanxs,

Rohit
GeneralRe: Where does the History gets Stored?
Klaus Probst
22:22 14 Jun '03  
In this particular case, wherever you tell it to. See ::SetStorageSubkey() function.

IE uses an OS service called secure storage.

___________
Klaus
[vbbox.com]
GeneralProblems...
Jörgen Sigvardsson
2:42 27 Feb '03  
In the IEnumString::Next() method, you are using lstrpcy() on OLE-strings. lstrcpy() is TCHAR, and thus this code will only work properly in UNICODE builds.

You want to use wcscpy() instead.. or CopyMemory if you want to avoid the CRT.

--
Uh huh. Yeah.
GeneralRe: Problems...
Klaus Probst
22:20 14 Jun '03  
No, the code is intentionally Unicode-only. IAutoComplete is available only on W2K and XP.

___________
Klaus
[vbbox.com]
GeneralError Opening
KP24
23:54 16 Dec '02  
Confused After compilation it is giving error "Could not find the file atlres.h" Infact I could not find the file 'Atlres.h' in .rc file plz help me

kp
GeneralRe: Error Opening
Klaus Probst
11:34 17 Dec '02  
You need to have a recent version (probably July 2001 onwards) of the Platform SDK and WTL 3.1 or 7.0 (the WTL include folder must be in your INCLUDE environment var or in the VC++ directory configuration).

___________
Klaus
[vbbox.com]
GeneralLink error unresolved symbol _CLSID_AutoComplete
Angus Comber
3:52 6 Dec '02  
Getting error:

LNK2001: unresolved external symbol _CLSID_AutoComplete

From your project.

I have copies of ShlDisp.h and ShlGuid.h.   Even if I define CLSID_AutoComplete just above CreateInstance line still get this error.

I am running Windows 2000.   Do I need to link to a libray or something?  

I just downloaded the demo!


Angus Comber
angus@iteloffice.com
GeneralRe: Link error unresolved symbol _CLSID_AutoComplete
Klaus Probst
23:23 6 Dec '02  
You probaby have an older version of the Platform SDK. You can download it from MSDN (big), or if you get MSDN Universal (or other edition) you can simply install it. It will update yout #include path once you install to reflect the new headers and libraries and you should be able to compile.

___________
Klaus
[vbbox.com]


Last Updated 20 Jun 2002 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010