65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 实现 Shell,第四部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (84投票s)

2003 年 3 月 14 日

Ms-PL

14分钟阅读

viewsIcon

303783

downloadIcon

4667

本文讨论了 Windows 启用的自动完成功能以及如何在 C# 中使用它们。自动完成是指在编辑框中输入的字符串进行展开的能力。本文将开发一个类来在您的应用程序中使用此功能。

Sample Image - csdoesshell4.jpg

引言

大家好。在第二部分中,我说过我将在下一篇文章中开始讲解 Shell 扩展。我很抱歉,但正如我在第三部分中所说,在转向 Shell 扩展之前,还有一个问题需要讨论。我希望在下一篇文章中我将开始讲解它。涉及的问题是使用操作系统启用的自动完成功能。与上一篇文章(第三部分)讨论的应用程序桌面工具栏一样,本文也是关于一个有趣的主题。

注意:本文不需要预先阅读,但一如既往,我建议阅读之前的“C# 实现 Shell”各部分以及相关的 MSDN 文章。

C# 实现 Shell 编程,第一部分
C# 实现 Shell 功能,第二部分
C# 实现 Shell,第三部分

MSDN:使用自动完成

那么,这个自动完成是什么?我将从一个例子开始。点击“开始”菜单,然后点击“运行”,在打开的对话框中输入一个字符。您可能会看到一个可用选项列表来完成字符串。它看起来应该像这样:

Run Example

Internet Explorer 中的地址栏也是如此。开始输入,字符串的其余部分就会展开。

Run Example

在本文中,我们将学习操作系统允许我们做什么,以及如何在我们自己的应用程序中使用此功能。一如既往,我们将创建一个名为 ShellAutoComplete 的类,它能很好地封装它,这个类将被添加到我们正在开发的 ShellLib 库中。

好了,开始工作吧。

第一部分:使用 SHAutoComplete

我们通常想要完成的最常见任务是使用自动完成来展开文件名和目录,或者我们使用的 URL(历史记录)甚至是最近使用列表(MRU)中的字符串。所以,如果我们想完成其中一项任务,我们可以使用一个名为 SHAutoComplete 的函数。这个函数是 Shell API 函数的一部分。这个函数接收一个编辑框的窗口句柄和一个标志参数来设置一些自动完成选项。在使用这个函数作用于编辑框后,该控件将安装自动完成功能。这就像魔法一样。

那么 SHAutoComplete 函数是什么样的,我们如何将其转换为 C# 呢?这是原始定义:

HRESULT SHAutoComplete(          
    HWND hwndEdit,
    DWORD dwFlags);

这是 C# 等效版本:

// 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);

正如您所见,该函数有两个参数。第一个参数 hwndEdit 是我们想要启用自动完成的编辑框的窗口句柄。事实上,这个句柄也可以是包含嵌入式编辑框的窗口的句柄,只有当您想这样做时,才需要响应 CBEM_GETEDITCONTROL 消息,通过返回嵌入式编辑框的句柄。一个像这样工作的控件是 ComboBoxEx,当使用 CBS_DROPDOWN 样式时。总之,正常用法是直接作用于编辑框。

第二个参数是 dwFlags。此参数设置一些自动完成选项。它可以是 AutoCompleteFlags 枚举的任意组合值。这是它的定义:

[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  
}

如果您查看枚举定义,您会看到它们分为几类。影响自动完成源的值是 FileSystemUrlHistoryUrlMRU。覆盖注册表默认值的值是:AutoSuggest_Force_OnAutoSuggest_Force_OffAutoAppend_Force_OnAutoAppend_Force_Off。现在,在这里我可以解释自动建议和自动附加选项是什么。

自动附加:这意味着当您输入时,字符串会自动用当前输入的字符串完成,当您输入更多字符串时,它会变得更精确,但要记住的是,字符串是自动附加的。

自动建议:当您输入字符串时,会出现一个下拉列表,其中包含当前字符串完成的建议。当然,您可以从列表中选择一个字符串。

