【问题标题】:How to make autocomplete on a TextBox show suggestions when empty如何在 TextBox 上进行自动完成,在空时显示建议
【发布时间】:2011-03-12 06:21:42
【问题描述】:

我在文本框(实际上是 ToolStripTextBox)上使用 AutoComplete 属性。这工作正常,只是在我至少输入一个字符之前它不会显示。即使文本框为空,我如何才能显示建议?

Mode = Suggest
Source = CustomSource

以编程方式设置源并限制为 10 个项目

或者,如果有人知道如何强制建议以编程方式显示在可能是解决方案的 OnEnter 事件中

【问题讨论】:

  • 它使用您当前的输入来减少显示的可能性列表。如果您在文本框中没有输入任何内容,则可能的列表是无限的。如果它不是无限的(因为你有一个有限列表),那么也许你应该考虑使用自动完成组合框来代替?
  • 我的set总是最多10个

标签: winforms autocomplete


【解决方案1】:

解决方案是直接封装和调用 AutoSuggest WinShell 函数。然后,您可以轻松启用此功能(甚至免费获得一些额外的功能)。

下面是一个工作示例,即使在框中没有输入任何内容(当用户按下向上/向下箭头时),它也允许您显示 AutoSuggest 列表

与此 Gist 中的代码相同:https://gist.github.com/sverrirs/e6a64faaab341882c3e801792f5e87ae

public void EnableAutoSuggest(TextBox tb, string[] suggestions) {
    // Try to enable a more advanced settings for AutoComplete via the WinShell interface
    try {
        var source = new SourceCustomList() {StringList = suggestions.ToArray()};
        // For options descriptions see: 
        // https://docs.microsoft.com/en-us/windows/desktop/api/shldisp/ne-shldisp-_tagautocompleteoptions
        var options = AUTOCOMPLETEOPTIONS.ACO_UPDOWNKEYDROPSLIST | AUTOCOMPLETEOPTIONS.ACO_USETAB |
                      AUTOCOMPLETEOPTIONS.ACO_AUTOAPPEND | AUTOCOMPLETEOPTIONS.ACO_AUTOSUGGEST;
        AutoCompleteExt.Enable(tb.Handle, source, options);
    }
    catch (Exception) {
        // Incase of an error, let's fall back to the default
        var source = new AutoCompleteStringCollection();
        source.AddRange(suggestions);
        tb.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
        tb.AutoCompleteSource = AutoCompleteSource.CustomSource;
        tb.AutoCompleteCustomSource = source;
    }      
}

public void DisableAutoSuggest(TextBox tb) {
    tb.AutoCompleteCustomSource = null;
    AutoCompleteExt.Disable(tb.Handle);
}

以及AutoCompleteExt的实现

using System;
using System.Runtime.InteropServices;

/// <summary>
/// From: https://www.codeproject.com/Articles/3792/C-does-Shell-Part-4
/// Note: The UCOMIEnumString interface is deprecated in .NET as of 2018!
/// </summary>
public class AutoCompleteExt {
    public static Guid CLSID_AutoComplete = new Guid("{00BB2763-6A77-11D0-A535-00C04FD7D062}");

    private static object GetAutoComplete()
    {
        return Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_AutoComplete));
    }

    public static void Enable(IntPtr controlHandle, SourceCustomList items, AUTOCOMPLETEOPTIONS options) {
        if (controlHandle == IntPtr.Zero)
            return;

        IAutoComplete2 iac = null;
        try {
            iac = (IAutoComplete2) GetAutoComplete();
            int ret = iac.Init(controlHandle, items, "", "");
            ret = iac.SetOptions(options);
            ret = iac.Enable(true);
        }
        finally {
            if (iac != null)
                Marshal.ReleaseComObject(iac);
        }
    }

    public static void Disable(IntPtr controlHandle) {
        if (controlHandle == IntPtr.Zero)
            return;

        IAutoComplete2 iac = null;
        try {
            iac = (IAutoComplete2) GetAutoComplete();
            iac.Enable(false);
        }
        finally {
            if (iac != null)
                Marshal.ReleaseComObject(iac);
        }
    }
}
/// <summary>
/// From https://www.pinvoke.net/default.aspx/Interfaces.IAutoComplete2
/// </summary>
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("EAC04BC0-3791-11D2-BB95-0060977B464C")]
public interface IAutoComplete2 {
    [PreserveSig]
    int Init(
        // Handle to the window for the system edit control that is to
        // have autocompletion enabled. 
        IntPtr hwndEdit,

        // 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.IUnknown)] object punkACL,

        // 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 pwszRegKeyPath,

        // 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/". 
        [MarshalAs(UnmanagedType.LPWStr)] string pwszQuickComplete
    );

