WPF:在MVVM中创建绑定到未知类型的最佳方法

时间:2013-10-30 21:20:32

标签: c# wpf mvvm datagrid

我正在寻找一种在编译时未知类型的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; }
}

然后我从DB

中检索他们的实例列表
public IEnumerable<Entity> Entities { get; set; } // A list of instances of type A for example

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

澄清

  1. 我只需要一次显示一个类型实例的一个网格。完整的方案是:

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

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

3 个答案:

答案 0 :(得分:5)

在这种情况下,DataGrid似乎不合适。如果你的列表被绑定到两个独立的实体,它就会破坏。

更好的选择可能是使用其他ItemsControl并为每种DataTemplate设置Entity。这将允许您为每个实体构建自定义编辑器,并有一个&#34;列表&#34;他们要编辑。

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

答案 1 :(得分:4)

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

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()new B()(或任何现有的A和B实例),因此selectedItem可用于以下语句中。

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

how to write DataGridColumnTemplate in code


编辑:

会员不能在这种情况下使用,因为INotifyPropertyChanged应该参与其中,所以我用属性替换了成员。

答案 2 :(得分: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类的了解。