【问题标题】:Using DataGridView with BindingSource.DataSource=List<T>, need to bind to inherited type of T使用 DataGridView 和 BindingSource.DataSource=List<T>,需要绑定到继承的 T 类型
【发布时间】:2012-05-21 20:27:22
【问题描述】:

我正在寻找使用 C# 和 .Net3.5 解决与 Winforms datagridview 的数据绑定问题。它有点复杂,包含一些我将提供代码的类。

我有一个这样定义的接口:

public interface MyElement : IEditableObject {
    string Name { get; set; }
    string Description { get; set; }
}

接下来我有一个继承自 MyElement 的抽象类,定义如下:

abstract public class MyElementAdapter : MyElement {
    private string myName;
    private string myDescription;
    private ModelElement myParent;
    public MyElementAdapter(MyElement parent) {
        myName = "New_Element";
        myDescription = string.Empty;
    }
    virtual public string Name {
        get { return myName; }
        set { myName = value; }
    }
    virtual public string Description {
        get { return myDescription; }
        set { myDescription = value; }
    }
    public MyElement Parent {
        get { return myParent; }
        set { myParent = value; }
    }
    public void BeginEdit() {
        // code to begin edit
    }
    public void CancelEdit() {
        // code to cancel edit
    }
    public void EndEdit() {
        // code to end edit
    }
}

有一个这样定义的接口类型的通用列表 - 它非常基本,只保留了 MyElements 的通用列表:

public class MyElementList : List<MyElement> {
    public MyElementList() {
    }
}

有一个名为 MyField 的继承类,它派生自 MyElementAdapter,定义如下:

public class MyField : MyElementAdapter {
    private int size;
    public MyField(MyElement parent) : base(parent) {
        size = 1;
    }
    public string MyFieldName {
        get { return this.Name; }
        set { this.Name = value;}
    }
    public string MyFieldDescription {
        get { return this.Description; }
        set { this.Description = value;}
    }
    public int Size {
        get { return size; }
        set { size = value; }
    }
}

有一个使用 MyElementList 的类,其中 MyField 是 List 类型:

public class MyRecordOfFields : MyElementAdapter {
    private MyElementList myMembers;
    public MyRecordOfFields(MyElement parent) : base(parent) {
        myMembers = new MyElementList();
    }
    public MyElementList MyMembers {
        get { return myMembers; }
        set { myMembers = value; }
    }
}

最后,我使用了一个表单来编辑 MyRecordOfFields 类型的对象。

public class Form1 : Form {
    private BindingSource bindingSource1;
    private MyRecordOfFields myLocalFields;
    private DataGridView dgv;

    private Form1_Load() {
        myLocalFields = new MyRecordOfFields(null);
        MyField field1 = new MyField(null);
        field1.MyFieldName = "Field_1";
        field1.MyFieldDescription = "Description1";
        field1.Size = 3;
        myLocalFields.MyMembers.Add(field1);
        MyField field2 = new MyField(null);
        field2.MyFieldName = "Field_2";
        field2.MyFieldDescription = "Description2";
        field2.Size = 3;
        myLocalFields.MyMembers.Add(field2);
        MyField field3 = new MyField(null);
        field3.MyFieldName = "Field_3";
        field3.MyFieldDescription = "Description3";
        field3.Size = 3;
        myLocalFields.MyMembers.Add(field3);

        bindingSource1 = new BindingSource();
        bindingSource1.AllowNew = true;

        dgv.AutoGenerateColumns = false;
        int colIndex = dgv.Columns.Add("MyFieldName", "MyFieldName");
        dgv.Columns[colIndex].DataPropertyName = "MyFieldName";
        colIndex = dgv.Columns.Add("MyFieldDescription", "MyFieldDescription");
        dgv.Columns[colIndex].DataPropertyName = "MyFieldDescription";
        colIndex = dgv.Columns.Add("Size", "Size");
        dgv.Columns[colIndex].DataPropertyName = "Size";
        dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
        dgv.AutoResizeColumns();

        bindingSource1.DataSource = myLocalFields.MyMembers;
        dgv.DataSource = bindingSource1;
        dgv.Invalidate();
    }
}

加载此表单时,所有内容都会编译并运行,但是当显示表单时,datagridview 有 3 行但全为空白。我正在尝试将 datagridview 的数据源直接设置为 MyRecordOfFields 类中的 MyElementList,但是对于添加到列表中的行数,没有显示任何内容。

当我没有将 datagridview 的数据源设置为列表,而是在循环中添加该列表中的每个元素时,数据将按应有的方式显示。

下面一行:

        bindingSource1.DataSource = myLocalFields.MyMembers;

改为循环:

        foreach (MyElement me in myLocalFields.MyMembers) {
            // No casting needed due to inheritance
            bindingSource1.Add(me);
        }

在更改为循环后,我必须添加事件处理程序,以添加、删除和更新底层数据源,即 myLocalFields (MyRecordOfFields.MyMembers)。

在我的场景中是否有我遗漏或未正确执行的内容?我知道它有点涉及接口和抽象类,但我正在使用预定义的实现,因此更改这些底层/基本类不是一种选择。提前谢谢你。

  • 洛伦兹

更新: 我尝试了一些建议,但无济于事。所以我的问题是:我是否可以通过将 bindingSource1.DataSource 设置为 MyRecordOfFields.MyMembers 属性中的 MyElementLIst 来公开 MyField 的继承属性(就像我设计的 datagridview [列])?

