ListBox自定义不同颜色项时遇数据源绑定异常的技术问询
异常System.ArgumentException: 'Items collection cannot be modified when the DataSource property is set.'的核心原因是:当ListBox通过DataSource绑定了数据源后,其Items集合会被锁定为只读状态,无法直接通过Items.Add/Items.Remove等方法修改。
你的代码中,在构造函数的逻辑里,当selectedPath存在且rectangles.txt文件存在时,执行了listBox1.DataSource = FileList.Keys.ToList();,这就绑定了数据源。之后再调用listBox1.Items.Add自然会触发异常。
另外,当前代码还存在一个隐藏问题:你绑定的数据源是string类型的FileList键,但要添加的是MyListBoxItem类型的对象,两者类型不统一,会导致DrawItem事件中的类型转换失败(非MyListBoxItem的项会进入else分支)。
要同时显示FileList的键和自定义的彩色提示项,需要统一数据源类型,通过修改绑定的数据源来添加项,而不是直接操作Items集合。具体步骤如下:
1. 创建统一的列表项基类
定义一个基类来统一两种类型的列表项,确保它们都包含显示文本和颜色属性:
public class ListBoxItemBase { public virtual string DisplayText { get; } public virtual Color ItemColor { get; } = Color.Black; // 默认黑色 }
2. 适配现有项类型
- 修改
MyListBoxItem继承自基类:
public class MyListBoxItem : ListBoxItemBase { public MyListBoxItem(Color c, string m) { ItemColor = c; DisplayText = m; } public override Color ItemColor { get; } public override string DisplayText { get; } }
- 创建适配FileList键的子类:
public class FileKeyListItem : ListBoxItemBase { private readonly string _key; public FileKeyListItem(string key) => _key = key; public override string DisplayText => _key; }
3. 修改构造函数中的数据源绑定逻辑
不再直接绑定FileList.Keys,而是创建一个包含所有项的统一列表,再绑定到DataSource:
public Form1() { InitializeComponent(); // 保留原有初始化代码... List<ListBoxItemBase> listItems = new List<ListBoxItemBase>(); if (selectedPath != "" && selectedPath != null) { if (System.IO.File.Exists(Path.Combine(selectedPath, "rectangles.txt"))) { string g = System.IO.File.ReadAllText(Path.Combine(selectedPath, "rectangles.txt")); g = g.Remove(0, 32); FileList = JsonConvert.DeserializeObject<Dictionary<string, string>>(g); // 将FileList的键转换为FileKeyListItem添加到统一列表 foreach (var key in FileList.Keys) { listItems.Add(new FileKeyListItem(key)); } label2.Text = listItems.Count.ToString(); } else { label2.Text = "0"; } } else { label2.Text = "0"; } // 添加自定义测试项到统一列表 listItems.Add(new MyListBoxItem(Color.Green, "Validated data successfully")); listItems.Add(new MyListBoxItem(Color.Red, "Failed to validate data")); // 绑定统一列表作为数据源 listBox1.DataSource = listItems; // 设置选中最后一项(如果有项的话) if (listItems.Count > 0) { listBox1.SelectedIndex = listItems.Count - 1; } // 保留原有剩余代码... }
4. 更新DrawItem事件处理
适配统一的基类,确保所有项都能正确绘制:
private void listBox1_DrawItem(object sender, DrawItemEventArgs e) { // 绘制背景和焦点矩形(必须添加,否则选中状态显示异常) e.DrawBackground(); e.DrawFocusRectangle(); ListBoxItemBase item = listBox1.Items[e.Index] as ListBoxItemBase; if (item != null) { e.Graphics.DrawString( item.DisplayText, listBox1.Font, new SolidBrush(item.ItemColor), e.Bounds.X, e.Bounds.Y ); } else { // 处理意外类型的项,使用默认样式绘制 e.Graphics.DrawString( listBox1.Items[e.Index].ToString(), listBox1.Font, new SolidBrush(e.ForeColor), e.Bounds.X, e.Bounds.Y ); } }
5. 更新SelectedIndexChanged事件
适配新的项类型,确保FileList的项被选中时能正确加载图片:
private void listBox1_SelectedIndexChanged(object sender, EventArgs e) { var fileItem = listBox1.SelectedItem as FileKeyListItem; if (fileItem != null && FileList.TryGetValue(fileItem.DisplayText, out string val)) { if (File.Exists(val)) { pictureBox1.Image = System.Drawing.Image.FromFile(val); } } // 自定义提示项被选中时可根据需求添加逻辑 }
6. 更新pictureBox2_MouseUp中的数据源更新
当新增裁剪区域后,需要重新构建统一列表并绑定:
private void pictureBox2_MouseUp(object sender, MouseEventArgs e) { // 保留原有逻辑... if (dr.Rect.Width > 0 && dr.Rect.Height > 0) { // 保留原有保存逻辑... // 重新构建统一列表 List<ListBoxItemBase> updatedItems = new List<ListBoxItemBase>(); foreach (var key in FileList.Keys) { updatedItems.Add(new FileKeyListItem(key)); } // 如果需要保留自定义提示项,可再次添加 // updatedItems.Add(new MyListBoxItem(Color.Green, "Validated data successfully")); // updatedItems.Add(new MyListBoxItem(Color.Red, "Failed to validate data")); listBox1.DataSource = updatedItems; if (updatedItems.Count > 0) { listBox1.SelectedIndex = updatedItems.Count - 1; } // 保留原有剩余逻辑... } // 保留原有剩余逻辑... }
内容的提问来源于stack exchange,提问作者Daniel Lip

