ListBox.DataSource 綁定集合時,Items 集合變為**唯讀代理**,直接呼叫 Items.Add/Remove/Clear 會拋 InvalidOperationException:「設定 DataSource 屬性後無法修改項集合」。
**核心原則**:修改綁定的資料源集合 (List<T>, BindingList<T>, DataTable),再重新指派 DataSource 或呼叫 ResetBindings()。
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Windows.Forms;
public static class ListBoxDataSourceHelper
{
// 取得目前綁定的 List (自動建立/複製)
private static List GetSourceList(ListBox lb)
{
if (lb.DataSource is List list) return list;
var newList = new List();
if (lb.DataSource != null)
{
// 嘗試從 IEnumerable 複製
foreach (var item in (IEnumerable)lb.DataSource)
if (item is T t) newList.Add(t);
}
lb.DataSource = null; // 先解除綁定
lb.DataSource = newList;
return newList;
}
// 取得 DataTable (針對 T = DataRow)
private static DataTable GetSourceTable(ListBox lb)
{
if (lb.DataSource is DataTable dt) return dt;
var newDt = new DataTable();
if (lb.DataSource is DataView dv) newDt = dv.Table.Copy();
else if (lb.DataSource is DataTable dt2) newDt = dt2.Copy();
lb.DataSource = null;
lb.DataSource = newDt;
return newDt;
}
public static void AddItem(ListBox lb, T item)
{
if (typeof(T) == typeof(DataRow))
GetSourceTable(lb).Rows.Add(((DataRow)(object)item).ItemArray);
else
GetSourceList(lb).Add(item);
lb.DisplayMember = lb.DisplayMember; // 觸發重新整理
}
public static void AddRange(ListBox lb, IEnumerable items)
{
if (typeof(T) == typeof(DataRow))
{
var dt = GetSourceTable(lb);
foreach (DataRow row in (IEnumerable)items)
dt.Rows.Add(row.ItemArray);
}
else
{
GetSourceList(lb).AddRange(items);
}
lb.DisplayMember = lb.DisplayMember;
}
public static void RemoveItem(ListBox lb, T item)
{
if (typeof(T) == typeof(DataRow))
GetSourceTable(lb).Rows.Remove((DataRow)(object)item);
else
GetSourceList(lb).Remove(item);
lb.DisplayMember = lb.DisplayMember;
}
public static void RemoveWhere(ListBox lb, Predicate match)
{
if (typeof(T) == typeof(DataRow))
{
var dt = GetSourceTable(lb);
for (int i = dt.Rows.Count - 1; i >= 0; i--)
if (match((T)(object)dt.Rows[i])) dt.Rows.RemoveAt(i);
}
else
{
GetSourceList(lb).RemoveAll(match);
}
lb.DisplayMember = lb.DisplayMember;
}
public static void ClearAll(ListBox lb)
{
if (typeof(T) == typeof(DataRow))
GetSourceTable(lb).Clear();
else
GetSourceList(lb).Clear();
lb.DisplayMember = lb.DisplayMember;
}
public static void MoveItems(ListBox from, ListBox to, IEnumerable selectedItems)
{
var items = selectedItems.Cast().ToList();
AddRange(to, items);
foreach (var item in items) RemoveItem(from, item);
}
public static void MoveAll(ListBox from, ListBox to)
{
if (typeof(T) == typeof(DataRow))
{
var dt = (DataTable)from.DataSource;
AddRange(to, dt.Rows.Cast().Select(r => (T)(object)r));
from.DataSource = dt.Clone(); // 保留結構清空資料
}
else
{
AddRange(to, (List)from.DataSource);
from.DataSource = new List();
}
}
}
// 綁定泛型列表
var list = new List { "A", "B", "C" };
lbSource.DataSource = list;
lbSource.DisplayMember = ""; // 顯示字串本身
// 移動選取項目到另一個 ListBox
ListBoxDataSourceHelper.MoveItems(lbSource, lbTarget, lbSource.SelectedItems);
// 新增項目
ListBoxDataSourceHelper.AddItem(lbSource, "New Item");
// 移除符合條件項目
ListBoxDataSourceHelper.RemoveWhere(lbSource, s => s.StartsWith("A"));
var bindingList = new BindingList { "A", "B", "C" };
lbSource.DataSource = bindingList;
// 直接操作 bindingList 即可自動更新 UI,無需重設 DataSource
bindingList.Add("D"); // UI 即時新增
bindingList.RemoveAt(0); // UI 即時移除
BindingList<T> 實作 IBindingList,支援新增/移除/排序通知。
var dt = new DataTable();
dt.Columns.Add("Name");
dt.Columns.Add("Value");
dt.Rows.Add("Item1", 100);
dt.Rows.Add("Item2", 200);
lbSource.DataSource = dt;
lbSource.DisplayMember = "Name"; // 顯示 Name 欄位
lbSource.ValueMember = "Value"; // 實際值
// 移除選取行
if (lbSource.SelectedItem is DataRowView drv)
drv.Row.Delete(); // 標記刪除
dt.AcceptChanges(); // 確認刪除
ListBox.Items**,會拋例外。List<T>, BindingList<T>, DataTable)。BindingList<T> 獲得自動 UI 同步,最省碼、最穩健。List<T>,操作後重設 DataSource = null; DataSource = list; 或呼叫 lb.DisplayMember = lb.DisplayMember; 觸發重繪。遵循「資料驅動 UI」原則,ListBox 資料操作即變得簡單可靠。