要记住的一件事是,您可以为自动完成列表选择多个源。它可以是文件系统、历史 URL 和最近使用列表的任意组合。但如果您想使用自己的列表呢?如果您想组合历史记录和您自己的列表呢?所有这些都不能用这个函数完成,但将在本文中进行回顾。

第二部分:自动完成对象模型

好吧,也许对象模型有点夸张,但有一个模型应该在这里解释。您看,微软已将整个自动完成功能放入了一个名为 AutoComplete 的 COM 对象中。该对象公开了 IAutoCompleteIAutoComplete2 接口。AutoComplete 对象知道如何创建一个带有字符串列表的窗口,以及如何在用户开始键入时展开字符串。AutoComplete 对象不具备的是字符串列表。字符串列表保存在另一个应该具有 IEnumString 接口的对象中。这个对象只知道如何枚举其字符串列表。操作系统提供了 4 个源对象。一个拥有文件系统字符串列表,名为 ACListISF。一个提供历史字符串列表,名为 ACLHistory。一个拥有 MRU 字符串列表,名为 ACLMRU。最后是一个特殊的名为 ACLMulti 的对象,稍后将进行解释。所有这些对象都具有 IEnumString 接口,并可用作 AutoComplete 对象的源。其中一些对象还具有接口:IACListIACList2。而 ACLMulti 对象还额外拥有 IObjMgr 接口。所有这些接口都将得到解释,但要记住的重要一点是,我们有一个主要的 AutoComplete 对象和几个可能的源。这是我为您制作的一个小图,以便更好地理解系统中拥有的对象及其接口。

Run Example

那么,为了在编辑框中实现自动完成,我们需要做什么?首先,我们需要创建 AutoComplete 对象,它有一个特定的 GUID(就像世界上任何其他 COM 对象一样)。然后我们创建一个源对象,例如 ACLHistory。然后我们将 ACLHistory 对象附加到我们的 AutoComplete 对象,并在我们的编辑框上激活 AutoComplete 对象。这就是使用对象模型而不是简单且不可扩展的 SHAutoComplete 函数时需要做的基本工作。

我提到了 GUID 的主题。我们有操作系统提供的 5 个对象。为了创建它们,我们需要它们的 GUID。这是它们的 C# 声明。这些声明在 ShellLib 库的现有类 ShellGUIDS 中。

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}");

下一部分将讨论创建和使用 AutoComplete 对象,这当然包括对其公开的接口的回顾。

第三部分:自动完成对象

我们应该做的第一件事是创建对象,那么我们如何在 C# 中创建 COM 对象?一种方法是“添加引用”对象,在这种情况下,我更喜欢更动态的方法,即使用 Activator 类。我们将在 ShellAutoComplete 类中编写一个私有函数,该函数将创建 Autocomplete 对象并将其返回给调用者。这是代码:

private Object GetAutoComplete()
{
    Type typeAutoComplete = Type.GetTypeFromCLSID(ShellGUIDs.CLSID_AutoComplete);

    Object obj;
    obj = Activator.CreateInstance(typeAutoComplete);
    return obj;
}

此函数使用 GetTypeFromCLSID 静态函数来返回已注册 COM 对象的 Type。然后使用 Activator 类的 CreateInstance 来创建对象。为了使用该对象,我们需要将其转换为其接口之一。

正如我之前所说,该对象有两个接口,我们首先将讨论 IAutoComplete。这是原始声明,然后是 C# 等效版本:

请注意,此处保留原始声明是为了让您更好地理解将 C++ 接口转换为 C# 接口时需要进行的更改。

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# 等效版本

[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. 
            
}

IAutoComplete 接口有两个函数:InitEnableInit 函数初始化 AutoComplete 对象,Enable 函数启用或禁用自动完成。请注意,Init 函数是您提供编辑框窗口句柄(在 hwndEdit 参数中)和源对象(在 punkACL 参数中)的地方。在本节后面,我将展示一个使用这些函数的示例。

自动完成对象公开的第二个接口是 IAutoComplete2。这个接口扩展了第一个接口,并声明了另外两个方法。这是它的声明:

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;

};