    // Enables or disables autocompletion.
    [PreserveSig]
    int Enable(bool value);

    // Sets the current autocomplete options.
    [PreserveSig]
    int SetOptions(AUTOCOMPLETEOPTIONS dwFlag);

    // Retrieves the current autocomplete options.
    [PreserveSig]
    int GetOptions(out AUTOCOMPLETEOPTIONS pdwFlag);
}

/// <summary>
///   Specifies values used by IAutoComplete2::GetOptions and 
///   "IAutoComplete2.SetOptions" for options surrounding autocomplete.
/// </summary>
/// <remarks>
///   [AUTOCOMPLETEOPTIONS Enumerated Type ()]
///   http://msdn.microsoft.com/en-us/library/bb762479.aspx
/// </remarks>
[Flags]
public enum AUTOCOMPLETEOPTIONS {
    /// <summary>Do not autocomplete.</summary>
    ACO_NONE = 0x0000,

    /// <summary>Enable the autosuggest drop-down list.</summary>
    ACO_AUTOSUGGEST = 0x0001,

    /// <summary>Enable autoappend.</summary>
    ACO_AUTOAPPEND = 0x0002,

    /// <summary>Add a search item to the list of 
    /// completed strings. When the user selects 
    /// this item, it launches a search engine.</summary>
    ACO_SEARCH = 0x0004,

    /// <summary>Do not match common prefixes, such as 
    /// "www." or "http://".</summary>
    ACO_FILTERPREFIXES = 0x0008,

    /// <summary>Use the TAB key to select an 
    /// item from the drop-down list.</summary>
    ACO_USETAB = 0x0010,

    /// <summary>Use the UP ARROW and DOWN ARROW keys to 
    /// display the autosuggest drop-down list.</summary>
    ACO_UPDOWNKEYDROPSLIST = 0x0020,

    /// <summary>Normal windows display text left-to-right 
    /// (LTR). Windows can be mirrored to display languages 
    /// such as Hebrew or Arabic that read right-to-left (RTL). 
    /// Typically, control text is displayed in the same 
    /// direction as the text in its parent window. If 
    /// ACO_RTLREADING is set, the text reads in the opposite 
    /// direction from the text in the parent window.</summary>
    ACO_RTLREADING = 0x0040,

    /// <summary>[Windows Vista and later]. If set, the 
    /// autocompleted suggestion is treated as a phrase 
    /// for search purposes. The suggestion, Microsoft 
    /// Office, would be treated as "Microsoft Office" 
    /// (where both Microsoft AND Office must appear in 
    /// the search results).</summary>
    ACO_WORD_FILTER = 0x0080,

    /// <summary>[Windows Vista and later]. Disable prefix 
    /// filtering when displaying the autosuggest dropdown. 
    /// Always display all suggestions.</summary>
    ACO_NOPREFIXFILTERING = 0x0100
}

/// <summary>
/// Implements the https://docs.microsoft.com/en-us/windows/desktop/api/objidl/nn-objidl-ienumstring
/// interface for autoccomplete
/// </summary>
public class SourceCustomList : UCOMIEnumString {

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

    public int Next(
            int 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 int 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;
    }

