2009-09-17 22 views
24

que tienen un cuadro de texto que hace terminación automática de este modo:WinForms | C# | ¿Autocompletar en medio de un cuadro de texto?

txtName.AutoCompleteMode = AutoCompleteMode.Suggest; 
txtName.AutoCompleteSource = AutoCompleteSource.CustomSource; 
txtName.AutoCompleteCustomSource = namesCollection; 

Funciona, pero sólo al principio de un cuadro de texto. Me gustaría que la función autocompletar se active con cualquier palabra que ingrese el usuario, en cualquier posición en el cuadro de texto.

+1

entonces usted tendrá que escribir esa funcionalidad –

+0

ah ok, así que nada al horno en ... sólo tiene que utilizar OnTextChanged y escribir mi propia ... gracias. – Chaddeus

+0

¿Conoce algún buen artículo sobre cómo escribir una autocompleta personalizada en C# para WinForms? – Chaddeus

Respuesta

32
using System; 
using System.Collections.Generic; 
using System.Drawing; 
using System.Windows.Forms; 

namespace TubeUploader 
{ 
    public class AutoCompleteTextBox : TextBox 
    { 
     private ListBox _listBox; 
     private bool _isAdded; 
     private String[] _values; 
     private String _formerValue = String.Empty; 

     public AutoCompleteTextBox() 
     { 
      InitializeComponent(); 
      ResetListBox(); 
     } 

     private void InitializeComponent() 
     { 
      _listBox = new ListBox(); 
      KeyDown += this_KeyDown; 
      KeyUp += this_KeyUp; 
     } 

     private void ShowListBox() 
     { 
      if (!_isAdded) 
      { 
       Parent.Controls.Add(_listBox); 
       _listBox.Left = Left; 
       _listBox.Top = Top + Height; 
       _isAdded = true; 
      } 
      _listBox.Visible = true; 
      _listBox.BringToFront(); 
     } 

     private void ResetListBox() 
     { 
      _listBox.Visible = false; 
     } 

     private void this_KeyUp(object sender, KeyEventArgs e) 
     { 
      UpdateListBox(); 
     } 

     private void this_KeyDown(object sender, KeyEventArgs e) 
     { 
      switch (e.KeyCode) 
      { 
       case Keys.Tab: 
        { 
         if (_listBox.Visible) 
         { 
          InsertWord((String)_listBox.SelectedItem); 
          ResetListBox(); 
          _formerValue = Text; 
         } 
         break; 
        } 
       case Keys.Down: 
        { 
         if ((_listBox.Visible) && (_listBox.SelectedIndex < _listBox.Items.Count - 1)) 
          _listBox.SelectedIndex++; 

         break; 
        } 
       case Keys.Up: 
        { 
         if ((_listBox.Visible) && (_listBox.SelectedIndex > 0)) 
          _listBox.SelectedIndex--; 

         break; 
        } 
      } 
     } 

     protected override bool IsInputKey(Keys keyData) 
     { 
      switch (keyData) 
      { 
       case Keys.Tab: 
        return true; 
       default: 
        return base.IsInputKey(keyData); 
      } 
     } 

     private void UpdateListBox() 
     { 
      if (Text == _formerValue) return; 
      _formerValue = Text; 
      String word = GetWord(); 

      if (_values != null && word.Length > 0) 
      { 
       String[] matches = Array.FindAll(_values, 
               x => (x.StartsWith(word, StringComparison.OrdinalIgnoreCase) && !SelectedValues.Contains(x))); 
       if (matches.Length > 0) 
       { 
        ShowListBox(); 
        _listBox.Items.Clear(); 
        Array.ForEach(matches, x => _listBox.Items.Add(x)); 
        _listBox.SelectedIndex = 0; 
        _listBox.Height = 0; 
        _listBox.Width = 0; 
        Focus(); 
        using (Graphics graphics = _listBox.CreateGraphics()) 
        { 
         for (int i = 0; i < _listBox.Items.Count; i++) 
         { 
          _listBox.Height += _listBox.GetItemHeight(i); 
          // it item width is larger than the current one 
          // set it to the new max item width 
          // GetItemRectangle does not work for me 
          // we add a little extra space by using '_' 
          int itemWidth = (int)graphics.MeasureString(((String)_listBox.Items[i]) + "_", _listBox.Font).Width; 
          _listBox.Width = (_listBox.Width < itemWidth) ? itemWidth : _listBox.Width; 
         } 
        } 
       } 
       else 
       { 
        ResetListBox(); 
       } 
      } 
      else 
      { 
       ResetListBox(); 
      } 
     } 

