【问题标题】:Can't Get/set combobox's SelectedValue if its data source is null如果组合框的数据源为空,则无法获取/设置组合框的 SelectedValue
【发布时间】:2019-10-10 08:21:53
【问题描述】:

我正在尝试向 winforms 组合框添加类似包含的自动完成功能。我从 this thread 开始考虑 Hovhannes Hakobyan 的想法。我不得不对其进行一些调整,因为自动完成不知道在哪里搜索。 让我从描述我的设置开始:

我有一个“零件”类,组合框用于显示其“名称”属性 (DisplayMember)。 'Name' 也是自动完成搜索包含给定字符串的项目的地方:

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,但无法设置(仍然为空):

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

我认为可能是因为我没有使用无法设置/获取 SelectedValue/SelectedText 的 combobox.DataSource 属性,但我在这里可能错了。欢迎任何想法! :)

【问题讨论】:

    标签: c# winforms combobox


    【解决方案1】:

    将组合框样式设置为ComboBoxStyle.DropDownList 始终返回""(空字符串)为SelectedText(reference source)

    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
    }
    

    【讨论】:

    • 谢谢。我认为 DropDownStyle 在我的情况下是 DropDown (但文本仍然是“”)。据我了解, dataManager 为空,因为 DataSource 为空,对吧?我可以以某种方式实现它以便能够使用 SelectedValue 吗?我无法使用 DataSource,因为无法从代码修改项目集合..
    【解决方案2】:

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

    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&lt;Part&gt;(this.PartId); 设置要选择的项目,然后我用cmbPart.GetSelectedValue&lt;Part&gt;(); 获得选择项目的SelectedValue。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-11-16
      • 2023-03-11
      • 2015-09-01
      • 1970-01-01
      • 2017-08-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多