Невозможно получить / установить SelectedValue комбобокса, если его источник данных является нулевым - PullRequest
0 голосов
/ 23 мая 2019

Я пытаюсь добавить похожее на содержимое автозаполнение в поле со списком 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, но я могу ошибаться здесь.Любые идеи приветствуются!:)

Ответы [ 2 ]

0 голосов
/ 24 мая 2019

Мне удалось заставить его работать с использованием методов расширения и отражения.Это работает хорошо, хотя я все еще надеюсь найти лучшее решение.Я создал класс расширения:

using System.Linq.Dynamic;

namespace JDE_Scanner_Desktop.Static
{
    static class Extensions
    {
        public static int GetSelectedValue<T>(this ComboBox combobox)
        {
            return (int)typeof(T).GetProperty(combobox.ValueMember).GetValue(combobox.SelectedItem);
        }

        public static void SetSelectedValue<T>(this ComboBox combobox, int? selectedValue)
        {
            if(selectedValue != null)
            {
                combobox.SelectedItem = combobox.Items.Cast<T>().Where(combobox.ValueMember + $"={selectedValue}").FirstOrDefault();
            }
        }
    }
}

Затем я устанавливаю элемент для выбора с помощью cmbPart.SetSelectedValue<Part>(this.PartId); и получаю SelectedValue выбранного элемента с помощью cmbPart.GetSelectedValue<Part>();.

Конечно.Я открыт для других решений!

0 голосов
/ 24 мая 2019

Установка стиля комбинированного списка на ComboBoxStyle.DropDownList всегда возвращает "" (пустая строка) как SelectedText (источник ссылки)

public string SelectedText 
{
    get 
    {
        if (DropDownStyle == ComboBoxStyle.DropDownList) 
            return "";
        return Text.Substring(SelectionStart, SelectionLength);
    }
    {
        // see link
    }
}

SelectedValue - член, унаследованный от ListControl и требующий обработки данных (источник ссылки) .

public object SelectedValue {
get 
{
    if (SelectedIndex != -1 && dataManager != null ) 
    {
        object currentItem = dataManager[SelectedIndex];
        object filteredItem = FilterItemOnProperty(currentItem, valueMember.BindingField);
        return filteredItem;
    }
    return null;
}
set 
{
    // see link
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...