Hello again. In part 2 I've said I will start explaining shell extension the next article. I'm sorry but as I said in part 3 there is still one issue I have to talked about before moving to shell extensions, I do hope the next article I'll start explaining it. The issue involved is using the AutoComplete features that the operating system enable us. As in the previous article (Part 3) which talked about Application Desktop Toolbars, this article is also about an interesting subject.
Note: This article doesn't requires previous reading, but, as always I do recommend reading the previous 'C# does Shell' parts and the relevant MSDN articles:
C# does Shell, Part 1
C# does Shell, Part 2
C# does Shell, Part 3
MSDN: Using Autocomplete
So, what is this AutoComplete stuff? I'll start with an example. Click on the Start menu, then on Run, in the opened dialog enter a single char. You will probably see a list of available options for completing the string. It should look something like this:
The same is with the address bar in the internet explorer. Start writing and the rest of the string will be expanded.
In this article we will learn what the operating system let us do with it, and how to use this functionality in our own applications. As always we will create a class named
ShellAutoComplete that nicely wraps it, this class will be added to the
ShellLib library we are developing.
Well, lets get to work.
Section 1: using SHAutoComplete
The most common task we usually want to accomplish is use the AutoComplete to expand strings like file names and directories or URL's that we used (History) or even strings from the Most Recently Used list (MRU). So if we want to accomplish one of those things we can use a function called
SHAutoComplete. This function is part of the Shell API functions. This functions receive a window handle of an edit box and a flags parameter to set some AutoComplete options. After using this function on the edit box, the control will have AutoComplete features installed. This do work like magic.
So how is the
SHAutoComplete function look like, and how we translate it to c#? here is the original definition:
<PRE lang=c++>HRESULT SHAutoComplete(
and here is the C# equivalent:
public static extern Int32 SHAutoComplete(
As you can see, the function has two parameters. The first one,
hwndEdit, is the window handle of the edit box we want to enable AutoComplete on. In fact this handle can be also a handle of a window that has an edit box embedded in it, only if you want to do that you need to respond to the message
CBEM_GETEDITCONTROL by returning the embedded edit box handle. One control that acts like that is the
ComboBoxEx, when using the
CBS_DROPDOWN style. Anyway, the normal use is directly on edit box.
The second parameter is
dwFlags. This parameter set some AutoComplete options. It can be any combination of values of the
AutoCompleteFlags enum. Here is its definition:
public enum AutoCompleteFlags : uint
Default = 0x00000000,
FileSystem = 0x00000001,
UrlAll = (UrlHistory | UrlMRU),
UrlHistory = 0x00000002,
UrlMRU = 0x00000004,
UseTab = 0x00000008,
FileSys_Only = 0x00000010,
FileSys_Dirs = 0x00000020,
AutoSuggest_Force_On = 0x10000000,
AutoSuggest_Force_Off = 0x20000000,
AutoAppend_Force_On = 0x40000000,
AutoAppend_Force_Off = 0x80000000
If you look on the enum definition you see they divides into several categories. The values that influence the AutoComplete source are
UrlMRU. The values that overrides registry defaults are:
AutoAppend_Force_Off. And here comes the place where I can explain what the AutoSuggest and AutoAppend options are.
AutoAppend: This means that as you write, the string is automatically completed with the current string entered, as you write more of the string it will get more precise, but the thing to remember is that the string is automatically appended.
AutoSuggest: As you write your string, a drop down list appears with the current suggestion for completion of the string. Off course you can select a string for the list.
One thing to remember is that you can choose several sources for the AutoComplete list. It can be any combination of the file system, History URL's and the Most Recently Used list. But what if you want to use your own list? what if you want to combine an the History and a list of your own? All this cannot be done with this function but will be reviewed in this article.
Section 2: AutoComplete Object Model
Well, maybe object model is a bit strong, but there is a model that should be explained here. You see, Microsoft has put the entire AutoComplete functionality in a COM object called
AutoComplete. This object reveals the interfaces
AutoComplete Object knows how to create a window with the string list and how to expand the string once the user start typing it. What the
AutoComplete object does not have is the string list. The string list is held in another object that should have the
IEnumString interface. This object knows only how to enumerate on its string list. The OS provides 4 source objects. One that have the File System string list named
ACListISF. One that gave the History string list named
ACLHistory. One that have the MRU string list named
ACLMRU. And finally a special object named
ACLMulti, which will be explained later. All those objects have the interface
IEnumString and can be used as a source for the
AutoComplete object. Some of those objects have also the interfaces:
IACList2. And the
ACLMulti Object has in addition the
IObjMgr interface. All those interfaces will be explained, but the important thing to remember is that we have a main
AutoComplete object and several possible sources for it. Here is a little diagram I've made for you to better understand what objects we have in the system and their interfaces:
Now, what do we need to do in order to have AutoComplete on an edit box? First we need to create the
AutoComplete object, which has a specific GUID (like any other COM object in the world). Then we create a source object,
ACLHistory for example. Then we attach the
ACLHistory object to our
AutoComplete object and activate the
AutoComplete object on our edit box. This is basically what has to be done when using the objects model instead of the simple and not expandable
I've mention the GUID's subject. We have 5 objects that the Operating System supply us. In order to create them we need their GUID's. Here is their C# declaration. These declarations are found in the
ShellLib library in the already existing class
public static Guid CLSID_AutoComplete =
public static Guid CLSID_ACLHistory =
public static Guid CLSID_ACListISF =
public static Guid CLSID_ACLMRU =
public static Guid CLSID_ACLMulti =
The next section will discuss creating and using of the
AutoComplete object, this includes off course a review of the interfaces it exposes.
Section 3: AutoComplete Object
The first thing we should do is creating the object, so how do we create a COM object in C#? One way is to 'add reference' of the object, in this case I prefer the more dynamic way, using the
Activator class. We will write a private function in the
ShellAutoComplete class which will create the
Autocomplete object and returns it to the caller. Here is the code:
private Object GetAutoComplete()
Type typeAutoComplete = Type.GetTypeFromCLSID(ShellGUIDs.CLSID_AutoComplete);
obj = Activator.CreateInstance(typeAutoComplete);
This function use the
GetTypeFromCLSID static function to return the
Type of a registered COM object. Then using the
CreateInstance of the
Activator class it creates the object. In order to use the object we need to cast it to one of its interfaces.
As I've said before, this object has two Interfaces, first we will discuss
IAutoComplete. Here is the original declaration and then comes the C# equivalent:
Note that the original declaration is here for you to better understand the changes that need to be done when translating a c++ interface into a c# interface.
IAutoComplete : public IUnknown
virtual HRESULT STDMETHODCALLTYPE Init(
/* [in] */ HWND hwndEdit,
/* [unique][in] */ IUnknown *punkACL,
/* [unique][in] */ LPCOLESTR pwszRegKeyPath,
/* [in] */ LPCOLESTR pwszQuickComplete) = 0;
virtual HRESULT STDMETHODCALLTYPE Enable(
/* [in] */ BOOL fEnable) = 0;
public interface IAutoComplete
IAutoComplete interface has two functions,
Init function Initialize the
AutoComplete object and the
Enable function enables or disables the autocompletion. Note that the
Init function is where you give the edit box window handle (in the
hwndEdit parameter) and the Source object (in the
punkACL parameter). Later in this section I'll show an example of using these functions.
The second interface the AutoComplete object expose is IAutoComplete2. This interface expands the first one and declares two more methods. Here is its declaration:
IAutoComplete2 : public IAutoComplete
virtual HRESULT STDMETHODCALLTYPE SetOptions(
/* [in] */ DWORD dwFlag) = 0;
virtual HRESULT STDMETHODCALLTYPE GetOptions(
/* [out] */ DWORD *pdwFlag) = 0;
and the c# equivalent:
public interface IAutoComplete2
out UInt32 pdwFlag);
Here you see two extra functions:
GetOptions. Those functions are simply to enable you tune a bit the
AutoComplete object behavior. The options allowed are those declared in the
public enum AutoCompleteOptions
None = 0,
AutoSuggest = 0x1,
AutoAppend = 0x2,
Search = 0x4,
FilterPreFixes = 0x8,
UseTab = 0x10,
UpDownKeyDropsList = 0x20,
RtlReading = 0x40
There is one very important thing to note about the
IAutoComplete2 interface. According to the original declaration it inherits
IAutoComplete, but when I wanted to declare this interface in C# it just wouldn't work if it was inheriting
IAutoComplete. I guess I've did something wrong, but it might just be a bug in C# or even in the .Net Framework. The only way it worked was when I add the functions of the
IAutoComplete in the
IAutoComplete2 interface and not to inherit. If someone has a solution to this problem I will gladly here it.
So, after all these declarations all we have is 2 interfaces with 4 functions total. Very simple indeed. As I've stated before all we need to do is create the
AutoComplete object, call the
Init function with the edit box window handle and the source object and then call the
Enable function and finally call the
SetOptions function to set some settings of the object. Lets see some code.
Here is the implementation of the
SetAutoComplete function in the
ShellAutoComplete class. This is the main function, we call this function after setting the
EditHandle member and the
ListSource member. Here it is:
public void SetAutoComplete(Boolean enable)
IAutoComplete2 iac2 = (IAutoComplete2)GetAutoComplete();
if (EditHandle == IntPtr.Zero)
throw new Exception("EditHandle must not be zero!");
if (ListSource == null)
throw new Exception("ListSource must not be null!");
ret = iac2.Init(EditHandle,ListSource,"","");
ret = iac2.SetOptions((UInt32)ACOptions);
ret= iac2.Enable(enable ? 1 : 0);
What we do here is call the
private function which creates an
object. After casting the object to the
interface we call the
function. When we finish we call
. Note that the
is another member that we set before calling this function.
Here is the declaration of the members of this class, the
SetAutoComplete function uses them:
public IntPtr EditHandle = IntPtr.Zero;
public Object ListSource = null;
public AutoCompleteOptions ACOptions =
AutoCompleteOptions.AutoSuggest | AutoCompleteOptions.AutoAppend;
In the following section we will discuss creating predefined source objects and their interfaces.
Section 4: Predefined Source Objects
As noted before the operating system supply us with 4 source objects. I this section we will discuss 3 of them:
ACLMRU. Also we will discuss their interfaces
So we want to create a source object. The creation itself is similar to the creation of the
AutoComplete object. Here is 3 static functions of the
ShellAutoComplete class that let you create those objects:
public static Object GetACLHistory()
Type typeACLHistory = Type.GetTypeFromCLSID(ShellGUIDs.CLSID_ACLHistory);
obj = Activator.CreateInstance(typeACLHistory);
public static Object GetACLMRU()
Type typeACLMRU = Type.GetTypeFromCLSID(ShellGUIDs.CLSID_ACLMRU);
obj = Activator.CreateInstance(typeACLMRU);
public static Object GetACListISF()
Type typeACListISF = Type.GetTypeFromCLSID(ShellGUIDs.CLSID_ACListISF);
obj = Activator.CreateInstance(typeACListISF);
No need to review those functions. I will not bring the declarations of the interfaces cause I don't use them in my code but we will discuss them. The
IACList interface has only one method named
Expand which receive only one string parameter. This method is used by the operating system. It tells the object to change the source list according to the string parameter. For example lets suppose the source object has all the files in the system. When the object gives its list, it contains only the strings in the root folder, once the user enters a delimiter the expand function is called and the object get an opportunity to set his string list to the files in the current folder. The
IACList2 interface has two methods.
SetOptions. These functions allow you to tune specific options of your source object. The options are depended on the source, so if you create a custom object you can inherit the
IACList2 interface also and have your object deal with its own defined options.
Note that an example of using the source objects can be found in the final section.
Section 5: User Defined Source Object
One of the main reasons for using the AutoComplete Object Model instead of the simple
SHAutoComplete function is the ability to create a custom list and use it with the
AutoComplete object. The source object need only to expose the
IACList2 interfaces are not mandatory. Now you probably expects me to declare the
IEnumString interface and use it. Well, here comes the good news, In the
System.Runtime.InteropServices there is a bunch of predefined managed interfaces. Among them is the interface
UCOMIEnumString which is the managed version of the
IEnumString interface, so in this case .Net has done the work alone and left me only to implement this interface in my own object.
Now we will develop our own custom source object named
SourceCustomList. This object will have a public field field named
StringList of type
string. The first thing to do is to declare his fields. A source object should also remember where in the string list he is, so we will declare a private integer to remember the current position in the list:
public string StringList;
private Int32 currentPosition = 0;
IEnumString has four functions that should be implemented. The first function is called
Next. This function receive a number of elements requested, a string array for returning the requested strings and another integer to write how many string are really returned. Here is our implementation:
public Int32 Next(
out Int32 pceltFetched)
pceltFetched = 0;
while ((currentPosition <= StringList.Length-1) && (pceltFetched<celt))
rgelt[pceltFetched] = StringList[currentPosition];
if (pceltFetched == celt)
The function is quite simple. First I set the
pceltFetched variable to 0 and then I iterate over the list and add strings into the requested array, as long as I have what to add. Finally the return values is depends whether I've filled the requested array or not.
The second method to implement is called
Skip. This function receive a number, and tell the object to skip some strings. So in our object we only need to advance the
currentPosition field. Here is the code:
public Int32 Skip(
currentPosition += (int)celt;
if (currentPosition <= StringList.Length-1)
Next is the
Reset function. I will not explain this function, rather I'll let you guess what it does:
public Int32 Reset()
currentPosition = 0;
Clone function. This function creates another object that contains the same enumeration state as the current one. In other words it creates an exact copy of this object:
public void Clone(
out UCOMIEnumString ppenum)
SourceCustomList clone = new SourceCustomList();
clone.currentPosition = currentPosition;
clone.StringList = (String)StringList.Clone();
ppenum = clone;
So there it is. We have created our own custom source object. Now all we should do is create an instance and set it as the source object of our
The only thing I haven't explained yet is how to use multi sources with the
AutoComplete object. This is covered in the next section.
Section 6: Multi Source object
So what if we want to use the custom object we have just created along with the History list? In this case the operating system supplied us a source object named
ACMulti. Remember him? I told you I'll explain him later. Well this object exposes the
IEnumString interface (like any other source object), and another interface named
IObjMgr. This interface allow us to attach several source objects to it. So when we want to use a combination of sources we need to create this object, attach some sources with functions of the
IObjMgr interface (soon to be reviewed) and set the
ACMulti object as the source object of the
AutoComplete object. Here is the code to create this system object:
public static Object GetACLMulti()
Type typeACLMulti = Type.GetTypeFromCLSID(ShellGUIDs.CLSID_ACLMulti);
obj = Activator.CreateInstance(typeACLMulti);
And here is the
public interface IObjMgr
This interface has functions for append and remove of other sources. I thing the definition is quite clear.
The next section will have an example of using the
ShellAutoComplete class, including using multi sources.
Section 7: Using the ShellAutoComplete
Brought to you here is a full example of using the class we have created in this article. The first sample uses the simple AutoComplete functionality. It includes setting some options and invoke the
DoAutoComplete function which in turn calls
private void btnConnectSimple_Click(object sender, System.EventArgs e)
ShellLib.ShellAutoComplete.AutoCompleteFlags flags = 0;
flags |= (chkFileSystem.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteFlags.FileSystem : 0;
flags |= (chkUrlHistory.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteFlags.UrlHistory : 0;
flags |= (chkUrlMRU.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteFlags.UrlMRU : 0;
flags |= (chkUseTab.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteFlags.UseTab : 0;
flags |= (chkFileSysOnly.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteFlags.FileSys_Only : 0;
flags |= (chkFileSysDirs.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteFlags.FileSys_Dirs : 0;
flags |= (rdoAutoAppendForceOff.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteFlags.AutoAppend_Force_Off : 0;
flags |= (rdoAutoAppendForceOn.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteFlags.AutoAppend_Force_On : 0;
flags |= (rdoAutoSuggestForceOff.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteFlags.AutoSuggest_Force_Off : 0;
flags |= (rdoAutoSuggestForceOn.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteFlags.AutoSuggest_Force_On : 0;
The second sample uses the AutoComplete Object Model and creates source objects, allowing also to create the multi source object and to append several sources to it:
private void btnConnectObject_Click(object sender, System.EventArgs e)
ShellLib.ShellAutoComplete ac = new ShellLib.ShellAutoComplete();
ac.EditHandle = edtFile.Handle;
ac.ACOptions = ShellLib.ShellAutoComplete.AutoCompleteOptions.None;
ac.ACOptions |= (chkACOAutoSuggest.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteOptions.AutoSuggest : 0;
ac.ACOptions |= (chkACOAutoAppend.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteOptions.AutoAppend : 0;
ac.ACOptions |= (chkACOSearch.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteOptions.Search : 0;
ac.ACOptions |= (chkACOUpDownKeyDropsList.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteOptions.UpDownKeyDropsList : 0;
ac.ACOptions |= (chkACOFilterPrefixs.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteOptions.FilterPreFixes : 0;
ac.ACOptions |= (chkACOUseTab.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteOptions.UseTab : 0;
ac.ACOptions |= (chkACORtlReading.Checked) ?
ShellLib.ShellAutoComplete.AutoCompleteOptions.RtlReading : 0;
MessageBox.Show("At least one source should be checked!");
ShellLib.IObjMgr multi =
ShellLib.SourceCustomList custom = new ShellLib.SourceCustomList();
custom.StringList = GetCustomList();
ac.ListSource = multi;
else if (rdoHistory.Checked)
ac.ListSource = ShellLib.ShellAutoComplete.GetACLHistory();
else if (rdoMRU.Checked)
ac.ListSource = ShellLib.ShellAutoComplete.GetACLMRU();
else if (rdoShellNamespace.Checked)
ac.ListSource = ShellLib.ShellAutoComplete.GetACListISF();
else if (rdoCustomList.Checked)
ShellLib.SourceCustomList custom = new ShellLib.SourceCustomList();
custom.StringList = GetCustomList();
ac.ListSource = custom;
I really recommend debugging the demo program to better understand the flow of these samples.
There was a problem using the
ShellAutoComplete class on ComboBox, because the .net ComboBox does not respond to the
CBEM_GETEDITCONTROL message, and the shell try to get the EditBox handle with this message. So the code sample has changed to give a solution to this problem. The solution is to use the API function
GetComboBoxInfo to get the handle manually, and then set the
EditHandle property to the internal EditBox handle of the ComboBox instead of the Combobox handle. Big thanks goes to Aleeza for finding this out.
Hope you like it, Don't forget to vote.