    /// <summary>
    /// This method skips the next specified number of elements in the enumeration sequence.
    /// </summary>
    /// <param name="celt"></param>
    /// <returns></returns>
    public int Skip(
        int celt)                    // Number of elements to be skipped. 
    {
        currentPosition += celt;
        if (currentPosition <= StringList.Length-1)
            return 0;
        else
            return 1;
    }

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

    // 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 {
            currentPosition = currentPosition,
            StringList = (String[]) StringList.Clone()
        };
        ppenum = clone;
    }        
}

【讨论】:

    【解决方案2】:

    考虑到这是一个hack。我设法解决了这个问题,并解决了 API 功能不足的问题。我会用代码告诉你:

        dim source as AutoCompleteStringCollection = new AutoCompleteStringColection()
        dim values() as String = new String() {" Monday", _
                                               " Tuesday", _
                                               " Wednesday", _
                                               " Thursday", _
                                               " Friday", _
                                               " Saturday", _
                                               " Sunday" }
        TextBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend
        TextBox1.AutoCompleteSource = AutoCompleteSource.CustomSource
        TextBox1.AutoCompleteCustomSource = source
    

    也就是说,为自动完成列表中的每个字符串添加一个空格。然后,这是您对该事实的了解,并将其用于您方便的目标。

    例如,您可以在单击、聚焦等时在 TextBox 中添加一个空格(请注意,这可以用任何字符完成。其想法是要知道自动完成列表中的每个字符串都以相同的开头字符)

    必须注意这一点。事实上,可以考虑扩展TextBox 表单并管理输入字符串的正确修剪。

    再次重申,是否将此称为推荐由您自己决定。这个答案的目的是解决想要一个TextBox 下拉建议列表而无需开始键入 API 的限制的问题,也称为解决方法或 ugly-hack

    【讨论】:

    • 注意:文字内容右对齐效果更好。
    • 有趣的方法。您可能还需要防止用户删除前导空格。当然比实现自己的自动完成行为要容易得多。
    • 确实如此。我的ExtendedTextBox 所做的是使用AutoCompletCustomSource 或提供的RegEx 验证输入(我可以使用ComboBox,但我正在重用一个扩展文本框,该文本框提供一个图标来提供有关输入的视觉反馈) .当失去焦点并且没有有效输入时,我清除文本。当输入且没有有效输入时,清除文本My.Computer.keyboard.SendKeys(" ")Sendkeys.flush()。获取输入字符串的属性是:TextBox1.Text.TrimStart(" ") 同样,必须谨慎管理输入。
    【解决方案3】:

    .net api 中没有这种可能性。更何况.net内部使用的IAutoComplete shell接口没有这种可能性。

    所以你应该接受它,或者实现你自己的自动完成行为而不使用默认行为。您可以按照之前的建议使用组合框下拉菜单,甚至可以创建自己的自动完成控件(在我的应用程序中,我选择了最后一个变体)。

    【讨论】:

      【解决方案4】:

      我不确定是否有任何解决方案,尤其是如果您删除 TextBox 中的所有文本,建议框会自动关闭。

      一种解决方法可能是让ComboBoxTextBox 重叠,您可以让它显示所有选项,但如果选择其中一个或输入某些内容,您会自动切换到TextBox 并显示它.
      虽然我建议在这种情况下我可能会考虑完全摆脱 TextBox 并坚持使用 ComboBox,因为这就是它的用途。

      要在ComboBox 中以编程方式显示下拉菜单,请查看SendMessage API 和消息CB_SHOWDROPDOWN(如果我没记错的话,您可以使用相同的 API/消息但使用不同的参数以编程方式关闭它也需要)。

      【讨论】:

        猜你喜欢
        • 2016-09-22
        • 1970-01-01
        • 2018-07-31
        • 1970-01-01
        • 2021-08-04
        • 2018-04-27
        • 2017-08-31
        • 1970-01-01
        • 2016-08-13
        相关资源
        最近更新 更多