     private String GetWord() 
     { 
      String text = Text; 
      int pos = SelectionStart; 

      int posStart = text.LastIndexOf(' ', (pos < 1) ? 0 : pos - 1); 
      posStart = (posStart == -1) ? 0 : posStart + 1; 
      int posEnd = text.IndexOf(' ', pos); 
      posEnd = (posEnd == -1) ? text.Length : posEnd; 

      int length = ((posEnd - posStart) < 0) ? 0 : posEnd - posStart; 

      return text.Substring(posStart, length); 
     } 

     private void InsertWord(String newTag) 
     { 
      String text = Text; 
      int pos = SelectionStart; 

      int posStart = text.LastIndexOf(' ', (pos < 1) ? 0 : pos - 1); 
      posStart = (posStart == -1) ? 0 : posStart + 1; 
      int posEnd = text.IndexOf(' ', pos); 

      String firstPart = text.Substring(0, posStart) + newTag; 
      String updatedText = firstPart + ((posEnd == -1) ? "" : text.Substring(posEnd, text.Length - posEnd)); 


      Text = updatedText; 
      SelectionStart = firstPart.Length; 
     } 

     public String[] Values 
     { 
      get 
      { 
       return _values; 
      } 
      set 
      { 
       _values = value; 
      } 
     } 

     public List<String> SelectedValues 
     { 
      get 
      { 
       String[] result = Text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); 
       return new List<String>(result); 
      } 
     } 

    } 

} 

Ejemplo de uso

using System; 
using System.Windows.Forms; 

namespace AutoComplete 
{ 
    public partial class TestForm : Form 
    { 
     private readonly String[] _values = { "one", "two", "three", "tree", "four", "fivee" }; 

     public TestForm() 
     { 
      InitializeComponent(); 
      // AutoComplete is our special textbox control on the form 
      AutoComplete.Values = _values; 
     } 

    } 
} 
+2

Esto tiene la ventaja adicional de funcionar bien con cuadros de texto de líneas múltiples. (Sin embargo, asegúrese de establecer 'AcceptsTab' en * true *) ¡Increíblemente útil! – ladenedge

+0

Realmente se merece +1, y le daría más si pudiera. Ahora estoy usando tu AutoCompleteTextBox en mi proyecto de código abierto y es una gran mejora para mi experiencia de usuario. ¡Gracias! – teamalpha5441

+7

El propietario de este código de control personalizado es Peter Holpar, publicado en 2010: http://pholpar.wordpress.com/2010/02/25/multivalue-autocomplete-winforms-textbox-for-tagging/ El código fuente puede descargarse en: http://autocompletetexboxcs.codeplex.com/ La próxima vez reconozca la contribución de alguien y trabaje si le interesa la programación. No olvide dar crédito, incluso si es un código abierto, no es plagio, pero es descortés y grosero. – WhySoSerious

7

Hice algunos cambios a la solución propuesta por @PaRiMaL RaJ debido a que el cuadro de lista no se está mostrando cuando el cuadro de texto estaba dentro de un control de usuario que no era lo suficientemente alto . Básicamente, en lugar de agregar el cuadro de lista al elemento primario del cuadro de texto, agregué al formulario y calculé la posición absoluta en el formulario.

