|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionHello 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 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 Well, lets get to work. Section 1: using SHAutoCompleteThe 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 So how is the HRESULT SHAutoComplete(
HWND hwndEdit,
DWORD dwFlags);
and here is the C# equivalent: // Instructs system edit controls to use AutoComplete to help complete URLs or
// file system paths.
[DllImport("shlwapi.dll")]
public static extern Int32 SHAutoComplete(
IntPtr hwndEdit,
UInt32 dwFlags);
As you can see, the function has two parameters. The first one, The second parameter is [Flags]
public enum AutoCompleteFlags : uint
{
// The default setting, equivalent to FileSystem | UrlAll. Default cannot be
// combined with any other flags.
Default = 0x00000000,
// This includes the File System as well as the rest of the shell
// (Desktop\My Computer\Control Panel\)
FileSystem = 0x00000001,
// Include the URLs in the users History and Recently Used lists. Equivalent
// to UrlHistory | UrlMRU.
UrlAll = (UrlHistory | UrlMRU),
// Include the URLs in the user's History list.
UrlHistory = 0x00000002,
// Include the URLs in the user's Recently Used list.
UrlMRU = 0x00000004,
// Allow the user to select from the autosuggest list by pressing the TAB
// key. If this flag is not set, pressing the TAB key will shift focus to
// the next control and close the autosuggest list. If UseTab is set,
// pressing the TAB key will select the first item in the list. Pressing
// TAB again will select the next item in the list, and so on. When the user
// reaches the end of the list, the next TAB key press will cycle the focus
// back to the edit control. This flag must be used in combination with one
// or more of the FileSys* or Url* flags.
UseTab = 0x00000008,
// This includes the File System
FileSys_Only = 0x00000010,
// Same as FileSys_Only except it only includes directories, UNC servers,
// and UNC server shares.
FileSys_Dirs = 0x00000020,
// Ignore the registry value and force the autosuggest feature on. A
// selection of possible completed strings will be displayed as a drop-down
// list, below the edit box. This flag must be used in combination with one
// or more of the FileSys* or Url* flags.
AutoSuggest_Force_On = 0x10000000,
// Ignore the registry default and force the autosuggest feature off. This
// flag must be used in combination with one or more of the FileSys* or
// Url* flags.
AutoSuggest_Force_Off = 0x20000000,
// Ignore the registry value and force the autoappend feature on. The
// completed string will be displayed in the edit box with the added
// characters highlighted. This flag must be used in combination with one
// or more of the FileSys* or Url* flags.
AutoAppend_Force_On = 0x40000000,
// Ignore the registry default and force the autoappend feature off. This
// flag must be used in combination with one or more of the FileSys* or
// Url* flags.
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 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 ModelWell, 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
Now, what do we need to do in order to have AutoComplete on an edit box? First we need to create the 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 public static Guid CLSID_AutoComplete =
new Guid("{00BB2763-6A77-11D0-A535-00C04FD7D062}");
public static Guid CLSID_ACLHistory =
new Guid("{00BB2764-6A77-11D0-A535-00C04FD7D062}");
public static Guid CLSID_ACListISF =
new Guid("{03C036F1-A186-11D0-824A-00AA005B4383}");
public static Guid CLSID_ACLMRU =
new Guid("{6756A641-dE71-11D0-831B-00AA005B4383}");
public static Guid CLSID_ACLMulti =
new Guid("{00BB2765-6A77-11D0-A535-00C04FD7D062}");
The next section will discuss creating and using of the Section 3: AutoComplete ObjectThe 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 private Object GetAutoComplete()
{
Type typeAutoComplete = Type.GetTypeFromCLSID(ShellGUIDs.CLSID_AutoComplete);
Object obj;
obj = Activator.CreateInstance(typeAutoComplete);
return obj;
}
This function use the As I've said before, this object has two Interfaces, first we will discuss 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. MIDL_INTERFACE("00bb2762-6a77-11d0-a535-00c04fd7d062")
IAutoComplete : public IUnknown
{
public:
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;
};
C# equivalent: [ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("00BB2762-6A77-11D0-A535-00C04FD7D062")]
public interface IAutoComplete
{
// Initializes the autocomplete object.
[PreserveSig]
Int32 Init(
IntPtr hwndEdit, // Handle to the window for the
// system edit control that is to
// have autocompletion enabled.
[MarshalAs(UnmanagedType.IUnknown)]
Object punkACL, // Pointer to the IUnknown interface
// of the string list object that
// is responsible for generating
// candidates for the completed
// string. The object must expose
// an IEnumString interface.
[MarshalAs(UnmanagedType.LPWStr)]
String pwszRegKeyPath, // Pointer to an optional null-
// terminated Unicode string that
// gives the registry path,
// including the value name, where
// the format string is stored as
// a REG_SZ value. The
// autocomplete object first
// looks for the path under
// HKEY_CURRENT_USER . If it fails,
// it then tries HKEY_LOCAL_MACHINE.
// For a discussion of the
// format string, see the
// definition of pwszQuickComplete.
[MarshalAs(UnmanagedType.LPWStr)]
String pwszQuickComplete); // Pointer to an optional string
// that specifies the format to be
// used if the user enters some text
// and presses CTRL+ENTER. Set
// this parameter to NULL to disable
// quick completion. Otherwise,
// the autocomplete object treats
// pwszQuickComplete as a sprintf
// format string, and the text in
// the edit box as its associated
// argument, to produce a new
// string. For example, set
// pwszQuickComplete to
// "http://www. %s.com/". When a
// user enters "MyURL" into the
// edit box and presses CTRL+ENTER,
// the text in the edit box is
// updated to
// "http://www.MyURL.com/".
// Enables or disables autocompletion.
[PreserveSig]
Int32 Enable(
Int32 fEnable); // Value that is set to TRUE to
// enable autocompletion, or to
// FALSE to disable it.
}
The The second interface the AutoComplete object expose is IAutoComplete2. This interface expands the first one and declares two more methods. Here is its declaration: MIDL_INTERFACE("EAC04BC0-3791-11d2-BB95-0060977B464C")
IAutoComplete2 : public IAutoComplete
{
public:
virtual HRESULT STDMETHODCALLTYPE SetOptions(
/* [in] */ DWORD dwFlag) = 0;
virtual HRESULT STDMETHODCALLTYPE GetOptions(
/* [out] */ DWORD *pdwFlag) = 0;
};
and the c# equivalent: [ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("EAC04BC0-3791-11D2-BB95-0060977B464C")]
public interface IAutoComplete2 /*: IAutoComplete */
{
// Initializes the autocomplete object.
[PreserveSig]
Int32 Init(
IntPtr hwndEdit, // Handle to the window for the system
// edit control that is to
// have autocompletion enabled.
[MarshalAs(UnmanagedType.IUnknown)]
Object punkACL, // Pointer to the IUnknown interface
// of the string list object that
// is responsible for generating
// candidates for the completed
// string. The object must expose
// an IEnumString interface.
[MarshalAs(UnmanagedType.LPWStr)]
String pwszRegKeyPath, // Pointer to an optional null-
// terminated Unicode string that
// gives the registry path,
// including the value name, where
// the format string is stored as
// a REG_SZ value. The
// autocomplete object first
// looks for the path under
// HKEY_CURRENT_USER . If it fails,
// it then tries
// HKEY_LOCAL_MACHINE.
// For a discussion of the format
// string, see the definition of
// pwszQuickComplete.
[MarshalAs(UnmanagedType.LPWStr)]
String pwszQuickComplete); // Pointer to an optional string
// that specifies the format to be
// used if the user enters some text
// and presses CTRL+ENTER. Set
// this parameter to NULL to
// disable quick completion.
// Otherwise, the autocomplete
// object treats pwszQuickComplete
// as a sprintf format string,
// and the text in the
// edit box as its associated
// argument, to produce a new
// string. For example, set
// pwszQuickComplete to
// "http://www. %s.com/". When a
// user enters "MyURL" into the edit
// box and presses CTRL+ENTER, the
// text in the edit box is updated
// to "http://www.MyURL.com/".
// Enables or disables autocompletion.
[PreserveSig]
Int32 Enable(
Int32 fEnable); // Value that is set to
// TRUE to enable autocompletion,
// or to FALSE to disable it.
// Sets the current autocomplete options.
[PreserveSig]
Int32 SetOptions(
UInt32 dwFlag); // Flags that allow an application to specify
// autocomplete options.
// Retrieves the current autocomplete options.
[PreserveSig]
Int32 GetOptions(
out UInt32 pdwFlag); // that indicate the options that are
// currently set.
}
Here you see two extra functions: [Flags]
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 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 Here is the implementation of the public void SetAutoComplete(Boolean enable)
{
Int32 ret;
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);
Marshal.ReleaseComObject(iac2);
}
What we do here is call the GetAutoComplete private function which creates an AutoComplete object. After casting the object to the IAutoComplete2 interface we call the Init function, then SetOptions and finally Enable function. When we finish we call ReleaseComObject. Note that the ACOptions is another member that we set before calling this function.
Here is the declaration of the members of this class, the 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 ObjectsAs noted before the operating system supply us with 4 source objects. I this section we will discuss 3 of them: So we want to create a source object. The creation itself is similar to the creation of the public static Object GetACLHistory()
{
Type typeACLHistory = Type.GetTypeFromCLSID(ShellGUIDs.CLSID_ACLHistory);
Object obj;
obj = Activator.CreateInstance(typeACLHistory);
return obj;
}
public static Object GetACLMRU()
{
Type typeACLMRU = Type.GetTypeFromCLSID(ShellGUIDs.CLSID_ACLMRU);
Object obj;
obj = Activator.CreateInstance(typeACLMRU);
return obj;
}
public static Object GetACListISF()
{
Type typeACListISF = Type.GetTypeFromCLSID(ShellGUIDs.CLSID_ACListISF);
Object obj;
obj = Activator.CreateInstance(typeACListISF);
return obj;
}
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 Note that an example of using the source objects can be found in the final section. Section 5: User Defined Source ObjectOne of the main reasons for using the AutoComplete Object Model instead of the simple Now we will develop our own custom source object named public string[] StringList;
private Int32 currentPosition = 0;
The // This method retrieves the next celt items in the enumeration sequence. If
// there are fewer than the requested number of elements left in the sequence,
// it retrieves the remaining elements. The number of elements actually retrieved
// is returned through pceltFetched, unless the caller passed in NULL for that
// parameter.
public Int32 Next(
Int32 celt, // Number of elements being requested.
String[] rgelt, // Array of size celt (or larger) of the
// elements of interest. The type of this
// parameter depends on the item being
// enumerated.
out Int32 pceltFetched) // Pointer to the number of elements actually
// supplied in rgelt. The Caller can pass
// in NULL if celt is 1.
{
pceltFetched = 0;
while ((currentPosition <= StringList.Length-1) && (pceltFetched<celt))
{
rgelt[pceltFetched] = StringList[currentPosition];
pceltFetched++;
currentPosition++;
}
if (pceltFetched == celt)
return 0; // S_OK;
else
return 1; // S_FALSE;
}
The function is quite simple. First I set the The second method to implement is called // This method skips the next specified number of elements in the
// enumeration sequence.
public Int32 Skip(
Int32 celt) // Number of elements to be skipped.
{
currentPosition += (int)celt;
if (currentPosition <= StringList.Length-1)
return 0;
else
return 1;
}
Next is the // This method resets the enumeration sequence to the beginning.
public Int32 Reset()
{
currentPosition = 0;
return 0;
}
Finally, the // This method creates another enumerator that contains the same enumeration
// state as the current one. Using this function, a client can record a
// particular point in the enumeration sequence and return to that point at a
// later time. The new enumerator supports the same interface as the original one.
public void Clone(
out UCOMIEnumString ppenum) // Address of the IEnumString pointer
// variable that receives the interface
// pointer to the enumeration object. If
// the method is unsuccessful, the value
// of this output variable is undefined.
{
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 Section 6: Multi Source objectSo 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 public static Object GetACLMulti()
{
Type typeACLMulti = Type.GetTypeFromCLSID(ShellGUIDs.CLSID_ACLMulti);
Object obj;
obj = Activator.CreateInstance(typeACLMulti);
return obj;
}
And here is the [ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("00BB2761-6A77-11D0-A535-00C04FD7D062")]
public interface IObjMgr
{
// Appends an object to the collection of managed objects.
[PreserveSig]
Int32 Append(
[MarshalAs(UnmanagedType.IUnknown)]
Object punk); // Address of the IUnknown interface of the object
// to be added to the list.
// Removes an object from the collection of managed objects.
[PreserveSig]
Int32 Remove(
[MarshalAs(UnmanagedType.IUnknown)]
Object punk); // Address of the IUnknown interface of the object
// to be removed from the list.
}
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 Section 7: Using the ShellAutoCompleteBrought 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 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;
ShellLib.ShellAutoComplete.DoAutoComplete(edtFile.Handle,flags);
}
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)
{
// create an AutoComplete object
ShellLib.ShellAutoComplete ac = new ShellLib.ShellAutoComplete();
// set edit handle
ac.EditHandle = edtFile.Handle;
// set options
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;
// set source
if (rdoMultiSource.Checked)
{
if ((!chkHistory.Checked)
&& (!chkMRU.Checked)
&& (!chkShellNamespace.Checked)
&& (!chkCustomList.Checked))
{
MessageBox.Show("At least one source should be checked!");
return;
}
ShellLib.IObjMgr multi =
(ShellLib.IObjMgr)ShellLib.ShellAutoComplete.GetACLMulti();
if (chkHistory.Checked)
multi.Append(ShellLib.ShellAutoComplete.GetACLHistory());
if (chkMRU.Checked)
multi.Append(ShellLib.ShellAutoComplete.GetACLMRU());
if (chkShellNamespace.Checked)
multi.Append(ShellLib.ShellAutoComplete.GetACListISF());
if (chkCustomList.Checked)
{
ShellLib.SourceCustomList custom = new ShellLib.SourceCustomList();
custom.StringList = GetCustomList();
multi.Append(custom);
}
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;
}
// activate AutoComplete
ac.SetAutoComplete(true);
}
I really recommend debugging the demo program to better understand the flow of these samples. Update:There was a problem using the
Hope you like it, Don't forget to vote.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||