将数据源与 Windows 窗体中的对象一起使用



我一直在尝试创建一个小型表单应用程序,我想尝试将 DataGridView 直接绑定到对象集合。

我创建了以下类

public class MyClassRepository
{
public List<MyClass> MyClassList { get; set; } = new List<MyClass> { new MyClass { Name = "Test" } };
}
public class MyClass
{
public string Name { get; set; }
}

我将以下代码添加到要测试的表单中。在通过 UI 设置 BindingSource 后,我基于设计器中的代码(同时按照本演练 https://msdn.microsoft.com/en-us/library/ms171892.aspx)

var tmp = new BindingSource();
tmp.DataMember = "MyClassList";
tmp.DataSource = typeof(MyClassRepository);

当这不起作用时,我开始运行 BindingSource 背后的代码以查看发生了什么。二传手调用ResetList尝试通过调用ListBindingHelper.GetListFromType来创建dataSourceInstance。此调用最终调用SecurityUtils.SecureCreateInstance(Type)其中类型是BindingList<MyClassRepository>。这会将 null 传递给 args,参数传递Activator.CreateInstance返回空集合。

在此之后调用ListBindingHelper.GetList(dataSourceInstance, this.dataMember)。此方法调用ListBindingHelper.GetListItemProperties,这将导致我的MyClassList属性PropertyDescriptor并将其分配给dmProp

此时,GetList调用GetFirstItemByEnumerable(dataSource as IEnumerable)其中 dataSource 是以前创建的(和空的)BindingList<MyClassRepository>实例并返回(currentItem == null) ? null : dmProp.GetValue(currentItem);

dmProp/MyClassList 的值永远不会被访问,并且 BindingSource 永远不会填充我创建的实例。我做错了什么吗?如果不是,源代码中是否存在错误?在我看来,应该调用SecureCreateInstance(Type type, object[] args)并且应该通过 args 传递 MyClassList,而不是现有的对SecureCreateInstance(Type type)的调用,或者无论如何都应该使用dmProp的值?

如果不正确,如何使设计器自动生成的代码将数据源设置为对象的实例?还是必须从 BindingSource 继承?如果是后者,为什么它为您提供了选择不从 BindingSource 继承的类的选项?

正如Reza Aghaei指出的那样,在设计器中,将BindingSource.DataSource设置为"MyClassRepository"可能会起作用,但是您仍然需要初始化(创建一个新的)MyClassRepository对象。我在发布的代码中没有看到这行代码:MyClassRepository myRepositiory = new MyClassRepository();数据源为空,因为您尚未创建"MyClassRepository"的实例,正如 Reza 指出的那样,这通常是在表单Load事件中完成的。

为简单起见,请在设计器中删除BindingSourceDataSource,只需在窗体 load 事件中设置BindingSource’s数据源,如下所示。首先,创建MyClassRepository的新"实例",然后将其MyClassList属性用作BindingSource的数据源。我希望这有所帮助。

MyClassRepository repOfMyClass;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
repOfMyClass = new MyClassRepository();
bindingSource1.DataSource = repOfMyClass.MyClassList;
dataGridView1.DataSource = bindingSource1;
}

编辑-----

经过进一步审查...我同意你应该能够按照你的描述去做。我能够使用下面的代码使其按预期工作。

BindingSource bindingSource1;
private void Form1_Load(object sender, EventArgs e) {
bindingSource1 = new BindingSource();
bindingSource1.DataSource = typeof(MyClassRepository);
bindingSource1.DataMember = "MyClassList";
dataGridView1.DataSource = bindingSource1;
}

我按照"设计器"中的相同步骤进行操作,它按预期工作。我还缺少什么吗?正如你所说...使用MyClassRepository mcr = new MyClassRepository()似乎是不必要的。此外,如果您无法使用上述两种方式之一使其工作......然后发生了其他事情。如果它不能按上述方式工作,会发生什么?

编辑 2

在没有创建"新"MyClassRepository的情况下,对象是意外的,我没有意识到添加到列表/网格中的新项目正在进入bindingSource1。重点是,如果不实例化"新"MyClassRepository对象,构造函数将永远不会运行。这意味着属性List<MyClass> MyClassList永远不会实例化。也不会设置默认值。

因此,在此上下文中将无法访问MyClassList变量。例如,在此特定情况下,如果将行添加到网格,则bindingSource1.Count属性将返回正确的行数。不仅行数在MyClassList中为零 (0),而且更重要的是......"如何"访问MyClassList属性而不首先实例化"新"MyClassRepository对象?由于这种无法访问,MyClassList将永远不会被使用。

编辑 3 ---

您要实现的目标可以通过多种方式完成。如果您希望MyClassList包含用户在网格中所做的实时更改,则使用您的代码和我发布的代码将不起作用。例如,在您的代码和我的代码中...如果用户将一行添加到网格中,它会将该项添加到"绑定源"中,但不会将其添加到MyClassList。我只能猜测这不是你想要的。否则,MyClassList的目的是什么.下面的代码"将"按预期使用MyClassList。如果你放弃"设计师"的观点...您可以在三 (3) 行代码中执行相同的操作...如果您修复损坏的MyClassRepository类并在窗体加载事件上创建一个新类。恕我直言,这比摆弄设计师要容易得多。

MyClassRepository的更改...添加了一个构造函数,添加了 Size 属性和方法作为示例。

class MyClassRepository {
public List<MyClass> MyClassList { get; set; }
public int MaxSize { get; set; }
public MyClassRepository() {
MyClassList = new List<MyClass>();
MaxSize = 1000;
}
public void MyClassListSize() {
MessageBox.Show("MyClassList.Count: " + MyClassList.Count);
}
// other list manager methods....
}

MyClass的更改...添加了一个属性作为示例。

class MyClass {
public string Name { get; set; }
public string Age { get; set; }
}

最后,窗体加载事件以创建新MyClassRepository,将绑定源设置为指向MyClassList最后将绑定源设置为网格的数据源。注意:创建myClassRepositorygridBindingSource全局变量是不必要的,以这种方式设置以检查MyClassList是否根据用户在网格中执行的操作实时更新。这是在下面的按钮单击事件中完成的。

MyClassRepository myClassRepository;
BindingSource gridBindingSource;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
try {
myClassRepository = new MyClassRepository();
gridBindingSource = new BindingSource(myClassRepository.MyClassList, "");
dataGridView1.DataSource = gridBindingSource;
}
catch (Exception ex) {
MessageBox.Show("Error: " + ex.Message); 
}
}
private void button1_Click_1(object sender, EventArgs e) {
MessageBox.Show("Binding source count:" + gridBindingSource.Count + Environment.NewLine +
"MyClassList count: " + myClassRepository.MyClassList.Count);
}

我希望这是有道理的。

设计器设置了设计时支持DataSource = typeof(Something),例如,允许您从下拉列表中选择DataMember,或者允许您在设置数据绑定时从下拉列表中选择数据源属性。

如何使设计器自动生成的代码集DataSource对象的实例?

强迫设计器这样做没有多大意义,因为设计器不知道您将使用什么实际数据源来加载数据。它可以是 Web 服务、WCF 服务、业务逻辑层类。

因此,在运行时,您需要将列表的实例分配给DataSource。例如Load表单的事件中。

最新更新