如果组合框的数据源为空,则无法获取/设置组合框的SelectedValue

时间:2019-05-23 17:58:27

标签: c# winforms combobox

我正在尝试向winforms组合框添加一个包含类的自动完成功能。我从this thread开始,介绍了Hovhannes Hakobyan的想法。我不得不调整一下它,因为自动完成功能不知道在哪里搜索。 让我开始描述我的设置:

我有一个'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);
            }
        }
    }

现在,自动完成功能可以正常工作-它会查找包含给定字符串的项目,并且效果很好。但问题是,由于某种原因,在组合框中选择了一个项目之后,组合框的SelectedValue==nullSelectedText=""仍然存在。同时SelectedItem包含适当的“ Part”对象,SelectedIndex也具有适当的值...

不幸的是,当我在填写表单时将combobox.SelectedValue设置为某个值时,组合框中没有任何项目被选中。另外,当我尝试获取combobox.SelectedValue时,它也显示为null(即使选择了一个项目)。我什至尝试根据SelectedItem手动设置SelectedValue,但无法设置(它仍然为null):

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 :(得分:0)

将组合框样式设置为ComboBoxStyle.DropDownList总是以"" (reference source)的形式返回SelectedText(空字符串)

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

SelectedValue是从ListControl继承的成员,并且需要对数据进行管理(reference source)

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
}

答案 1 :(得分:0)

我设法使用扩展方法和反射使其工作。尽管我仍然希望找到更好的解决方案,但是它运行良好。我已经创建了扩展类:

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);选择的项目,而我要使用cmbPart.GetSelectedValue<Part>();选择项目的SelectedValue。

我当然愿意接受其他解决方案!