将 DataSource 与 Windows 表单中的对象一起使用

Using a DataSource with an object in Windows Forms

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

我创建了以下 classes

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 背后的代码来查看发生了什么。 setter 调用 ResetList 尝试通过调用 ListBindingHelper.GetListFromType 创建 dataSourceInstance。此调用最终调用 SecurityUtils.SecureCreateInstance(Type),其中类型为 BindingList<MyClassRepository>。这将 null 传递给 args 传递给 Activator.CreateInstance 其中 returns 一个空集合。

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

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

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

如果这不正确,我该如何让设计器自动生成的代码将数据源设置为对象的一个​​实例?还是必须继承自 BindingSource?如果是后者,为什么它让您可以选择不从 BindingSource 继承的 class?

Designer 设置 DataSource = typeof(Something) 用于设计时支持,例如让您从下拉列表中选择 DataMember 或让您在设置时从下拉列表中选择数据源 属性数据绑定。

How do I make the Designers automatically generated code set the DataSource to an instance of the object?

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

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

正如 Reza Aghaei 指出的那样,在设计器中,将 BindingSource.DataSource 设置为“MyClassRepository”可能有效,但是您仍然需要初始化(创建一个新的)MyClassRepository 对象。我在发布的代码中的任何地方都没有看到这行代码:MyClassRepository myRepositiory = new MyClassRepository(); 数据源是空的,因为您还没有创建“MyClassRepository”的实例,正如 Reza 指出的那样,这通常以 Load事件。

为简单起见,在设计器中删除 BindingSourceDataSource,并在表单加载事件中简单地设置 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,对象是意外的,我没有意识到添加到 list/grid 的新项目正在进入 bindingSource1。要点是,如果不实例化“新”MyClassRepository 对象,构造函数将永远不会 运行。这意味着 属性 List<MyClass> MyClassList 永远不会被实例化。也不会设置默认值。

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

编辑 3 ---

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

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

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);
}

我希望这是有道理的。 ;-)