public class AutoCompleteTextBox : TextBox 
    { 
     private ListBox _listBox; 
     private bool _isAdded; 
     private String[] _values; 
     private String _formerValue = String.Empty; 

     public AutoCompleteTextBox() 
     { 
      InitializeComponent(); 
      ResetListBox(); 
     } 

     private void InitializeComponent() 
     { 
      _listBox = new ListBox(); 
      this.KeyDown += this_KeyDown; 
      this.KeyUp += this_KeyUp; 
     } 

     private void ShowListBox() 
     { 
      if (!_isAdded) 
      { 
       Form parentForm = this.FindForm(); // new line added 
       parentForm.Controls.Add(_listBox); // adds it to the form 
       Point positionOnForm = parentForm.PointToClient(this.Parent.PointToScreen(this.Location)); // absolute position in the form 
       _listBox.Left = positionOnForm.X; 
       _listBox.Top = positionOnForm.Y + Height; 
       _isAdded = true; 
      } 
      _listBox.Visible = true; 
      _listBox.BringToFront(); 
     } 



     private void ResetListBox() 
     { 
      _listBox.Visible = false; 
     } 

     private void this_KeyUp(object sender, KeyEventArgs e) 
     { 
      UpdateListBox(); 
     } 

     private void this_KeyDown(object sender, KeyEventArgs e) 
     { 
      switch (e.KeyCode) 
      { 
       case Keys.Enter: 
       case Keys.Tab: 
        { 
         if (_listBox.Visible) 
         { 
          Text = _listBox.SelectedItem.ToString(); 
          ResetListBox(); 
          _formerValue = Text; 
          this.Select(this.Text.Length, 0); 
          e.Handled = true; 
         } 
         break; 
        } 
       case Keys.Down: 
        { 
         if ((_listBox.Visible) && (_listBox.SelectedIndex < _listBox.Items.Count - 1)) 
          _listBox.SelectedIndex++; 
         e.Handled = true; 
         break; 
        } 
       case Keys.Up: 
        { 
         if ((_listBox.Visible) && (_listBox.SelectedIndex > 0)) 
          _listBox.SelectedIndex--; 
         e.Handled = true; 
         break; 
        } 


      } 
     } 

     protected override bool IsInputKey(Keys keyData) 
     { 
      switch (keyData) 
      { 
       case Keys.Tab: 
        if (_listBox.Visible) 
         return true; 
        else 
         return false; 
       default: 
        return base.IsInputKey(keyData); 
      } 
     } 

     private void UpdateListBox() 
     { 
      if (Text == _formerValue) 
       return; 

      _formerValue = this.Text; 
      string word = this.Text; 

      if (_values != null && word.Length > 0) 
      { 
       string[] matches = Array.FindAll(_values, 
               x => (x.ToLower().Contains(word.ToLower()))); 
       if (matches.Length > 0) 
       { 
        ShowListBox(); 
        _listBox.BeginUpdate(); 
        _listBox.Items.Clear(); 
        Array.ForEach(matches, x => _listBox.Items.Add(x)); 
        _listBox.SelectedIndex = 0; 
        _listBox.Height = 0; 
        _listBox.Width = 0; 
        Focus(); 
        using (Graphics graphics = _listBox.CreateGraphics()) 
        { 
         for (int i = 0; i < _listBox.Items.Count; i++) 
         { 
          if (i < 20) 
           _listBox.Height += _listBox.GetItemHeight(i); 
          // it item width is larger than the current one 
          // set it to the new max item width 
          // GetItemRectangle does not work for me 
          // we add a little extra space by using '_' 
          int itemWidth = (int)graphics.MeasureString(((string)_listBox.Items[i]) + "_", _listBox.Font).Width; 
          _listBox.Width = (_listBox.Width < itemWidth) ? itemWidth : this.Width; ; 
         } 
        } 
        _listBox.EndUpdate(); 
       } 
       else 
       { 
        ResetListBox(); 
       } 
      } 
      else 
      { 
       ResetListBox(); 
      } 
     } 

     public String[] Values 
     { 
      get 
      { 
       return _values; 
      } 
      set 
      { 
       _values = value; 
      } 
     } 

     public List<String> SelectedValues 
     { 
      get 
      { 
       String[] result = Text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); 
       return new List<String>(result); 
      } 
     } 

    } 
0

Las otras soluciones no funcionaron para mí en un entorno de varias líneas a mis necesidades, por lo que hemos añadido a la respuesta de @Francisco Goldenstein para permitir esto. Lo que necesitaba era autocompletar cualquier 'palabra' en el TextBox y en cualquier posición/línea. Después de pruebas mínimas, esta clase parece funcionar bastante bien para mí en un TextBox de varias líneas. Espero que ayude a alguien.

Los cambios principales se encuentran en UpdateListBox() y this_KeyDown(), para tratar la palabra 'actual', es decir, la que está justo antes de la posición de intercalación, en lugar de todo el contenido de la caja de texto.

Cambia la definición de separators en UpdateListBox() según tus necesidades.

using System; 
using System.Drawing; 
using System.Windows.Forms; 

class MultiLineAutoCompleteTextBox : TextBox 
{ 
    private ListBox _listBox; 
    private bool _isAdded; 
    private String[] _values; 
    private String _formerValue = String.Empty; 
    private int _prevBreak; 
    private int _nextBreak; 
    private int _wordLen; 

    public MultiLineAutoCompleteTextBox() 
    { 
     InitializeComponent(); 
     ResetListBox(); 
    } 

    private void InitializeComponent() 
    { 
     _listBox = new ListBox(); 
     KeyDown += this_KeyDown; 
     KeyUp += this_KeyUp; 
    } 

