【问题标题】:C# AutoCompleteC# 自动完成
【发布时间】:2023-03-19 22:12:01
【问题描述】:

我正在尝试向文本框添加自动完成功能,结果来自数据库。它们的格式为

[001] 最后,第一个中间

目前您必须输入 [001]... 才能显示条目。 所以问题是,即使我先输入名字,我也希望它完成。所以如果一个条目是

[001] 史密斯,约翰 D

如果我开始输入 John,那么此条目应该会显示在自动完成的结果中。

目前的代码看起来像

AutoCompleteStringCollection acsc = new AutoCompleteStringCollection();
txtBox1.AutoCompleteCustomSource = acsc;
txtBox1.AutoCompleteMode = AutoCompleteMode.Suggest; 
txtBox1.AutoCompleteSource = AutoCompleteSource.CustomSource; 

....

if (results.Rows.Count > 0)
    for (int i = 0; i < results.Rows.Count && i < 10; i++) 
    {
        row = results.Rows[i];
        acsc.Add(row["Details"].ToString());
    }
}

results 是一个包含查询结果的数据集

查询是使用 like 语句的简单搜索查询。如果我们不使用自动完成功能并且只是将结果扔到一个数组中,则会返回正确的结果。

有什么建议吗?

编辑:

这是返回结果的查询

SELECT Name from view_customers where Details LIKE '{0}'

其中 {0} 是搜索字符串的占位符。

【问题讨论】:

  • 乍一看还可以。查看更多代码会很有用,包括使用的查询和“id”的初始化(你有两个自动完成框吗?)。
  • 好的,添加了对查询的编辑。我只使用 1 个自动完成框。该 ID 实际上从未在结果中使用,它被添加以供以后使用,因此我在此编辑中将其删除。很抱歉造成混乱。
  • 有一个不错的免费 c# autocomplete control 可用 (with source code) 很容易修改。

标签: c# winforms autocomplete


【解决方案1】:

这将为您提供您正在寻找的自动完成行为。

附上的例子是一个完整的工作表,只需要你的数据源和绑定的列名。

using System;
using System.Data;
using System.Windows.Forms;

public partial class frmTestAutocomplete : Form
{

    private DataTable maoCompleteList; //the data table from your data source
    private string msDisplayCol = "name"; //displayed text
    private string msIDcol = "id"; //ID or primary key

    public frmTestAutocomplete(DataTable aoCompleteList, string sDisplayCol, string sIDcol)
    {
        InitializeComponent();

        maoCompleteList = aoCompleteList
        maoCompleteList.CaseSensitive = false; //turn off case sensitivity for searching
        msDisplayCol = sDisplayCol;
        msIDcol = sIDcol;
    }

    private void frmTestAutocomplete_Load(object sender, EventArgs e)
    {

            testCombo.DisplayMember = msDisplayCol;
            testCombo.ValueMember = msIDcol; 
            testCombo.DataSource = maoCompleteList;
            testCombo.SelectedIndexChanged += testCombo_SelectedIndexChanged;
            testCombo.KeyUp += testCombo_KeyUp; 

    }


    private void testCombo_KeyUp(object sender, KeyEventArgs e)
    {
        //use keyUp event, as text changed traps too many other evengts.

        ComboBox oBox = (ComboBox)sender;
        string sBoxText = oBox.Text;

        DataRow[] oFilteredRows = maoCompleteList.Select(MC_DISPLAY_COL + " Like '%" + sBoxText + "%'");

        DataTable oFilteredDT = oFilteredRows.Length > 0
                                ? oFilteredRows.CopyToDataTable()
                                : maoCompleteList;

        //NOW THAT WE HAVE OUR FILTERED LIST, WE NEED TO RE-BIND IT WIHOUT CHANGING THE TEXT IN THE ComboBox.

        //1).UNREGISTER THE SELECTED EVENT BEFORE RE-BINDING, b/c IT TRIGGERS ON BIND.
        testCombo.SelectedIndexChanged -= testCombo_SelectedIndexChanged; //don't select on typing.
        oBox.DataSource = oFilteredDT; //2).rebind to filtered list.
        testCombo.SelectedIndexChanged += testCombo_SelectedIndexChanged;


        //3).show the user the new filtered list.
        oBox.DroppedDown = true; //do this before repainting the text, as it changes the dropdown text.

        //4).binding data source erases text, so now we need to put the user's text back,
        oBox.Text = sBoxText;
        oBox.SelectionStart = sBoxText.Length; //5). need to put the user's cursor back where it was.


    }

    private void testCombo_SelectedIndexChanged(object sender, EventArgs e)
    {

        ComboBox oBox = (ComboBox)sender;

        if (oBox.SelectedValue != null)
        {
            MessageBox.Show(string.Format(@"Item #{0} was selected.", oBox.SelectedValue));
        }
    }
}

