2009-09-17 11 views
24

Mam pole tekstowe, które wykonuje autouzupełnianie tak:WinForms | C# | Autouzupełnianie w środku pola tekstowego?

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

to działa, ale tylko na początku pola tekstowego. Chciałbym autouzupełniania kopać dla każdego słowa, które użytkownik wprowadza, w dowolnym miejscu w polu tekstowym.

+1

potem trzeba będzie napisać, że funkcjonalność –

+0

ah ok, więc nic pieczone w ... wystarczy użyć OnTextChanged i napisać własny ... dzięki. – Chaddeus

+0

Czy znasz jakieś dobre artykuły na temat pisania niestandardowego autouzupełniania w języku C# dla WinForms? – Chaddeus

Odpowiedz

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

    } 

} 

Wykorzystanie próbki

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

Ma to dodatkową zaletę, że działa dobrze z wielopasmowymi ramkami tekstowymi. (Pamiętaj jednak, aby ustawić "AcceptsTab" na * true *). Niewiarygodnie przydatne! – ladenedge

+0

Naprawdę zasługujesz na +1, a ja dałbym ci więcej, gdybym mógł. Korzystam z twojego AutoCompleteTextBox w moim projekcie open source i to jest wielki ulepszenie dla mojego doświadczenia użytkownika. Dziękuję Ci! – teamalpha5441

+7

Właścicielem tego niestandardowego kodu sterującego jest Peter Holpar, opublikowany w 2010 roku: http://pholp.wordpress.com/2010/02/25/multivalue-autocomplete-winforms-textbox-for-tagging/ Kod źródłowy można pobrać na stronie: http://autocompletetexboxcs.codeplex.com/ Następnym razem potwierdź czyjś wkład i pracuj jeśli zależy Ci na programowaniu. Nie zapomnij o przyznaniu kredytu, nawet jeśli jest to darmowy otwarty kod, nie jest to plagiat, ale jest dyskretny i niegrzeczny. – WhySoSerious

7

Zrobiłem kilka zmian do rozwiązania zaproponowanego przez @PaRiMaL Raj ponieważ pole listy nie był wyświetlany, kiedy pole tekstowe był wewnątrz UserControl, który nie był na tyle wysoki . Zasadniczo, zamiast dodawać pole listy do nadrzędnego pola tekstowego, dodałem do formularza i obliczyłem absolutną pozycję w formularzu.

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

Inne rozwiązania nie działają na mnie w multilinii środowiska do moich potrzeb, więc dodałem do odpowiedzi @Francisco Goldenstein za umożliwienie tego. To, czego potrzebowałem, to autouzupełnianie dowolnego "słowa" w TextBox oraz w dowolnej pozycji/linii. Po minimalnym testowaniu ta klasa wydaje się działać wystarczająco dobrze dla mnie w wielowierszowym TextBox. Mam nadzieję, że to komuś pomaga.

Główne zmiany są w UpdateListBox() i this_KeyDown(), aby poradzić sobie z "bieżącym" słowem, tj. Tym znajdującym się tuż przed pozycją kurtuzy, a nie całą zawartością pola tekstowego.

Zmień definicję separators w UpdateListBox() w zależności od potrzeb.

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; 
     } 
    } 
} 
Powiązane problemy