    private void ShowListBox() 
    { 
     if (!_isAdded) 
     { 
      Form parentForm = FindForm(); 
      if (parentForm == null) return; 

      parentForm.Controls.Add(_listBox); 
      Point positionOnForm = parentForm.PointToClient(Parent.PointToScreen(Location)); 
      _listBox.Left = positionOnForm.X; 
      _listBox.Top = positionOnForm.Y + Height; 
      _isAdded = true; 
     } 
     _listBox.Visible = true; 
     _listBox.BringToFront(); 
    } 

    private void ResetListBox() 
    { 
     _listBox.Visible = false; 
    } 

    private void this_KeyUp(object sender, KeyEventArgs e) 
    { 
     UpdateListBox(); 
    } 

    private void this_KeyDown(object sender, KeyEventArgs e) 
    { 
     switch (e.KeyCode) 
     { 
      case Keys.Enter: 
      case Keys.Tab: 
      case Keys.Space: 
      { 
       if (_listBox.Visible) 
       { 
        Text = Text.Remove(_prevBreak == 0 ? 0 : _prevBreak + 1, _prevBreak == 0 ? _wordLen + 1 : _wordLen); 
        Text = Text.Insert(_prevBreak == 0 ? 0 : _prevBreak + 1, _listBox.SelectedItem.ToString()); 
        ResetListBox(); 
        _formerValue = Text; 
        Select(Text.Length, 0); 
        e.Handled = true; 
       } 
       break; 
      } 
      case Keys.Down: 
      { 
       if ((_listBox.Visible) && (_listBox.SelectedIndex < _listBox.Items.Count - 1)) 
        _listBox.SelectedIndex++; 
       e.Handled = true; 
       break; 
      } 
      case Keys.Up: 
      { 
       if ((_listBox.Visible) && (_listBox.SelectedIndex > 0)) 
        _listBox.SelectedIndex--; 
       e.Handled = true; 
       break; 
      } 


     } 
    } 

    protected override bool IsInputKey(Keys keyData) 
    { 
     switch (keyData) 
     { 
      case Keys.Tab: 
       if (_listBox.Visible) 
        return true; 
       else 
        return false; 
      default: 
       return base.IsInputKey(keyData); 
     } 
    } 

    private void UpdateListBox() 
    { 
     if (Text == _formerValue) return; 
     if (Text.Length == 0) 
     { 
      _listBox.Visible = false; 
      return; 
     } 

     _formerValue = Text; 
     var separators = new[] { '|', '[', ']', '\r', '\n', ' ', '\t' }; 
     _prevBreak = Text.LastIndexOfAny(separators, CaretIndex > 0 ? CaretIndex - 1 : 0); 
     if (_prevBreak < 1) _prevBreak = 0; 
     _nextBreak = Text.IndexOfAny(separators, _prevBreak + 1); 
     if (_nextBreak == -1) _nextBreak = CaretIndex; 
     _wordLen = _nextBreak - _prevBreak - 1; 
     if (_wordLen < 1) return; 

     string word = Text.Substring(_prevBreak + 1, _wordLen); 

     if (_values != null && word.Length > 0) 
     { 
      string[] matches = Array.FindAll(_values, 
       x => (x.ToLower().Contains(word.ToLower()))); 
      if (matches.Length > 0) 
      { 
       ShowListBox(); 
       _listBox.BeginUpdate(); 
       _listBox.Items.Clear(); 
       Array.ForEach(matches, x => _listBox.Items.Add(x)); 
       _listBox.SelectedIndex = 0; 
       _listBox.Height = 0; 
       _listBox.Width = 0; 
       Focus(); 
       using (Graphics graphics = _listBox.CreateGraphics()) 
       { 
        for (int i = 0; i < _listBox.Items.Count; i++) 
        { 
         if (i < 20) 
          _listBox.Height += _listBox.GetItemHeight(i); 
         // it item width is larger than the current one 
         // set it to the new max item width 
         // GetItemRectangle does not work for me 
         // we add a little extra space by using '_' 
         int itemWidth = (int)graphics.MeasureString(((string)_listBox.Items[i]) + "_", _listBox.Font).Width; 
         _listBox.Width = (_listBox.Width < itemWidth) ? itemWidth : Width; ; 
        } 
       } 
       _listBox.EndUpdate(); 
      } 
      else 
      { 
       ResetListBox(); 
      } 
     } 
     else 
     { 
      ResetListBox(); 
     } 
    } 

    public int CaretIndex => SelectionStart; 

    public String[] Values 
    { 
     get 
     { 
      return _values; 
     } 
     set 
     { 
      _values = value; 
     } 
    } 
} 
Cuestiones relacionadas