将2个列表绑定到组合框而不创建第3个列表,包含其他2个

时间:2017-06-29 09:08:45

标签: c# .net datasource controls

我希望有2个列表并希望它们成为我的组合框的数据源, 有没有办法做到这一点,没有第三个列表组合每个变化的其他2个列表,如:

    List<string> list1 = new List<string>(),
        list2 = new List<string>(),
        list3 = new List<string>();

    private void Init()
    {
        comboBox1.DataSource = list3;
    }

    private void ListsChanged()
    {
        list3.Clear();
        list3.AddRange(list1);
        list3.AddRange(list2);
    }

1 个答案:

答案 0 :(得分:0)

这是您可能想要考虑切换到WPF的一个很好的例子。 WPF API以CompositeCollection类的形式内置了此功能。

也就是说,可以在Winforms中实现类似的效果。你&#34;只是&#34;必须编写自己的绑定源实现,可以聚合现有的源。我把&#34;只是&#34;在引号中,因为以完全功能的方式正确实现绑定源是非常重要的。

也就是说,这是一个适用于简单案例的例子(即那些不涉及对源数据进行排序的案例):

class CompositeBindingList<T> : IList<T>, IBindingList, ICancelAddNew
{
    private const string _kstrIndexOutOfRange = "index must be non-negative and less than Count";
    private readonly List<BindingList<T>> _sources = new List<BindingList<T>>();

    #region CompositeBindingList<T>-specific members

    public void AddBindingList(BindingList<T> list)
    {
        list.AddingNew += _OnAddingNew;
        list.ListChanged += _OnListChanged;
        _sources.Add(list);
        ListChanged?.Invoke(this, new ListChangedEventArgs(ListChangedType.Reset, 0));
    }

    public void RemoveBindingList(int index)
    {
        _sources.RemoveAt(index);
        ListChanged?.Invoke(this, new ListChangedEventArgs(ListChangedType.Reset, 0));
    }

    public int BindingListCount
    {
        get { return _sources.Count; }
    }

    #endregion

    #region BindingList-mirroring members

    public event AddingNewEventHandler AddingNew;

    public T AddNew()
    {
        if (_sources.Count == 0)
        {
            _sources.Add(new BindingList<T>());
        }

        return _sources[_sources.Count - 1].AddNew();
    }

    #endregion 

    #region IList<T> members

    public T this[int index]
    {
        get { return _sources[_GetSourceIndexOrThrow(ref index)][index]; }
        set { _sources[_GetSourceIndexOrThrow(ref index)][index] = value; }
    }

    public int Count => _sources.Sum(s => s.Count);

    public bool IsReadOnly => _sources.Cast<ICollection<T>>().All(s => s.IsReadOnly);

    public void Add(T item)
    {
        if (_sources.Count == 0)
        {
            _sources.Add(new BindingList<T>());
        }

        _sources[_sources.Count - 1].Add(item);
    }

    public void Clear() => _sources.Clear();

    public bool Contains(T item) => _sources.Any(s => s.Contains(item));

    public void CopyTo(T[] array, int arrayIndex)
    {
        foreach (BindingList<T> source in _sources)
        {
            source.CopyTo(array, arrayIndex);
            arrayIndex += source.Count;
        }
    }

    public IEnumerator<T> GetEnumerator() => _sources.SelectMany(s => s).GetEnumerator();

    public int IndexOf(T item)
    {
        var (source, index, baseIndex) = _GetIndexOf(item);

        return index >= 0 ? baseIndex + index : -1;
    }

    private (BindingList<T> source, int index, int baseIndex) _GetIndexOf(T item)
    {
        int baseIndex = 0;

        foreach (BindingList<T> source in _sources)
        {
            int index = source.IndexOf(item);

            if (index >= 0)
            {
                return (source, index, baseIndex);
            }

            baseIndex += source.Count;
        }

        return (null, -1, -1);
    }

    public void Insert(int index, T item)
    {
        int sourceIndex = _GetSourceIndex(ref index);

        if (sourceIndex == -1)
        {
            if (index != 0)
            {
                throw new IndexOutOfRangeException(_kstrIndexOutOfRange);
            }

            sourceIndex = _sources.Count - 1;
            index = _sources[sourceIndex].Count;
        }

        _sources[sourceIndex].Insert(index, item);
    }

    public bool Remove(T item)
    {
        var (source, index, baseIndex) = _GetIndexOf(item);

        if (index < 0)
        {
            return false;
        }
        else
        {
            source.RemoveAt(baseIndex + index);
            return true;
        }
    }

    public void RemoveAt(int index) => _sources[_GetSourceIndex(ref index)].RemoveAt(index);

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    #endregion

    #region ICollection members

    object ICollection.SyncRoot => throw new NotSupportedException();

