首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# WinForms ListBox DataSource 問題解決:泛型管理類實現項目移動

     編輯:關於C語言

前言

WinForms ListBox 設定 DataSource 後,直接操作 Items 集合會拋出「設定 DataSource 屬性後無法修改項集合」例外。本文提供泛型管理類 LstCtrlMove_Mgr<T>,統一處理 List<T> 與 DataTable 兩種資料來源的項目新增、移除、移動操作。

問題根因


// 錯誤示範
listBox.DataSource = myList;
listBox.Items.Add("New Item");  // InvalidOperationException

// 原因:DataSource 綁定時,ListBox 內部使用 CurrencyManager 管理資料
// Items 集合變為唯讀,所有異動必須透過資料來源本身

解決方案核心邏輯

原則:對資料來源 (List<T> 或 DataTable) 進行異動,再重新指派 DataSource 觸發 UI 更新。

完整管理類實作


using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Windows.Forms;

namespace Lucifer
{
    public static class LstCtrlMove_Mgr
    {
        // 從 ListBox 移除項目
        public static void RemoveItems(ListBox lstBox, IEnumerable items)
        {
            if (typeof(T) == typeof(DataRow))
            {
                // DataTable 模式
                DataTable dt = (DataTable)lstBox.DataSource;
                DataTable newDt = dt.Clone();
                
                foreach (DataRow dr in dt.Rows)
                {
                    bool shouldRemove = false;
                    foreach (DataRowView item in items)
                    {
                        if (dr == item.Row) { shouldRemove = true; break; }
                    }
                    if (!shouldRemove) newDt.ImportRow(dr);
                }
                lstBox.DataSource = newDt;
            }
            else
            {
                // List 模式
                var list = new List((List)lstBox.DataSource);
                list.RemoveAll(item1 => items.Cast().Any(item2 => item1.Equals(item2)));
                lstBox.DataSource = list;
            }
        }

        // 向 ListBox 新增項目
        public static void AddItems(ListBox lstBox, IEnumerable items)
        {
            if (typeof(T) == typeof(DataRow))
            {
                DataTable dt = null;
                foreach (object item in items)
                {
                    if (item is DataRowView drv) dt = drv.Row.Table.Clone();
                    else if (item is DataRow dr) dt = dr.Table.Clone();
                    if (dt != null) break;
                }
                
                if (lstBox.DataSource != null) dt = ((DataTable)lstBox.DataSource).Copy();
                if (dt == null) dt = new DataTable(); // fallback
                
                foreach (object item in items)
                {
                    if (item is DataRowView drv) dt.ImportRow(drv.Row);
                    else if (item is DataRow dr) dt.ImportRow(dr);
                }
                lstBox.DataSource = dt;
            }
            else
            {
                var list = new List();
                if (lstBox.DataSource != null) list.AddRange((List)lstBox.DataSource);
                foreach (T item in items) list.Add(item);
                lstBox.DataSource = list;
            }
        }

        // 移動選中項目:從 lstBox1 到 lstBox2
        public static void Move(ListBox lstBox1, ListBox lstBox2)
        {
            if (lstBox1.SelectedItems.Count > 0)
            {
                AddItems(lstBox2, lstBox1.SelectedItems);
                RemoveItems(lstBox1, lstBox1.SelectedItems);
            }
        }

        // 移動所有項目
        public static void MoveAll(ListBox lstBox1, ListBox lstBox2)
        {
            if (typeof(T) == typeof(DataRow))
            {
                DataTable dt = (DataTable)lstBox.DataSource;
                AddItems(lstBox2, dt.Rows);
                lstBox1.DataSource = dt.Clone(); // 空結構
            }
            else
            {
                AddItems(lstBox2, (List)lstBox1.DataSource);
                lstBox1.DataSource = new List();
            }
        }
    }
}

使用範例

List<string> 模式


// 表單載入初始化
private void Form_Load(object sender, EventArgs e)
{
    var sourceList = new List { "Apple", "Banana", "Cherry", "Date" };
    var targetList = new List();
    
    listBoxSource.DataSource = sourceList;
    listBoxTarget.DataSource = targetList;
    
    // 重要:指定泛型型別參數
    // 移動選中項目
    LstCtrlMove_Mgr.Move(listBoxSource, listBoxTarget);
    
    // 移動所有
    LstCtrlMove_Mgr.MoveAll(listBoxSource, listBoxTarget);
}

DataTable 模式 (泛型參數指定為 DataRow)


private void Form_Load(object sender, EventArgs e)
{
    DataTable dtSource = GetDataTable(); // 包含 Name, Value 欄位
    DataTable dtTarget = dtSource.Clone(); // 空結構
    
    listBoxSource.DataSource = dtSource;
    listBoxSource.DisplayMember = "Name";
    listBoxSource.ValueMember = "Value";
    
    listBoxTarget.DataSource = dtTarget;
    listBoxTarget.DisplayMember = "Name";
    listBoxTarget.ValueMember = "Value";
    
    // 關鍵:泛型參數用 DataRow
    LstCtrlMove_Mgr.Move(listBoxSource, listBoxTarget);
}

關鍵設計決策

決策點選擇理由
泛型參數T 為資料項型別List<T> 直接對應;DataTable 用 DataRow 代表項目
DataTable 異動方式Clone + ImportRow避免直接 Delete 觸發 AcceptChanges,保持資料完整性
重新指派 DataSource每次異動後重設觸發 CurrencyManager 重新繫結,UI 即時更新
靜態類別無狀態工具方法簡單、執行緒安全 (單一 UI 執行緒)、易測試

常見陷阱與修正

  • 未指定泛型參數:LstCtrlMove_Mgr.Move(...) 編譯錯誤,必須 LstCtrlMove_Mgr<string>.Move(...)
  • DataTable 模式泛型參數錯誤:必須用 DataRow,不能用 DataRowView 或 DataTable
  • DisplayMember 未設定:DataTable 模式下 ListBox 顯示 System.Data.DataRowView,需設定 DisplayMember
  • 大量資料效能:頻繁重設 DataSource 會重建繫結,大量資料考慮 BindingList<T> 或 VirtualMode

總結

ListBox.DataSource 綁定後項集合唯讀是設計使然,解決之道是操作資料來源、重新指派 DataSource。本文提供的泛型管理類統一處理 List<T> 與 DataTable 兩種常見資料來源,封裝新增/移除/移動邏輯,可直接整合至專案使用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved