【问题标题】:WPF: Best way to create bindings to unknown types in MVVMWPF:在 MVVM 中创建与未知类型的绑定的最佳方式
【发布时间】:2013-11-10 17:18:04
【问题描述】:

我正在寻找一种在 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)时,我从数据库中获得B 的实例列表,并且需要在网格中显示这些实例等等.. .
  2. 插件 DLL 是一个没有 xamls 的类库(我的用户也是制作此插件的人,我不希望他们为自己的实体编写 DataTemplates。 我也无法预先定义DataTemplates,因为我不知道在运行时之前需要显示的类型。每种类型都可以有不同类型和数量的属性。我在编译时只知道它们都来自Entity

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

【问题讨论】:

  • 为每个实体类型创建特定的 DataGrid,并使用 ContentPresenter 在运行时切换视图。保持简单。
  • 我不确定您是否需要更新从 Entity 派生的类型中的属性,以及这些更新是否需要显示在 ItemsControl 中。如果这样做,您将需要为这些属性实现 INotifyPropertyChanged 更改通知,或者将它们转换为 Dependency 属性。
  • 如果您真的希望它们同时在一个网格中,您可以将视图模型属性设为 IEnumerable 类型。
  • @HighCore 我对这个问题做了一些更新,你能看看吗?到目前为止,我想出的最好的想法是通过反射迭代类型的属性并以编程方式创建它的列。有没有更简单的方法?
  • 为什么不将AutoGenerateColumns 属性设置为True 并让datagrid 根据您的对象公开的属性为您创建列?

标签: c# wpf mvvm datagrid


【解决方案1】:

DataGrid 在这种情况下似乎不合适。如果您的列表绑定到两个单独的实体,它会很糟糕。

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

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

【讨论】:

  • 感谢您的回答。我对这个问题做了一些更新,你可以看看吗?到目前为止,我想出的最好的想法是通过反射迭代类型的属性并以编程方式创建它的列。有没有更简单的方法?
  • @Omribitan 具有固定类型 - 即:绑定到 ObservableCollection&lt;ConcreteEntityType&gt; 而不是 OC&lt;Entity&gt;,然后为每个人构建一个数据模板......那么不需要反射。
  • 尽管用户在程序集中创建类型,但这种对 OP 的作用。创建模板应该由用户决定(OP 说过他们不希望用户必须这样做),或者应该使用动态网格。只需使用编程绑定/列,这一切都是可能的。
【解决方案2】:

由于您事先不知道实体的属性名称,我认为您最好的选择是将 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()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,所以我将成员替换为属性。

【讨论】:

    【解决方案3】:

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

    [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 类。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-07
      • 1970-01-01
      • 2011-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-01
      • 1970-01-01
      相关资源
      最近更新 更多