    bool ICollection.IsSynchronized => false;

    void ICollection.CopyTo(Array array, int index)
    {
        CopyTo((T[])array, index);
    }

    #endregion 

    #region IList members

    bool IList.IsFixedSize => _sources.Cast<IList>().All(s => s.IsFixedSize);

    object IList.this[int index]
    {
        get => this[index];
        set => this[index] = (T)value;
    }

    int IList.Add(object value)
    {
        if (value is T)
        {
            Add((T)value);
            return Count - 1;
        }
        else
        {
            return -1;
        }
    }

    bool IList.Contains(object value)
    {
        return Contains((T)value);
    }

    int IList.IndexOf(object value)
    {
        return value is T ? IndexOf((T)value) : -1;
    }

    void IList.Insert(int index, object value)
    {
        Insert(index, (T)value);
    }

    void IList.Remove(object value)
    {
        if (value is T)
        {
            Remove((T)value);
        }
    }

    #endregion

    #region IBindingList members

    public event ListChangedEventHandler ListChanged;

    bool IBindingList.AllowNew => _sources.All(s => s.AllowNew);

    bool IBindingList.AllowEdit => _sources.All(s => s.AllowEdit);

    bool IBindingList.AllowRemove => _sources.All(s => s.AllowRemove);

    bool IBindingList.SupportsChangeNotification => _sources.Cast<IBindingList>().All(s => s.SupportsChangeNotification);

    bool IBindingList.SupportsSearching => _sources.Cast<IBindingList>().All(s => s.SupportsSearching);

    bool IBindingList.SupportsSorting => false;

    bool IBindingList.IsSorted => false;

    PropertyDescriptor IBindingList.SortProperty => throw new NotSupportedException();

    ListSortDirection IBindingList.SortDirection => throw new NotSupportedException();

    object IBindingList.AddNew()
    {
        return AddNew();
    }

    void IBindingList.AddIndex(PropertyDescriptor property)
    {
        foreach (IBindingList list in _sources)
        {
            list.AddIndex(property);
        }
    }

    void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction)
    {
        throw new NotSupportedException();
    }

    int IBindingList.Find(PropertyDescriptor property, object key)
    {
        int baseIndex = 0;

        foreach (IBindingList list in _sources)
        {
            int index = list.Find(property, key);

            if (index >= 0)
            {
                return baseIndex + index;
            }

            baseIndex += list.Count;
        }

        return -1;
    }

    void IBindingList.RemoveIndex(PropertyDescriptor property)
    {
        foreach (IBindingList list in _sources)
        {
            list.RemoveIndex(property);
        }
    }

    void IBindingList.RemoveSort()
    {
        throw new NotSupportedException();
    }

    #endregion

    #region ICancelAddNew

    public void CancelNew(int itemIndex)
    {
        _sources[_sources.Count - 1].CancelNew(itemIndex);
    }

    public void EndNew(int itemIndex)
    {
        _sources[_sources.Count - 1].EndNew(itemIndex);
    }

    #endregion

    #region Private implementation details

    private void _OnListChanged(object sender, ListChangedEventArgs e)
    {
        int baseIndex = 0, sourceIndex = -1;

        for (int i = 0; i < _sources.Count; i++)
        {
            if (_sources[i] == sender)
            {
                sourceIndex = i;
                break;
            }

            baseIndex += _sources[i].Count;
        }

        if (sourceIndex == -1)
        {
            throw new Exception("internal exception -- unknown sender of ListChanged event");
        }

        ListChangedEventArgs e2;

        switch (e.ListChangedType)
        {
            case ListChangedType.ItemAdded:
            case ListChangedType.ItemChanged:
            case ListChangedType.ItemDeleted:
                e2 = new ListChangedEventArgs(e.ListChangedType, e.NewIndex + baseIndex);
                break;
            case ListChangedType.ItemMoved:
                if (e.PropertyDescriptor != null)
                {
                    e2 = new ListChangedEventArgs(e.ListChangedType, e.NewIndex + baseIndex, e.PropertyDescriptor);
                }
                else
                {
                    e2 = new ListChangedEventArgs(e.ListChangedType, e.NewIndex + baseIndex, e.OldIndex + baseIndex);
                }
                break;
            case ListChangedType.PropertyDescriptorAdded:
            case ListChangedType.PropertyDescriptorChanged:
            case ListChangedType.PropertyDescriptorDeleted:
                e2 = new ListChangedEventArgs(e.ListChangedType, e.PropertyDescriptor);
                break;
            case ListChangedType.Reset:
                e2 = new ListChangedEventArgs(e.ListChangedType, e.NewIndex + baseIndex);
                break;
            default:
                throw new ArgumentException("invalid value for e.ListChangedType");
        }

        ListChanged?.Invoke(this, e2);
    }

    private void _OnAddingNew(object sender, AddingNewEventArgs e)
    {
        AddingNew?.Invoke(this, e);
    }

    private int _GetSourceIndexOrThrow(ref int index)
    {
        int sourceIndex = _GetSourceIndex(ref index);

        if (sourceIndex >= 0)
        {
            return sourceIndex;
        }

        throw new IndexOutOfRangeException(_kstrIndexOutOfRange);
    }

    private int _GetSourceIndex(ref int index)
    {
        int sourceIndex = 0;

        while (sourceIndex < _sources.Count && index > _sources[sourceIndex].Count)
        {
            index -= _sources[sourceIndex].Count;
            sourceIndex++;
        }

        if (sourceIndex < _sources.Count)
        {
            return sourceIndex;
        }

        return -1;
    }

    #endregion
}