以及 C# 等效版本:

[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. 
            
}

这里您可以看到两个额外的函数:SetOptionsGetOptions。这些函数只是为了让您稍微调整 AutoComplete 对象的行为。允许的选项是 AutoCompleteOptions 枚举中声明的选项:

[Flags]
public enum AutoCompleteOptions
{
    None                      = 0,
    AutoSuggest               = 0x1,
    AutoAppend                = 0x2,
    Search                    = 0x4,
    FilterPreFixes            = 0x8,
    UseTab                    = 0x10,
    UpDownKeyDropsList        = 0x20,
    RtlReading                = 0x40
}     

关于 IAutoComplete2 接口有一件非常重要的事情需要注意。根据原始声明,它继承了 IAutoComplete,但当我尝试在 C# 中声明这个接口时,如果它继承 IAutoComplete,它就无法工作。我猜我做错了什么,但这可能只是 C# 甚至 .Net Framework 中的一个 bug。唯一有效的方法是将 IAutoComplete 的函数添加到 IAutoComplete2 接口中,而不是继承。如果有人有解决方案,我将乐于听到。

所以,经过所有这些声明之后,我们只有 2 个接口,总共 4 个函数。确实非常简单。正如我之前所说,我们所需要做的就是创建 AutoComplete 对象,调用带有编辑框窗口句柄和源对象的 Init 函数,然后调用 Enable 函数,最后调用 SetOptions 函数来设置对象的一些设置。让我们来看一些代码。

这是 ShellAutoComplete 类中 SetAutoComplete 函数的实现。这是主函数,我们在设置 EditHandle 成员和 ListSource 成员后调用此函数。这是:

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);
}
在这里,我们调用 GetAutoComplete 私有函数,该函数创建一个 AutoComplete 对象。将对象转换为 IAutoComplete2 接口后,我们调用 Init 函数,然后是 SetOptions,最后是 Enable 函数。完成后,我们调用 ReleaseComObject。请注意,ACOptions 是我们在调用此函数之前设置的另一个成员。

这是此类的成员声明,SetAutoComplete 函数使用了它们:

public IntPtr EditHandle = IntPtr.Zero;
public Object ListSource = null;
public AutoCompleteOptions ACOptions = 
            AutoCompleteOptions.AutoSuggest | AutoCompleteOptions.AutoAppend;

在下一部分中,我们将讨论创建预定义的源对象及其接口。

第四部分:预定义源对象

如前所述,操作系统提供了 4 个源对象。在本节中,我们将讨论其中 3 个:ACListISFACLHistoryACLMRU。我们还将讨论它们的接口 IACListIACList2

所以,我们想创建一个源对象。创建本身类似于创建 AutoComplete 对象。这是 ShellAutoComplete 类的 3 个静态函数,允许您创建这些对象:

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

无需回顾这些函数。我不会提供接口的声明,因为我不在我的代码中使用它们,但我们将讨论它们。IACList 接口只有一个名为 Expand 的方法,它只接收一个字符串参数。这个方法由操作系统使用。它告诉对象根据字符串参数更改源列表。例如,假设源对象包含系统中的所有文件。当对象提供其列表时,它只包含根文件夹中的字符串,一旦用户输入分隔符,就会调用 expand 函数,对象就有机会将其字符串列表设置为当前文件夹中的文件。IACList2 接口有两个方法:GetOptionsSetOptions。这些函数允许您调整源对象的特定选项。选项取决于源,因此如果您创建了一个自定义对象,您也可以继承 IACList2 接口,并让您的对象处理自己定义的选项。

请注意,源对象的使用示例可以在最后一节中找到。

第五部分:用户定义的源对象

使用自动完成对象模型而不是简单的 SHAutoComplete 函数的一个主要原因是因为能够创建自定义列表并将其与 AutoComplete 对象一起使用。源对象只需要公开 IEnumString 接口。IACListIACList2 接口不是必需的。现在您可能期望我声明 IEnumString 接口并使用它。那么,这是个好消息,在 System.Runtime.InteropServices 中有一组预定义的托管接口。其中有 UCOMIEnumString 接口,它是 IEnumString 接口的托管版本,所以在这种情况下,.Net 独自完成了工作,只剩下我来实现我自己的对象中的这个接口。

