Я пытаюсь добавить похожее на содержимое автозаполнение в поле со списком winforms.Я начал с идеи Ованеса Акопяна из этой темы .Мне пришлось немного его настроить, потому что автозаполнение не знало, где искать.Позвольте мне начать с описания моей установки:
У меня есть класс 'Part', и в выпадающем списке отображается его свойство 'Name' (DisplayMember).«Имя» также там, где автозаполнение должно искать элементы, содержащие данную строку:
public class Part
{
public int PartId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
В коде формы я создаю новый объект AutoCompleteBehavior, который будет обрабатывать все события для меня, и япрохождение поля со списком и списком объектов. Несмотря на то, что я ссылаюсь на класс 'Part' здесь, я пытаюсь построить общее решение, поэтому я использую дженерики, где это возможно :
new AutoCompleteBehavior<Part>(this.cmbPart, parts.Items);
cmbPart.DisplayMember = "Name";
cmbPart.ValueMember = "PartId";
Ниже приведен полный класс AutoCompleteBehavior:
public class AutoCompleteBehavior<T>
{
private readonly ComboBox comboBox;
private string previousSearchterm;
private T[] originalList;
public AutoCompleteBehavior(ComboBox comboBox, List<T>Items)
{
this.comboBox = comboBox;
this.comboBox.AutoCompleteMode = AutoCompleteMode.Suggest; // crucial otherwise exceptions occur when the user types in text which is not found in the autocompletion list
this.comboBox.TextChanged += this.OnTextChanged;
this.comboBox.KeyPress += this.OnKeyPress;
this.comboBox.SelectionChangeCommitted += this.OnSelectionChangeCommitted;
object[] items = Items.Cast<object>().ToArray();
this.comboBox.DataSource = null;
this.comboBox.Items.AddRange(items);
}
private void OnSelectionChangeCommitted(object sender, EventArgs e)
{
if (this.comboBox.SelectedItem == null)
{
return;
}
var sel = this.comboBox.SelectedItem;
this.ResetCompletionList();
comboBox.SelectedItem = sel;
}
private void OnTextChanged(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(this.comboBox.Text) || !this.comboBox.Visible || !this.comboBox.Enabled)
{
return;
}
this.ResetCompletionList();
}
private void OnKeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\r' || e.KeyChar == '\n')
{
e.Handled = true;
if (this.comboBox.SelectedIndex == -1 && this.comboBox.Items.Count > 0
&& this.comboBox.Items[0].ToString().ToLowerInvariant().StartsWith(this.comboBox.Text.ToLowerInvariant()))
{
this.comboBox.Text = this.comboBox.Items[0].ToString();
}
this.comboBox.DroppedDown = false;
// Guardclause when detecting any enter keypresses to avoid a glitch which was selecting an item by means of down arrow key followed by enter to wipe out the text within
return;
}
// Its crucial that we use begininvoke because we need the changes to sink into the textfield Omitting begininvoke would cause the searchterm to lag behind by one character That is the last character that got typed in
this.comboBox.BeginInvoke(new Action(this.ReevaluateCompletionList));
}
private void ResetCompletionList()
{
this.previousSearchterm = null;
try
{
this.comboBox.SuspendLayout();
if (this.originalList == null)
{
this.originalList = this.comboBox.Items.Cast<T>().ToArray();
}
if (this.comboBox.Items.Count == this.originalList.Length)
{
return;
}
while (this.comboBox.Items.Count > 0)
{
this.comboBox.Items.RemoveAt(0);
}
this.comboBox.Items.AddRange(this.originalList.Cast<object>().ToArray());
}
finally
{
this.comboBox.ResumeLayout(true);
}
}
private void ReevaluateCompletionList()
{
var currentSearchterm = this.comboBox.Text.ToLowerInvariant();
if (currentSearchterm == this.previousSearchterm)
{
return;
}
this.previousSearchterm = currentSearchterm;
try
{
this.comboBox.SuspendLayout();
if (this.originalList == null)
{
this.originalList = this.comboBox.Items.Cast<T>().ToArray(); // backup original list
}
T[] newList;
if (string.IsNullOrEmpty(currentSearchterm))
{
if (this.comboBox.Items.Count == this.originalList.Length)
{
return;
}
newList = this.originalList;
}
else
{
newList = this.originalList.Where($"{comboBox.DisplayMember}.Contains(@0)", currentSearchterm).ToArray();
//newList = this.originalList.Where(x => x.ToString().ToLowerInvariant().Contains(currentSearchterm)).ToArray();
}
try
{
// clear list by loop through it otherwise the cursor would move to the beginning of the textbox
while (this.comboBox.Items.Count > 0)
{
this.comboBox.Items.RemoveAt(0);
}
}
catch
{
try
{
this.comboBox.Items.Clear();
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
this.comboBox.Items.AddRange(newList.Cast<object>().ToArray()); // reset list
}
finally
{
if (currentSearchterm.Length >= 1 && !this.comboBox.DroppedDown)
{
this.comboBox.DroppedDown = true; // if the current searchterm is empty we leave the dropdown list to whatever state it already had
Cursor.Current = Cursors.Default; // workaround for the fact the cursor disappears due to droppeddown=true This is a known bu.g plaguing combobox which microsoft denies to fix for years now
this.comboBox.Text = currentSearchterm; // Another workaround for a glitch which causes all text to be selected when there is a matching entry which starts with the exact text being typed in
this.comboBox.Select(currentSearchterm.Length, 0);
}
this.comboBox.ResumeLayout(true);
}
}
}
Теперь автозаполнение работает KIND OF - оно ищет элементы, содержащие заданную строку, и делает это хорошо.Проблема, однако, в том, что по какой-то причине в выпадающем списке SelectedValue==null
и SelectedText=""
после выбора элемента в выпадающем списке.В то же время SelectedItem
содержит правильный объект 'Part' и SelectedIndex
также имеет правильное значение ...
К сожалению, когда я устанавливаю combobox.SelectedValue в какое-то значение, когда я заполняю формув поле со списком не выбран ни один элемент.Кроме того, когда я пытаюсь получить combobox.SelectedValue, он также говорит, что ноль (даже если элемент выбран).Я даже пытался вручную установить SelectedValue на основе SelectedItem, но я не могу установить его (он все еще нулевой):
private void OnSelectionChangeCommitted(object sender, EventArgs e)
{
if (this.comboBox.SelectedItem == null)
{
return;
}
var sel = this.comboBox.SelectedItem;
this.ResetCompletionList();
comboBox.SelectedItem = sel;
string valueName = comboBox.ValueMember;
comboBox.ValueMember = "";
comboBox.SelectedValue = typeof(T).GetProperty(valueName).GetValue(sel);
}
Я думаю, что, возможно, это потому, что я не использую свойство combobox.DataSource, которое яне могу установить / получить SelectedValue / SelectedText, но я могу ошибаться здесь.Любые идеи приветствуются!:)