我正在寻找一种在编译时未知类型的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
...
我只需要一次显示一个类型实例的一个网格。完整的方案是:
Entity
派生的类型列表A
和B
A
,我从数据库中获取A
个实例列表 - 到目前为止一直很好。A
中显示 DataGrid
个实例列表。B
)时,我从DB获取B
个实例的列表,需要在网格中显示这些实例等...... 插件DLL是一个没有xamls的类库(我的用户也是制作此插件的用户,我不希望他们必须为他们的实体编写DataTemplate
个。
我也不能预测DataTemplate
,因为我不知道我需要在运行时显示的类型。每种类型都可以具有不同类型和数量的属性。我在complie-time中所知道的是,它们都来自Entity
。
答案 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类的了解。