8

我正在寻找一种DataGrid在编译时以未知类型显示数据的方法。

我有以下基类

public abstract class Entity
{
    // Some implementation of methods ...
}

在运行时,我加载一个插件 DLL 并使用反射来获取从Entity. 例如:

public class A : Entity
{
    public LocalAddress Address{ get; set; }
}

public class B : Entity
{
    public Vendor Vendor { get; set; }

    public string Name { get; set; }
}

然后我从数据库中检索他们的实例列表

public IEnumerable<Entity> Entities { get; set; } // A list of instances of type A for example

Entities是 DataGrid 的ItemsSource,但是我可以将属性绑定到的最佳方法是DataGrid什么?由于属性可能很复杂,我还需要能够绑定到特定路径,例如Address.HomeNum...

澄清

  1. 我一次只需要显示一个类型实例的一个网格。完整的场景是这样的:

    1. Entity我通过反射获得了从插件 DLL派生的类型列表
    2. 我在列表中显示他们的名字。(在此示例中,该列表将包含AB
    3. 当用户单击特定项目时,比如说A,我A从数据库中获得了一个实例列表 - 到目前为止一切都很好。
    4. 想在. _ADataGrid
    5. 当用户从列表中选择另一个项目(意思是另一种类型,可以说B)时,我从数据库中获得一个B实例列表,并且需要在网格中显示这些实例等等......
  2. 插件 DLL 是一个没有 xamls 的类库(我的用户也是制作这个插件的人,我不希望他们必须DataTemplate为他们的实体编写 s。我也不能DataTemplate像我一样制作 predifned直到运行时我才知道需要显示的类型。每种类型都可以有不同的类型和数量的属性。我在编译时所知道的是它们都派生自Entity.

  3. 网格也应该是可编辑的。
4

3 回答 3

5

在这种情况下, ADataGrid似乎不合适。如果您的列表绑定到两个单独的实体,它将严重损坏。

更好的选择可能是使用其他ItemsControl的并DataTemplate为每种类型的Entity. 这将允许您为每个实体构建自定义编辑器,并拥有它们的“列表”进行编辑。

如果您知道实体将始终属于单一类型,我会改为构建该特定类型的集合并绑定到它。

于 2013-10-30T21:24:25.337 回答
4

由于您事先不知道实体的属性名称,我认为您最好的选择是将 DataGrid 保留在 Xaml 中,但将其 DataGridColumns 的定义和绑定移动到后面的代码中。

AddColumnsForProperty(PropertyInfo property, string parentPath = "")
{
     var title = property.Name;
     var path = parentPath + (parentPath=="" ? "" : ".") + property.Name;

     if(property.PropertyType == typeof(string))
     {
        var column = new DataGridTextColumn();
        column.Header = title;
        column.Binding = new Binding(path);
        dataGrid.Columns.Add(column);
     }
     else if(property.PropertyType == typeof(bool))
     {
        //use DataGridCheckBoxColumn and so on
     }
     else
     {
          //...
     }

     var properties = property.GetProperties();
     foreach(var item in properties)
     {
          AddColumnsForProperty(item, path);
     }
}

现在,如果您执行这些操作,您的 dataGrid 列将被填充。并通过在可观察集合中添加所需类型的所有实例并将其绑定到 DataGrid 的 ItemsSource 它应该可以工作。selectedItem 应该是从 Entity 派生的类之一的实例。列表框包含new A()and new B()(或 A 和 B 的任何现有实例),因此 selectedItem 可以在以下语句中使用。

var propertyList = selectedItem.GetType().GetProperties();
foreach (var property in propertyList) 
    AddColumnsForProperty(PropertyInfo property);

如何在代码中编写 DataGridColumnTemplate


编辑:

在这种情况下不能使用成员,因为应该涉及到 INotifyPropertyChanged,所以我用属性替换了成员。

于 2013-11-05T03:17:20.747 回答
0

我会使用属性来指定究竟什么是可绑定的(包括复合对象):

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public abstract class EntityAttribute : Attribute
{
    internal abstract IEnumerable<EntityColumn> GetColumns(object instance, PropertyInfo property);
}

该属性支持普通属性以及复合结构。您应该简单地继承并实现该方法。

EntityColumn 表示单个值。简化版可以这样实现:

public class EntityColumn
{
    private readonly Action<object> _setMethod;
    private readonly Func<object> _getMethod;

    public string Caption { get; private set; }

    public object Value
    {
        get { return _getMethod(); }
        set { _setMethod(value);}
    }

    internal EntityColumn(string caption, Action<object> setMethod, Func<object> getMethod)
    {
        _getMethod = getMethod;
        _setMethod = setMethod;
        Caption = caption;
    }
}

稍后,您可以为 EntityColumn 创建单个 DataTemplate 并将其用于所有可能实体的所有属性。实体对象将包含额外的方法来返回与其相关的所有 EntityColumn:

 public IList<EntityColumn> GetColumns()
    {
        var objectType = GetType();
        var properties = objectType.GetProperties();
        return properties.SelectMany(
            p => p.GetCustomAttributes<EntityAttribute>().SelectMany(a => a.GetColumns(this, p))).ToList();
    }

对于实体的集合,您可以引入 EntityCollection,它将吸收列信息并提供类似于 DataSet 的结构。此实现为您提供了动态结构的灵活性,并使几乎所有内容都保持强类型。您甚至可以扩展属性和 EntityColumn 以支持验证。

在显示对象时,您宁愿使用 ItemsControl 甚至是从 ItemsControl 继承的自写控件,以利用了解 Entity 和 EntityCollection 类的优势。

于 2013-11-08T20:33:43.207 回答