注意:

  1. 以上要求您的原始数据包含在BindingList<T>个对象中,而不是List<T>个对象中。其原因主要是尝试使用List<T>个对象进行此类操作并不合理,因为List<T>并未提供任何类型的列表更改通知,所以无论如何都不会有一种有效的方法让控件绑定到数据更新。
  2. 此实现使用C#中的新值元组功能。要编译此代码,您需要使用C#7,并且需要使用NuGet包管理器来安装System.ValueTuple包。
  3. 上面的实现维护了一个源BindingList<T>对象的集合,并实现了BindingList<T>所做的相同接口。它将对其进行的操作代理到适当的源列表,并且当任何源列表发生更改时,它会引发绑定到DataSource属性的相应事件。

    我尝试正确实现Add()ICancelAddNew接口。这在使用例如DataGridView。但是,我没有费心去测试代码的任何部分,因为它与你的问题没有直接关系。

    请注意,我实现了任何排序功能。这样做需要更多的开销,包括在实现和它使用的源列表之间需要额外的间接层(即维护聚合数据的排序视图)。

    确实实际上实现了比严格必要的更多。我可以在任何突变成员(插入,添加,删除等)中抛出NotSupportedException(),并要求所有修改都要通过原始源列表。那么上面只会转发ListChangedEvent。但是,那里的乐趣在哪里? :)

    将上述课程添加到项目后,使用它非常简单。只需创建此类的实例,使用要包含的每个源AddBindingList()对象调用BindingList<T>,然后将comboBox1.DataSource设置为此新CompositeBindingList<T>实例。控件将观察对CompositeBindingList<T>或其源列表的任何更改,并更新其内容以进行匹配。 (修改CompositeBindingList<T>对象将相应地修改其中一个基础源列表。)

    请参阅下面的完整Winforms示例。


    对于它的价值,如果您坚持使用List<T>作为源列表对象,那么您也可以在源列表发生更改时从头开始重新创建数据源。顺便说一句,你可以用以下的东西:

    comboBox1.DataSource = list1.Concat(list2).ToArray();
    


    正如所承诺的,这里有完整的Winforms代码来演示(不包括Program类......我假设您可以自己设置):

    public class Form1 : Form
    {
        private readonly BindingList<string> _list1 = new BindingList<string>();
        private readonly BindingList<string> _list2 = new BindingList<string>();
        private readonly CompositeBindingList<string> _composite = new CompositeBindingList<string>();
        private readonly ViewModel _model = new ViewModel();
    
        class ViewModel : INotifyPropertyChanged
        {
            private int _index;
            public int Index
            {
                get { return _index; }
                set { _UpdateField(ref _index, value); }
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected void _UpdateField<T>(ref T field, T newValue,
                Action<T> onChangedCallback = null,
                [CallerMemberName] string propertyName = null)
            {
                if (EqualityComparer<T>.Default.Equals(field, newValue))
                {
                    return;
                }
    
                T oldValue = field;
    
                field = newValue;
                onChangedCallback?.Invoke(oldValue);
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    
        public Form1()
        {
            InitializeComponent();
    
            textBox1.DataBindings.Add("Text", _model, "Index", false, DataSourceUpdateMode.OnPropertyChanged);
    
            listBox1.DataSource = _list1;
            listBox2.DataSource = _list2;
    
            _composite.AddBindingList(_list1);
            _composite.AddBindingList(_list2);
    
            listBox3.DataSource = _composite;
            comboBox1.DataSource = _composite;
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
            _list1.Add($"_list1: {_list1.Count + 1}");
        }
    
        private void button2_Click(object sender, EventArgs e)
        {
            _list2.Add($"_list2: {_list2.Count + 1}");
        }
    
        private void button3_Click(object sender, EventArgs e)
        {
            comboBox1.DataSource = _list2.Concat(_list1).ToArray();
        }
    
        private void button4_Click(object sender, EventArgs e)
        {
            _composite.Insert(_model.Index, $"global: {_composite.Count + 1}");
        }
    
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
    
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
    
        #region Windows Form Designer generated code
    
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.listBox1 = new System.Windows.Forms.ListBox();
            this.button2 = new System.Windows.Forms.Button();
            this.listBox2 = new System.Windows.Forms.ListBox();
            this.comboBox1 = new System.Windows.Forms.ComboBox();
            this.listBox3 = new System.Windows.Forms.ListBox();
            this.button3 = new System.Windows.Forms.Button();
            this.button4 = new System.Windows.Forms.Button();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(13, 13);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(160, 53);
            this.button1.TabIndex = 0;
            this.button1.Text = "Add";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // listBox1
            // 
            this.listBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
            | System.Windows.Forms.AnchorStyles.Left)));
            this.listBox1.FormattingEnabled = true;
            this.listBox1.IntegralHeight = false;
            this.listBox1.ItemHeight = 31;
            this.listBox1.Location = new System.Drawing.Point(13, 72);
            this.listBox1.Name = "listBox1";
            this.listBox1.Size = new System.Drawing.Size(315, 506);
            this.listBox1.TabIndex = 1;
            // 
            // button2
            // 
            this.button2.Location = new System.Drawing.Point(343, 12);
            this.button2.Name = "button2";
            this.button2.Size = new System.Drawing.Size(160, 53);
            this.button2.TabIndex = 0;
            this.button2.Text = "Add";
            this.button2.UseVisualStyleBackColor = true;
            this.button2.Click += new System.EventHandler(this.button2_Click);
            // 
            // listBox2
            // 
            this.listBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
            | System.Windows.Forms.AnchorStyles.Left)));
            this.listBox2.FormattingEnabled = true;
            this.listBox2.IntegralHeight = false;
            this.listBox2.ItemHeight = 31;
            this.listBox2.Location = new System.Drawing.Point(343, 71);
            this.listBox2.Name = "listBox2";
            this.listBox2.Size = new System.Drawing.Size(315, 507);
            this.listBox2.TabIndex = 1;
            // 
            // comboBox1
            // 
            this.comboBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
            this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
            this.comboBox1.FormattingEnabled = true;
            this.comboBox1.Location = new System.Drawing.Point(729, 20);
            this.comboBox1.Name = "comboBox1";
            this.comboBox1.Size = new System.Drawing.Size(286, 39);
            this.comboBox1.TabIndex = 2;
            // 
            // listBox3
            // 
            this.listBox3.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
            | System.Windows.Forms.AnchorStyles.Left)
            | System.Windows.Forms.AnchorStyles.Right)));
            this.listBox3.FormattingEnabled = true;
            this.listBox3.IntegralHeight = false;
            this.listBox3.ItemHeight = 31;
            this.listBox3.Location = new System.Drawing.Point(674, 72);
            this.listBox3.Name = "listBox3";
            this.listBox3.Size = new System.Drawing.Size(338, 506);
            this.listBox3.TabIndex = 1;
            // 
            // button3
            // 
            this.button3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
            this.button3.Location = new System.Drawing.Point(574, 12);
            this.button3.Name = "button3";
            this.button3.Size = new System.Drawing.Size(149, 53);
            this.button3.TabIndex = 3;
            this.button3.Text = "Refresh";
            this.button3.UseVisualStyleBackColor = true;
            this.button3.Click += new System.EventHandler(this.button3_Click);
            // 
            // button4
            // 
            this.button4.Location = new System.Drawing.Point(12, 598);
            this.button4.Name = "button4";
            this.button4.Size = new System.Drawing.Size(177, 52);
            this.button4.TabIndex = 4;
            this.button4.Text = "Add Global";
            this.button4.UseVisualStyleBackColor = true;
            this.button4.Click += new System.EventHandler(this.button4_Click);
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(195, 606);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(154, 38);
            this.textBox1.TabIndex = 5;
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(16F, 31F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(1027, 660);
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.button4);
            this.Controls.Add(this.button3);
            this.Controls.Add(this.comboBox1);
            this.Controls.Add(this.listBox3);
            this.Controls.Add(this.listBox2);
            this.Controls.Add(this.listBox1);
            this.Controls.Add(this.button2);
            this.Controls.Add(this.button1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
            this.PerformLayout();
    
        }
    
        #endregion
    
        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.ListBox listBox1;
        private System.Windows.Forms.Button button2;
        private System.Windows.Forms.ListBox listBox2;
        private System.Windows.Forms.ComboBox comboBox1;
        private System.Windows.Forms.ListBox listBox3;
        private System.Windows.Forms.Button button3;
        private System.Windows.Forms.Button button4;
        private System.Windows.Forms.TextBox textBox1;
    }