2009-04-28 20 views
42

Estoy tratando de agregar una función de autocompletar a un cuadro de texto, los resultados provienen de una base de datos. Vienen en el formato deC# Autocompletar

[001] Por último, Primer Segundo

Actualmente debe escribir [001] ... para conseguir las entradas para mostrar. Así que el problema es que quiero que se complete incluso si escribo el primer nombre primero. Así que si una entrada era

[001] Smith, John D

si empezaba a escribir John entonces esta entrada debería aparecer en los resultados para el auto completo.

Actualmente el código se ve algo así como

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

resultados es un conjunto de datos que contiene la consulta resulta

La consulta es una consulta de búsqueda sencilla mediante la instrucción similares. Se devuelven los resultados correctos si no utilizamos la función autocompletar y simplemente lanzamos los resultados a una matriz.

¿Algún consejo?

EDIT:

Ésta es la consulta que devuelve los resultados

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

Con {0} siendo el marcador de posición para la cadena buscada.

+0

Se ve bien de un vistazo. Sería útil ver más código, incluida la consulta utilizada y la inicialización de 'id' (¿tienes dos cajas de autocompletar?). –

+0

Ok agregó una edición con la consulta. Solo estoy usando 1 caja de autocompletar.En realidad, la ID no se usa en los resultados que se agregó para su uso posterior y, por lo tanto, la eliminé en esta edición. Perdón por la confusion. – corymathews

+0

Hay un buen C# [control de autocompletar] gratuito (http://code.google.com/p/email-autocomplete) disponible ([con código fuente] (http://email-autocomplete.googlecode.com/svn/ tronco)) que es fácil de modificar. – Jimmy

Respuesta

41

La funcionalidad Autocompletar existente sólo admite búsquedas por prefijo. No parece haber ninguna forma decente para anular el comportamiento.

Algunas personas han implementado sus propias funciones de autocompletar anulando el evento OnTextChanged. Esa es probablemente tu mejor opción.

Por ejemplo, puede agregar un ListBox justo debajo del TextBox y establecer su visibilidad predeterminada en falso. Luego puede usar el evento OnTextChanged del TextBox y el evento SelectedIndexChanged del ListBox para mostrar y seleccionar elementos.

Esto parece funcionar bastante bien como un ejemplo rudimentario:

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

Hay mucho más que podría hacer sin demasiado esfuerzo: añadir texto al cuadro de texto, capturar comandos adicionales del teclado, y así sucesivamente.

+3

Recuerde que esto significa que el menú desplegable no puede sobresalir del formulario debajo del borde inferior de la ventana. –

+0

+1 waw gracias por esto voy a intentarlo :) – Zavael

+0

Ug. El motivo por el que veo esta pregunta es que reemplacé una solución de cuadro de texto/listbox en un control de usuario, con un combo, ya que tuve problemas para no sobrescribir el final de los formularios ... – Ted

0

Si se está utilizando esa consulta (con {0} se sustituye por la cadena introducida), es posible que necesite:

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

LIKE todavía necesita el carácter % ... Y sí, se debe utilizar parámetros en lugar de confiar en la entrada del usuario :)

Además, parece devolver la columna Name, pero consultar en la columna Details. Entonces, si alguien escribe "John Smith", si no está en la columna Details, no obtendrá lo que desea recuperar.

+0

bien, entonces ya se está haciendo ... hmm ... interesante ... :) – Damovisa

+0

Me doy cuenta de que está seleccionando Nombre, pero está consultando los Detalles, ese no es el problema, ¿verdad? – Damovisa

+0

ah nope me consiguió los cambios de nombre al publicar la pregunta. Acabo de cambiar los nombres para que sea más fácil de entender. Pensé que era constante, pero lo arreglaré. – corymathews

5

Si decide utilizar una consulta que se basa en la entrada del usuario Asegúrese de que utiliza SqlParameters para evitar ataques de inyección SQL

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

Aquí hay una implementación que hereda la clase de control ComboBox, en lugar de reemplazar todo el cuadro combinado con un nuevo control. Muestra su propio menú desplegable cuando escribe en el cuadro de texto, pero al hacer clic para mostrar la lista desplegable se maneja como antes (es decir, no con este código). Como tal, obtienes ese control y apariencia nativos adecuados.

¡Úselo, modifíquelo y edite la respuesta si desea mejorarlo!

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

Esto no ' simplemente 'heredar de ComboBox. Tiene algunos problemas: '' 'ComboParentForm = this.Parent();' '' probablemente sea '' 'this.Parent;' '', '' 'ContainsNoCase''' se parece a un método de extensión personalizado,' ' 'IntEx.Limit''' parece una clase de ayuda personalizada. –

+1

Gracias por señalarlos - se han actualizado en consecuencia. ¿Está eso más claro? - Espero que ayude/ayude. – noelicus

+0

Gracias! Esa es la mejor implementación que he visto. He encontrado algunos problemas en el código y también lo he simplificado un poco. https://yadi.sk/i/SCrni3ZlqzoKr –

-1

dos métodos tuvieron éxito en el control TextBox AutoComplete con SQL:

pero se debe hacer lo siguiente:

a- Crear nuevo proyecto

b- agregar la clase de componentes para proyectar y eliminar component1.designer "según el nombre que le dé a la clase de componente"

descarga c- "Download sample - 144.82 KB" y abrirlo y clase abierta de AutoCompleteTextbox AutoCompleteTextbox.cs
d- seleccionar todo como se ilustra en la imagen y copiarla en clase de componente actual

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

electrónico Final - Proyecto de ejecución y detenerse para ver el nuevo AutoCompleteTextbox en toolbox.

Ahora puede añadir los dos siguiente método que se puede utilizar SQL con ellos

1- Form_Load en

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- en TextChanged Evento

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; 

    } 
0

ESTO LE DARÁ EL COMPORTAMIENTO AUTOCOMPLETO QUE ESTÁ BUSCANDO.

El ejemplo adjunto es un formulario de trabajo completo, solo necesita su fuente de datos y nombres de columnas enlazados.

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

public partial class frmTestAutocomplete : Form 
{ 

    private DataTable maoCompleteList; 
    private const string MC_DISPLAY_COL = "name"; 
    private const string MC_ID_COL = "id"; 

    public frmTestAutocomplete() 
    { 
     InitializeComponent(); 
    } 

    private void frmTestAutocomplete_Load(object sender, EventArgs e) 
    { 
     using (clsDataAccess oData = new clsDataAccess()) 
     { 

      maoCompleteList = oData.PurificationRuns; 
      maoCompleteList.CaseSensitive = false; //turn off case sensitivity for searching 

      testCombo.DisplayMember = MC_DISPLAY_COL; 
      testCombo.ValueMember = MC_ID_COL; 
      testCombo.DataSource = GetDataTableFromDatabase(); 
      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; 
}