现在我们将开发我们自己的自定义源对象,名为 SourceCustomList。该对象将有一个名为 StringList 的公共字段,类型为 string[]。首先要做的是声明它的字段。源对象还应该记住它在字符串列表中的位置,因此我们将声明一个私有整数来记住列表中的当前位置。

public string[] StringList;
private Int32 currentPosition = 0;

IEnumString 有四个应该实现的函数。第一个函数称为 Next。该函数接收请求的元素数量,一个用于返回请求字符串的字符串数组,以及另一个整数来写入实际返回的字符串数量。这是我们的实现:

// 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;
}

该函数很简单。首先,我将 pceltFetched 变量设置为 0,然后迭代列表并将字符串添加到请求的数组中,只要我有要添加的内容。最后,返回值取决于我是否填满了请求的数组。

要实现的第二个方法称为 Skip。该函数接收一个数字,并告诉对象跳过一些字符串。所以,在我们的对象中,我们只需要前进 currentPosition 字段。这是代码:

// 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;
}

接下来是 Reset 函数。我不会解释这个函数,而是让您猜测它是什么意思。

// This method resets the enumeration sequence to the beginning.
public Int32 Reset()
{
    currentPosition = 0;
    return 0;
}

最后是 Clone 函数。这个函数创建另一个包含与当前对象相同枚举状态的对象。换句话说,它创建了这个对象的精确副本。

// 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;
}                    

就这样。我们创建了自己的自定义源对象。现在我们应该做的就是创建一个实例,并将其设置为我们 AutoComplete 对象的源对象。

我唯一还没有解释的是如何使用多源与 AutoComplete 对象。这将在下一节中介绍。

第六部分:多源对象

那么,如果我们想将我们刚刚创建的自定义对象与历史记录列表一起使用呢?在这种情况下,操作系统提供了名为 ACMulti 的源对象。还记得他吗?我告诉过您稍后会解释他。这个对象公开了 IEnumString 接口(就像其他任何源对象一样),以及一个名为 IObjMgr 的接口。这个接口允许我们将多个源对象附加到它。所以,当我们想使用源的组合时,我们需要创建这个对象,使用 IObjMgr 接口的函数(稍后将回顾)附加一些源,并将 ACMulti 对象设置为 AutoComplete 对象的源对象。这是创建此系统对象的代码:

public static Object GetACLMulti()
{
    Type typeACLMulti = Type.GetTypeFromCLSID(ShellGUIDs.CLSID_ACLMulti);

    Object obj;
    obj = Activator.CreateInstance(typeACLMulti);
    return obj;
}

这是 IObjMgr 定义:

[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. 
        
}

这个接口有用于追加和移除其他源的函数。我认为定义很清楚。

下一节将有一个使用 ShellAutoComplete 类的示例,包括使用多源。

第七部分:使用 ShellAutoComplete

在此提供一个使用我们在本文中创建的类的完整示例。第一个示例使用简单的自动完成功能。它包括设置一些选项并调用 DoAutoComplete 函数,该函数又调用 SHAutoComplete

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);
}

第二个示例使用自动完成对象模型并创建源对象,还允许创建多源对象并将多个源附加到它。

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

}

我强烈建议调试演示程序以更好地理解这些示例的流程。

更新

在 ComboBox 上使用 ShellAutoComplete 类时出现了一个问题,因为 .net ComboBox 不响应 CBEM_GETEDITCONTROL 消息,而 Shell 尝试通过此消息获取 EditBox 句柄。因此,代码示例已更改,以解决此问题。解决方案是使用 API 函数 GetComboBoxInfo 手动获取句柄,然后将 EditHandle 属性设置为 ComboBox 的内部 EditBox 句柄,而不是 ComboBox 句柄。非常感谢 **Aleeza** 发现了这一点。

 

希望您喜欢,别忘了投票。

© . All rights reserved.