2009-09-17 68 views
24

我有一個文本框,做自動完成,像這樣:WinForms | C#|在文本框中間自動完成?

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

它的工作原理,但只在一個文本框的開始。我希望自動完成功能能夠在用戶輸入的任何單詞的文本框中的任意位置輸入。

+1

然後你將需要編寫該功能 – 2009-09-17 06:22:24

+0

啊好的,所以沒有烤...只使用OnTextChanged並寫我自己的...謝謝。 – Chaddeus 2009-09-17 07:30:38

+0

瞭解有關在C#中爲WinForms編寫自定義自動完成的好文章? – Chaddeus 2009-09-17 22:36:04

回答

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

    } 

} 

用法示例

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

這具有與多行文本框很好地配合的額外好處。 (但是,確保將'AcceptsTab'設置爲* true *。)非常有用! – ladenedge 2013-03-11 03:57:42

+0

你真的值得+1,如果可以的話,我會給你更多。我現在在我的開源項目中使用您的AutoCompleteTextBox,這對我的用戶體驗有很大的改進。謝謝! – teamalpha5441 2013-04-18 19:21:46

+7

此自定義控制代碼的所有者是Peter Holpar,發佈於2010年:http://pholpar.wordpress.com/2010/02/25/multivalue-autocomplete-winforms-textbox-for-tagging/源代碼可以下載在:http://autocompletetexboxcs.codeplex.com/下次,如果你關心編程,承認某人的貢獻和工作。不要忘記給予信貸,即使它是免費的開放代碼,這不是抄襲,但它是無禮的和粗魯的。 – WhySoSerious 2014-01-21 03:24:09

7

我提出由@PaRiMaL拉吉提出的解決方案進行一些更改,因爲當文本框是一個用戶控件,這是不夠高的內部沒有被顯示在列表框中。基本上,我不是將列表框添加到文本框的父級,而是添加到表單中,並計算表單中的絕對位置。

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

其他的解決方案並沒有爲我在多環境中我需要工作,所以我已經添加到@Francisco Goldenstein的答案啓用它。我需要的是自動完成TextBox中任何位置/行的「單詞」。經過最少的測試後,這個類對於我在多行文本框中的工作似乎足夠好。希望它能幫助別人。

主要更改是在UpdateListBox()this_KeyDown()中處理「當前」字詞,即恰好在插入符號位置之前的字詞,而不是整個文本框內容。

separators的定義更改爲UpdateListBox()以滿足您的需求。

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