//=====================================================================================================
//      code from frmTestAutocomplete.Designer.cs
//=====================================================================================================
partial class frmTestAutocomplete
{
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Windows Form Designer generated code

    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
        this.testCombo = new System.Windows.Forms.ComboBox();
        this.SuspendLayout();
        // 
        // testCombo
        // 
        this.testCombo.FormattingEnabled = true;
        this.testCombo.Location = new System.Drawing.Point(27, 51);
        this.testCombo.Name = "testCombo";
        this.testCombo.Size = new System.Drawing.Size(224, 21);
        this.testCombo.TabIndex = 0;
        // 
        // frmTestAutocomplete
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(292, 273);
        this.Controls.Add(this.testCombo);
        this.Name = "frmTestAutocomplete";
        this.Text = "frmTestAutocomplete";
        this.Load += new System.EventHandler(this.frmTestAutocomplete_Load);
        this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.ComboBox testCombo;
}

【讨论】:

    【解决方案2】:

    有两种方法在使用 SQL 的 autoComplete textBox 控件中成功:

    但您应该执行以下操作:

    a- 创建新项目

    b-将组件类添加到项目并删除co​​mponent1.designer“根据您给组件类的名称”

    c-下载"Download sample - 144.82 KB" 并打开它并从 AutoCompleteTextbox.cs
    打开 AutoCompleteTextbox 类 d- 选择所有如图所示并将其复制到当前组件类

    http://i.stack.imgur.com/oSqCa.png

    e- Final - 运行项目并停止以在 toolBox 中查看新的 AutoCompleteTextbox。

    现在您可以添加以下两种方法,您可以使用它们来使用 SQL

    1-在Form_Load中

    private void Form1_Load(object sender, EventArgs e)
      {            
       SqlConnection cn = new SqlConnection(@"server=.;database=My_dataBase;integrated security=true");
       SqlDataAdapter da = new SqlDataAdapter(@"SELECT [MyColumn] FROM [my_table]", cn);
       DataTable dt = new DataTable();
       da.Fill(dt);
    
       List<string> myList = new List<string>();
        foreach (DataRow row in dt.Rows)
           {
              myList.Add((string)row[0]);
           }
    
       autoCompleteTextbox1.AutoCompleteList = myList;
        }  
    

    2- 在 TextChanged 事件中

     private void autoCompleteTextbox_TextChanged(object sender, EventArgs e)
            {           
             SqlConnection cn = new SqlConnection(@"server=.;database=My_dataBase;integrated security=true");
             SqlDataAdapter da = new SqlDataAdapter(@"SELECT [MyColumn] FROM [my_table]", cn);
             DataTable dt = new DataTable();
             da.Fill(dt);
    
         List<string> myList = new List<string>();
          foreach (DataRow row in dt.Rows)
            {
              myList.Add((string)row[0]);
            }
    
       autoCompleteTextbox2.AutoCompleteList = myList;
    
        }
    

    【讨论】:

      【解决方案3】:

      这是一个继承ComboBox 控件类的实现,而不是用新控件替换整个组合框。当您在文本框中键入时,它会显示自己的下拉列表,但单击以显示下拉列表的处理方式与以前一样(即不使用此代码)。因此,您可以获得适当的本机控制和外观。

      如果您想改进它,请使用它,修改它并编辑答案!

      class ComboListMatcher : ComboBox, IMessageFilter
      {
          private Control ComboParentForm; // Or use type "Form" 
          private ListBox listBoxChild;
          private int IgnoreTextChange;
          private bool MsgFilterActive = false;
      
          public ComboListMatcher()
          {
              // Set up all the events we need to handle
              TextChanged += ComboListMatcher_TextChanged;
              SelectionChangeCommitted += ComboListMatcher_SelectionChangeCommitted;
              LostFocus += ComboListMatcher_LostFocus;
              MouseDown += ComboListMatcher_MouseDown;
              HandleDestroyed += ComboListMatcher_HandleDestroyed;
          }
      
          void ComboListMatcher_HandleDestroyed(object sender, EventArgs e)
          {
              if (MsgFilterActive)
                  Application.RemoveMessageFilter(this);
          }
      
          ~ComboListMatcher()
          {
          }
      
          private void ComboListMatcher_MouseDown(object sender, MouseEventArgs e)
          {
              HideTheList();
          }
      
          void ComboListMatcher_LostFocus(object sender, EventArgs e)
          {
              if (listBoxChild != null && !listBoxChild.Focused)
                  HideTheList();
          }
      
          void ComboListMatcher_SelectionChangeCommitted(object sender, EventArgs e)
          {
              IgnoreTextChange++;
          }
      
          void InitListControl()
          {
              if (listBoxChild == null)
              {
                  // Find parent - or keep going up until you find the parent form
                  ComboParentForm = this.Parent;
      
                  if (ComboParentForm != null)
                  {
                      // Setup a messaage filter so we can listen to the keyboard
                      if (!MsgFilterActive)
                      {
                          Application.AddMessageFilter(this);
                          MsgFilterActive = true;
                      }
      
                      listBoxChild = listBoxChild = new ListBox();
                      listBoxChild.Visible = false;
                      listBoxChild.Click += listBox1_Click;
                      ComboParentForm.Controls.Add(listBoxChild);
                      ComboParentForm.Controls.SetChildIndex(listBoxChild, 0); // Put it at the front
                  }
              }
          }
      
      
          void ComboListMatcher_TextChanged(object sender, EventArgs e)
          {
              if (IgnoreTextChange > 0)
              {
                  IgnoreTextChange = 0;
                  return;
              }
      
              InitListControl();
      
              if (listBoxChild == null)
                  return;
      
              string SearchText = this.Text;
      
              listBoxChild.Items.Clear();
      
              // Don't show the list when nothing has been typed
              if (!string.IsNullOrEmpty(SearchText))
              {
                  foreach (string Item in this.Items)
                  {
                      if (Item != null && Item.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase))
                          listBoxChild.Items.Add(Item);
                  }
              }
      
              if (listBoxChild.Items.Count > 0)
              {
                  Point PutItHere = new Point(this.Left, this.Bottom);
                  Control TheControlToMove = this;
      
                  PutItHere = this.Parent.PointToScreen(PutItHere);
      
                  TheControlToMove = listBoxChild;
                  PutItHere = ComboParentForm.PointToClient(PutItHere);
      
                  TheControlToMove.Show();
                  TheControlToMove.Left = PutItHere.X;
                  TheControlToMove.Top = PutItHere.Y;
                  TheControlToMove.Width = this.Width;
      
                  int TotalItemHeight = listBoxChild.ItemHeight * (listBoxChild.Items.Count + 1);
                  TheControlToMove.Height = Math.Min(ComboParentForm.ClientSize.Height - TheControlToMove.Top, TotalItemHeight);
              }
              else
                  HideTheList();
          }
      
          /// <summary>
          /// Copy the selection from the list-box into the combo box
          /// </summary>
          private void CopySelection()
          {
              if (listBoxChild.SelectedItem != null)
              {
                  this.SelectedItem = listBoxChild.SelectedItem;
                  HideTheList();
                  this.SelectAll();
              }
          }
      
          private void listBox1_Click(object sender, EventArgs e)
          {
              var ThisList = sender as ListBox;
      
              if (ThisList != null)
              {
                  // Copy selection to the combo box
                  CopySelection();
              }
          }
      
          private void HideTheList()
          {
              if (listBoxChild != null)
                  listBoxChild.Hide();
          }
      
          public bool PreFilterMessage(ref Message m)
          {
              if (m.Msg == 0x201) // Mouse click: WM_LBUTTONDOWN
              {
                  var Pos = new Point((int)(m.LParam.ToInt32() & 0xFFFF), (int)(m.LParam.ToInt32() >> 16));
      
                  var Ctrl = Control.FromHandle(m.HWnd);
                  if (Ctrl != null)
                  {
                      // Convert the point into our parent control's coordinates ...
                      Pos = ComboParentForm.PointToClient(Ctrl.PointToScreen(Pos));
      
                      // ... because we need to hide the list if user clicks on something other than the list-box
                      if (ComboParentForm != null)
                      {
                          if (listBoxChild != null &&
                              (Pos.X < listBoxChild.Left || Pos.X > listBoxChild.Right || Pos.Y < listBoxChild.Top || Pos.Y > listBoxChild.Bottom))
                          {
                              this.HideTheList();
                          }
                      }
                  }
              }
              else if (m.Msg == 0x100) // WM_KEYDOWN
              {
                  if (listBoxChild != null && listBoxChild.Visible)
                  {
                      switch (m.WParam.ToInt32())
                      {
                          case 0x1B: // Escape key
                              this.HideTheList();
                              return true;
      
                          case 0x26: // up key
                          case 0x28: // right key
                              // Change selection
                              int NewIx = listBoxChild.SelectedIndex + ((m.WParam.ToInt32() == 0x26) ? -1 : 1);
      
                              // Keep the index valid!
                              if (NewIx >= 0 && NewIx < listBoxChild.Items.Count)
                                  listBoxChild.SelectedIndex = NewIx;
                              return true;
      
                          case 0x0D: // return (use the currently selected item)
                              CopySelection();
                              return true;
                      }
                  }
              }
      
              return false;
          }
      }
      

      【讨论】:

      • 这并不“简单地”从 ComboBox 继承。它有几个问题:ComboParentForm = this.Parent(); 应该是 this.Parent;ContainsNoCase 看起来像自定义扩展方法,IntEx.Limit 看起来像自定义帮助类。
      • 感谢您指出这些 - 已相应更新。这更清楚吗? - 希望它有帮助/有帮助。
      • 谢谢!这是我见过的最好的实现。我在代码中发现了一些问题,并且我对其进行了一些简化。 yadi.sk/i/SCrni3ZlqzoKr
      • a) 你需要两个 using 语句。 using System.Windows.Forms;using System.Drawing; 一个用于例如ComboBox 和一个用于点 b) 这条线 if (Item != null &amp;&amp; Item.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase)) 不起作用,没有包含这样的两个参数的方法。有一个包含一个参数,一个字符串。您可以使用if (Item != null &amp;&amp; Item.ToLower().Contains(SearchText.ToLower())),它可能会按照您的意愿行事。
      • 我有一个相当无知的问题。如何添加到列表中?
      【解决方案4】:

      现有的自动完成功能仅支持按前缀搜索。似乎没有任何体面的方法来覆盖该行为。

      有些人通过覆盖OnTextChanged 事件实现了自己的自动完成功能。这可能是你最好的选择。

      例如,您可以在TextBox 下方添加ListBox,并将其默认可见性设置为false。然后您可以使用TextBoxOnTextChanged 事件和ListBoxSelectedIndexChanged 事件来显示和选择项目。

      作为一个基本示例,这似乎工作得很好:

      public Form1()
      {
          InitializeComponent();
      
      
          acsc = new AutoCompleteStringCollection();
          textBox1.AutoCompleteCustomSource = acsc;
          textBox1.AutoCompleteMode = AutoCompleteMode.None;
          textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
      }
      
      private void button1_Click(object sender, EventArgs e)
      {
          acsc.Add("[001] some kind of item");
          acsc.Add("[002] some other item");
          acsc.Add("[003] an orange");
          acsc.Add("[004] i like pickles");
      }
      
      void textBox1_TextChanged(object sender, System.EventArgs e)
      {
          listBox1.Items.Clear();
          if (textBox1.Text.Length == 0)
          {
          hideResults();
          return;
          }
      
          foreach (String s in textBox1.AutoCompleteCustomSource)
          {
          if (s.Contains(textBox1.Text))
          {
              Console.WriteLine("Found text in: " + s);
              listBox1.Items.Add(s);
              listBox1.Visible = true;
          }
          }
      }
      
      void listBox1_SelectedIndexChanged(object sender, System.EventArgs e)
      {
          textBox1.Text = listBox1.Items[listBox1.SelectedIndex].ToString();
          hideResults();
      }
      
      void listBox1_LostFocus(object sender, System.EventArgs e)
      {
          hideResults();
      }
      
      void hideResults()
      {
          listBox1.Visible = false;
      }
      

      您可以轻松完成更多工作:将文本附加到文本框、捕获其他键盘命令等等。

      【讨论】:

      • 请记住,这意味着下拉菜单不能悬在窗体底部边缘下方。
      • 丑。我正在查看这个问题的原因是我用组合替换了用户控件中的文本框/列表框解决方案,因为我遇到了无法悬垂表单底部的问题...
      【解决方案5】:

      如果您正在运行该查询({0} 被输入的字符串替换),您可能需要:

      SELECT Name from view_customers where Details LIKE '%{0}%'
      

      LIKE 仍然需要 % 字符...是的,您应该使用参数而不是信任用户的输入 :)

      此外,您似乎返回了Name 列,但查询的是Details 列。因此,如果有人输入“John Smith”,如果那不在 Details 列中,您将无法得到您想要的。

      【讨论】:

      • 我注意到您正在选择名称,但您正在查询详细信息 - 这不是问题吗?
      • 啊,不,在发布问题时让我知道了名称更改。我只是更改了名称以使其更易于理解。以为我是一致的,但我会解决它。
      【解决方案6】:

      如果您决定使用基于用户输入的查询,请确保使用 SqlParameters 以避免 SQL 注入攻击

      SqlCommand sqlCommand = new SqlCommand();
      sqlCommand.CommandText = "SELECT Name from view_customers where Details LIKE '%" + @SearchParam + "%'";
      sqlCommand.Parameters.AddWithValue("@SearchParam", searchParam);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-08-20
        • 2010-10-14
        • 2011-07-31
        • 2012-11-19
        • 1970-01-01
        相关资源
        最近更新 更多