WinForms | C#| 自动完成在文本框的中间?

我有一个文本框,像这样自动完成:

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

它的工作原理,但只在一个文本框的开始。 我希望自动完成function能够在文本框中的任何位置启动用户input的任何单词。

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

我对@PaRiMaL RaJ提出的解决scheme做了一些更改,因为当文本框在UserControl内部的高度不够时,列表框不显示。 基本上,而不是将该列表框添加到文本框的父项,我添加到窗体,并计算窗体中的绝对位置。

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