這是一個很好的例子,您可能想考慮切換到WPF。 WPF API具有內置的此功能,其格式爲CompositeCollection
類。
這就是說,它是可能實現的WinForms類似的效果。你「只是」必須編寫自己的綁定源實現,它可以聚合現有的源。我將「just」放在引號中,因爲正確地以全功能的方式實現綁定源是不平凡的。
這就是說,這裏,將簡單的情況下工作的例子(即那些不涉及排序源數據):
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
}
注:以上
- 要求您原始數據包含在
BindingList<T>
對象中,而不是List<T>
對象。原因主要是,嘗試使用List<T>
對象嘗試執行此類操作並沒有什麼意義,因爲List<T>
未提供任何種類的列表更改通知,因此不會有一種有效的方式無論如何,控制都要綁定到數據更新。
- 此實現使用C#中的新值元組功能。要編譯此代碼,您需要使用C#7,並且需要使用NuGet程序包管理器來安裝System.ValueTuple程序包。
上面的實現維護源對象的集合,並實現與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
類&hellip;我認爲你可以得到成立你自己):
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;
}
你可以做的嘗試:'comboBox1.DataSource = list1.Concat(列表2);' – Mederic
尼斯一個,謝謝 – Pedro
我將其添加爲一個答案 – Mederic