英文:
Winforms ListBox with DataSource bound to dotted path of nested object crashes when intermediate object of dotted path is null
问题
- ListBox的
DataSource
绑定到Detail.Tags
。 - 当我选择第一行时,ListBox会按预期填充。
- 当我选择第二行时,期望的(并且希望的)结果是,ListBox简单地显示为空,因为为了演示目的,ItemB的
Detail
属性被故意设置为null,所以ItemB的Detail.Tags
不存在。 - 实际结果是程序崩溃到桌面,出现
System.ArgumentException: '复杂数据绑定接受的数据源可以是IList或IListSource。'
。
英文:
- The ListBox's
DataSource
is bound toDetail.Tags
. - When I select the first row, ListBox populates as expected.
- When I select the second row, the expected (and desired) result is that the ListBox simply displays nothing, because ItemB's
Detail
property is purposely null for demonstration purposes, so ItemB'sDetail.Tags
doesn't exist. - Actual result is that program crashes to desktop with
System.ArgumentException: 'Complex DataBinding accepts as a data source either an IList or an IListSource.'
Minimal reproducible example:
public partial class Form1 : Form
{
private readonly IList<Item> _items;
private BindingSource _bs = new BindingSource(){ DataSource = typeof(Item) };
public Form1()
{
InitializeComponent();
_items = GenerateSampleItems();
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.DataSource = _items;
listBox1.DataBindings.Add(new Binding("DataSource", _bs, "Detail.Tags", false, DataSourceUpdateMode.Never));
}
private void DataGridView1_SelectionChanged(object sender, EventArgs e)
{
if (dataGridView1.SelectedRows.Count == 1)
{
_bs.DataSource = dataGridView1.SelectedRows[0].DataBoundItem;
}
else
{
_bs.DataSource = typeof(Item);
}
}
private IList<Item> GenerateSampleItems()
{
return new List<Item>()
{
new Item()
{
Name = "ItemA"
,Detail = new Detail()
{
Expiration = new DateTime(2024,1,1)
,Tags = new BindingList<Tag>(new List<Tag>()
{
new Tag()
{
TagName = "FirstT"
,TagValue = "FirstV"
}
,new Tag()
{
TagName = "SecondT"
,TagValue = "SecondV"
}
})
}
}
,new Item()
{
Name = "ItemB"
// Detail purposely omitted
}
,new Item()
{
Name = "ItemC"
// Detail purposely omitted
}
};
}
}
class Item
{
public string Name { get; set; }
public Detail Detail { get; set; }
}
public class Detail
{
public DateTime Expiration { get; set; }
public BindingList<Tag> Tags { get; set; }
}
public class Tag
{
public string TagName { get; set; }
public string TagValue { get; set; }
}
答案1
得分: 1
你可以通过为每个模型创建一个 BindingSource
来解决这个问题:
- 主
BindingSource
,其中DataSource
属性设置为一个Item
列表。这个是DataGridView.DataSource
。 - 第二个
BindingSource
用于导航主BindingSource
的Detail
数据成员。 - 第三个用于导航和显示详细的
BindingSource
的Tags
数据成员。这个是ListBox.DataSource
。
public partial class Form1 : Form
{
private IList<Item> _items;
private BindingSource _bsItems, _bsDetail, _bsTags;
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_items = GenerateSampleItems();
_bsItems = new BindingSource(_items, null);
_bsDetail = new BindingSource(_bsItems, "Detail");
_bsTags = new BindingSource(_bsDetail, "Tags");
dataGridView1.DataSource = _bsItems;
listBox1.DataSource = _bsTags;
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
_bsItems.Dispose();
_bsDetail.Dispose();
_bsTags.Dispose();
}
private IList<Item> GenerateSampleItems()
{
return new List<Item>()
{
new Item()
{
Name = "ItemA",
Detail = new Detail
{
Expiration = new DateTime(2024, 1, 1),
Tags = new BindingList<Tag>(new List<Tag>()
{
new Tag
{
TagName = "FirstT",
TagValue = "FirstV"
},
new Tag
{
TagName = "SecondT",
TagValue = "SecondV"
}
})
}
},
new Item()
{
Name = "ItemB"
// 详细信息故意省略
},
new Item()
{
Name = "ItemC"
// 详细信息故意省略
}
};
}
}
// 项目的命名空间中的其他位置
public class Item
{
public string Name { get; set; }
public Detail Detail { get; set; }
// 可选: 根据需要更改或删除...
public override string ToString()
{
return $"Name: {Name} - Detail: {Detail}";
}
}
public class Detail
{
public DateTime Expiration { get; set; }
public BindingList<Tag> Tags { get; set; }
// 可选: 根据需要更改或删除...
public override string ToString()
{
var tags = $"[{string.Join(", ", Tags)}]";
return $"Expiration: {Expiration} - Tags: {tags}";
}
}
public class Tag
{
public string TagName { get; set; }
public string TagValue { get; set; }
// 可选: 根据需要更改或删除...
public override string ToString()
{
return $"{TagName}: {TagValue}";
}
}
就是这样。无需添加 DataBindings
或处理网格的 SelectionChanged
事件,如您在代码片段中所示。
另一方面,如果您需要显示选定的 Item.Detail.Tags
,则需要在网格的选择更改时将它们展开为列表,并将结果绑定到 ListBox
。
// +
using System.Linq;
public partial class Form1 : Form
{
private BindingSource _bsItems;
public Form1() => InitializeComponent();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_bsItems = new BindingSource(GenerateSampleItems(), null);
dataGridView1.DataSource = _bsItems;
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
_bsItems.Dispose();
}
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
listBox1.DataSource = dataGridView1.SelectedCells
.Cast<DataGridViewCell>()
.Select(cell => cell.OwningRow).Distinct()
.Where(row => (row.DataBoundItem as Item)?.Detail != null)
.SelectMany(row => (row.DataBoundItem as Item).Detail.Tags)
.ToList();
}
}
英文:
You can solve this problem by Creating a BindingSource
for each model:
- Main
BindingSource
where itsDataSource
property is set to a list ofItem
. This one is theDataGridView.DataSource
. - Second
BindingSource
to navigate theDetail
data members of the mainBindingSource
. - Third one to navigate and display the
Tags
data members of the detail'sBindingSource
. This one is theListBox.DataSource
.
public partial class Form1 : Form
{
private IList<Item> _items;
private BindingSource _bsItems, _bsDetail, _bsTags;
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_items = GenerateSampleItems();
_bsItems = new BindingSource(_items, null);
_bsDetail = new BindingSource(_bsItems, "Detail");
_bsTags = new BindingSource(_bsDetail, "Tags");
dataGridView1.DataSource = _bsItems;
listBox1.DataSource = _bsTags;
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
_bsItems.Dispose();
_bsDetail.Dispose();
_bsTags.Dispose();
}
private IList<Item> GenerateSampleItems()
{
return new List<Item>()
{
new Item()
{
Name = "ItemA",
Detail = new Detail
{
Expiration = new DateTime(2024,1,1),
Tags = new BindingList<Tag>(new List<Tag>()
{
new Tag
{
TagName = "FirstT",
TagValue = "FirstV"
},
new Tag
{
TagName = "SecondT",
TagValue = "SecondV"
}
})
}
},
new Item()
{
Name = "ItemB"
// Detail purposely omitted
},
new Item()
{
Name = "ItemC"
// Detail purposely omitted
}
};
}
}
// Elsewhere within the project's namespace
public class Item
{
public string Name { get; set; }
public Detail Detail { get; set; }
// Optional: Change, remove as needed...
public override string ToString()
{
return $"Name: {Name} - Detail: {Detail}";
}
}
public class Detail
{
public DateTime Expiration { get; set; }
public BindingList<Tag> Tags { get; set; }
// Optional: Change, remove as needed...
public override string ToString()
{
var tags = $"[{string.Join(", ", Tags)}]";
return $"Expiration: {Expiration} - Tags: {tags}";
}
}
public class Tag
{
public string TagName { get; set; }
public string TagValue { get; set; }
// Optional: Change, remove as needed...
public override string ToString()
{
return $"{TagName}: {TagValue}";
}
}
That's all. No need to add DataBindings
nor to handle the grid's SelectionChanged
event as shown in your code snippet.
On the other hand, if you need to display the selected Item.Detail.Tags
, then you need to flatten them in a list whenever the grid's selection changes and bind the result to the ListBox
.
// +
using System.Linq;
public partial class Form1 : Form
{
private BindingSource _bsItems;
public Form1() => InitializeComponent();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_bsItems = new BindingSource(GenerateSampleItems(), null);
dataGridView1.DataSource = _bsItems;
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
_bsItems.Dispose();
}
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
listBox1.DataSource = dataGridView1.SelectedCells
.Cast<DataGridViewCell>()
.Select(cell => cell.OwningRow).Distinct()
.Where(row => (row.DataBoundItem as Item)?.Detail != null)
.SelectMany(row => (row.DataBoundItem as Item).Detail.Tags)
.ToList();
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论