这是我需要一些指导的地方:MyElement 是一个接口; MyElementAdapter 是从 MyElement 派生的抽象; MyElementList 是 MyElement 的通用列表; MyField 是从 MyElementAdapter 派生的。将 MyElement 或任何派生类(即 MyField)添加到 MyElementList 是可行的。然而,我想在 UI 中公开这些派生类的属性(datagridview、控件等)。

更新#2: 现在我的 datagridview 绑定到 MyElementList 一切正常。我现在正在添加/删除/更新 myLocalFields.MyMembers 列表。我添加了一个带有以下代码的按钮:

private void AddItem_Click(object sender, EventArgs e) {
    MyField field1 = new MyField(null);
    field1.MyFieldName = "Field_" + myLocalFields.MyMembers.Count + 1;
    field1.MyFieldDescription = "Description" + myLocalFields.MyMembers.Count + 1;
    field1.Size = myLocalFields.MyMembers.Count + 1;
    myLocalFields.MyMembers.Add(field1);
    ((BindingSource)dataGridView1.DataSource).ResetBindings(false);
}

完成此方法后,我看到我的基础数据 (myLocalField.MyMembers) 发生了变化,但 bindingSource1 和 dataGridView1 没有。过去,对底层数据的任何更新都是对底层数据和绑定源进行的——我想对底层数据进行更改并将其传播到绑定源/datagridview。

我认为以下内容会处理对 datagridview 的任何更改,但事实并非如此:

    ((BindingSource)dataGridView1.DataSource).ResetBindings(false);

是否需要使用另一种方法来更新 datagridview 而无需添加额外的 datagridview 事件?

【问题讨论】:

  • “Pubic”成员总是很难相处。
  • 您说一切都可以编译并运行,但您发布的代码存在大量编译错误。您没有显示真实代码是有原因的吗?
  • 我在一个预先建立的项目中工作,无法更改底层应用程序框架,也无法发布实际代码。我发布的代码与我正在使用的代码非常匹配。我很抱歉没有发布整个代码 - 这会很激烈。

标签: .net winforms data-binding datagridview bindingsource


【解决方案1】:

首先:

  • 请考虑接受您的问题的答案。
  • 请发布可编译的代码。

真的应该在你的界面前加上I。它使您的代码更易于解释。

您将数据源设置为List&lt;MyElement&gt;。这意味着它可以绑定的可用属性是NameDescription

public interface MyElement : IEditableObject { 
    public string Name { get; set; } 
    public string Description { get; set; } 
} 

但您手动创建绑定到名为MyFieldNameMyFieldDescriptionSize 的属性的DataGridView 列。数据绑定器无法将您告诉它的期望值与您给它的值匹配。

执行此操作时,无需先设置数据源:

foreach (MyElement me in myLocalFields.MyMembers) {         
            bindingSource1.Add(me);         
        }         

我相信您添加的第一个对象是建立绑定的对象,在本例中是 MyField,它具有您的 DataGridView 所期望的属性。

您有多种选择。以下是其中一些,排名不分先后。

选项1。使用AutoGenerateColumns = true,并删除所有手动创建列的代码。

选项 2。 创建列时,使用 MyElement 属性名称:

int colIndex = dgv.Columns.Add("MyFieldName", "MyFieldName");  
dgv.Columns[colIndex].DataPropertyName = "Name";  
colIndex = dgv.Columns.Add("MyFieldDescription", "MyFieldDescription");  
dgv.Columns[colIndex].DataPropertyName = "Description";  

选项 3. 将对象转换为 MyField,以便 BindingSource 为您告诉网格期望的属性生成绑定:

而不是这个:

bindingSource1.DataSource = myLocalFields.MyMembers;

这样做:

bindingSource1.DataSource = myLocalFields.MyMembers.Cast<MyField>();

希望您能在这里看到主题。关键是您当前正在将显式绑定的列与自动创建的绑定相结合,而自动绑定不是您的列设置的目的。我给你们的选项都以不同的方式解决了这个问题。

【讨论】:

  • 感谢您对代码的建议和您的回答。我已经应用了这些答案,但我仍然没有公开 MyField 属性的有界数据网格视图。
  • 我已经编辑了我的答案并给出了三​​个不同的选项,我保证它们将根据您发布的示例代码起作用。如果这对您有帮助,请投票并接受答案,我建议您对其他一些问题也这样做,因为如果人们认为您永远不会接受/投票,他们不太愿意提供帮助。
  • 选项 3 对我有用!感谢 Igby 的建议和帮助。
  • 最后一个问题是:在我当前的解决方案(它有一个预定义的框架)中,我在 MyElementList 的智能中没有看到 Cast() 方法。但是,在此处提供的代码中,Case() 方法是一个选项。我已经“完全”构建了示例代码,因为它在我的解决方案中是结构化的。我是否缺少与通用列表实现相关的概念?有一个 ConvertAll() 方法,我试过了,它可以工作。不知道为什么...?
  • Cast() 是 LINQ 的一部分;它是在 Enumerable 类上定义的扩展方法。您需要将 using System.Linq; 添加到文件中,然后 Intellisense 会显示它。
猜你喜欢
  • 2017-05-18
  • 2010-09-12
  • 2013-08-03
  • 1970-01-01
  • 2011-07-19
  • 1970-01-01
  • 2013-05-17
  • 2014-05-12
  • 2013-10-24
相关资源
最近更新 更多