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

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. 通过反射从插件DLL中获取派生自Entity的类型列表
    2. 在列表中显示它们的名称。 (在此示例中,该列表将包含AB
    3. 当用户单击特定项时,例如A,我从DB获取A实例列表-到目前为止都很好。
    4. 我想在DataGrid中显示A实例的列表。
    5. 当用户选择列表中的另一个项目(意味着另一种类型,比如B)时,我从DB获取B的实例列表,并需要在网格中显示它们,以此类推...
  2. 插件DLL是没有xamls的类库(也是我的用户制作这些插件的人,我不希望他们必须为其实体编写DataTemplate。 我还不能制作预定义的DataTemplate,因为在运行时之前我不知道需要显示的类型。每种类型都可以具有不同类型和数量的属性。在编译时,我只知道它们都派生自Entity

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

为每个实体类型创建特定的DataGrid,并使用ContentPresenter在运行时切换视图。保持简单。 - Federico Berasategui
我不确定您是否需要更新从Entity派生的类型中的属性,以及这些更新是否需要在ItemsControl中显示。如果需要,请实现INotifyPropertyChanged更改通知来更新这些属性,或将它们转换为Dependency属性。 - user2819245
如果您确实希望它们同时出现在一个网格中,则可以将您的视图模型属性设置为IEnumerable<dynamic>类型。 - Millie Smith
1
为什么不将AutoGenerateColumns属性设置为True,让datagrid根据对象公开的属性为您创建列呢? - Rohit Vats
@RohitVats 因为实体可能包含包含自己属性的复杂属性,我可能想要绑定例如 Employee.Devision.IsNew,而且我不能简单地覆盖 Devision 的 ToString 方法,因为我希望该列是一个复选框,因为 IsNew 属性是布尔值... - Omri Btian
显示剩余7条评论
3个回答

5
在这种情况下,DataGrid似乎不太合适。如果您的列表绑定到两个不同的实体,则会出现严重错误。
更好的选择可能是使用其他某些ItemsControl,并为每种类型的Entity设置一个DataTemplate。这将使您能够针对每个实体构建自定义编辑器,并拥有一个“列表”来进行编辑。
如果您知道实体始终为单一类型,那么我会创建该特定类型的集合,并进行绑定。

谢谢回答。我对问题进行了一些更新,您能帮忙看一下吗?到目前为止,我想到的最好的方法是通过反射迭代类型的属性并编程方式创建它的列。有更简单的方法吗? - Omri Btian
@Omribitan 有一个固定的类型 - 即:绑定到 ObservableCollection<ConcreteEntityType> 而不是 OC<Entity>,然后为每个类型构建数据模板... 这样就不需要反射了。 - Reed Copsey
1
这种方法对于OP来说有些不利,因为用户正在创建程序集中的类型。创建模板应该由用户自己决定(但OP已经表示他们不希望用户这样做),或者应该使用动态网格。所有这些都可以通过编程绑定/列来实现。 - Charleh

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的ItemsSource,它应该可以正常工作。selectedItem应该是从Entity派生的类之一的实例。列表框包含new A()new B()(或任何现有的A和B的实例),因此selectedItem可以在以下语句中使用。

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

如何在代码中编写DataGridColumnTemplate


编辑:

在这种情况下不能使用成员(Member),因为需要涉及到INotifyPropertyChanged,所以我用属性(Properties)代替了成员。


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,并将其用于所有可能实体的所有属性。Entity Object将包含其他方法来返回与其相关的所有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类的了解。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接