2009-09-17 7 views
24

J'ai une zone de texte qui fait autocomplétion comme ceci:WinForms | C# | AutoComplete au milieu d'une zone de texte?

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

Il fonctionne, mais seulement au début d'une zone de texte. Je voudrais que l'autocomplétion apparaisse pour n'importe quel mot que l'utilisateur entre, à n'importe quelle position dans la zone de texte.

+1

alors vous aurez besoin d'écrire cette fonctionnalité –

+0

ah ok, donc rien cuit dans ... il suffit d'utiliser OnTextChanged et écrire mon propre ... merci. – Chaddeus

+0

Connaissez-vous de bons articles sur l'écriture d'une saisie semi-automatique personnalisée en C# pour WinForms? – Chaddeus

Répondre

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

    } 

} 

Exemple d'utilisation

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

Cela a l'avantage supplémentaire de fonctionner correctement avec les zones de texte multilignes. (Assurez-vous de régler 'AcceptsTab' sur * true *, cependant.) Incroyablement utile! – ladenedge

+0

Vous méritez vraiment +1, et je vous donnerais plus si je le pouvais. J'utilise votre AutoCompleteTextBox dans mon projet open source maintenant et c'est une grande amélioration pour mon expérience utilisateur. Je vous remercie! – teamalpha5441

+7

Le propriétaire de ce code de contrôle personnalisé est Peter Holpar, posté en 2010: http://pholpar.wordpress.com/2010/02/25/multivalue-autocomplete-winforms-textbox-for-tagging/ Le code source peut être téléchargé à: http://autocompletetexboxcs.codeplex.com/ La prochaine fois, reconnaissez la contribution de quelqu'un et travaillez si vous vous souciez de la programmation. N'oubliez pas de donner crédit même s'il s'agit d'un code ouvert gratuit, ce n'est pas du plagiat, mais c'est discourtoise et impoli. – WhySoSerious

7

J'ai fait quelques changements à la solution proposée par @PaRiMaL raj parce que la zone de liste n'a pas été affiché lorsque la zone de texte était à l'intérieur d'un UserControl qui n'était pas assez grand . Fondamentalement, au lieu d'ajouter la zone de liste au parent de la zone de texte, j'ai ajouté au formulaire et je calcule la position absolue dans le formulaire.

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

Les autres solutions ne fonctionnent pour moi dans un environnement multiligne à mes besoins, j'ai ajouté à la réponse de @Francisco Goldenstein pour permettre cela. Ce dont j'avais besoin était de compléter automatiquement un 'mot' dans le TextBox et dans n'importe quelle position/ligne. Après un minimum de tests, cette classe semble assez bien fonctionner pour moi dans un TextBox multiligne. J'espère que ça aide quelqu'un.

Les principaux changements sont en UpdateListBox() et this_KeyDown(), pour traiter le mot 'courant', c'est-à-dire celui juste avant la position du curseur, plutôt que le contenu entier de la zone de texte.

Modifiez la définition de separators en UpdateListBox() pour répondre à vos besoins.

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