Подтвердить что ты не робот

Как динамически создавать столбцы в DataGrid WPF?

Я пытаюсь отобразить результаты запроса в datagrid WPF. Тип ItemsSource, к которому я привязан, равен IEnumerable<dynamic>. Поскольку возвращаемые поля не определены до времени выполнения, я не знаю тип данных до тех пор, пока запрос не будет оценен. Каждая "строка" возвращается как ExpandoObject с динамическими свойствами, представляющими поля.

Я надеялся, что AutoGenerateColumns (как показано ниже) сможет генерировать столбцы из ExpandoObject, как это делается со статическим типом, но он не отображается.

<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Results}"/>

Есть ли способ сделать это декларативно или мне нужно с уверенностью подключиться к некоторым С#?

ИЗМЕНИТЬ

Хорошо, это даст мне правильные столбцы:

// ExpandoObject implements IDictionary<string,object> 
IEnumerable<IDictionary<string, object>> rows = dataGrid1.ItemsSource.OfType<IDictionary<string, object>>();
IEnumerable<string> columns = rows.SelectMany(d => d.Keys).Distinct(StringComparer.OrdinalIgnoreCase);
foreach (string s in columns)
    dataGrid1.Columns.Add(new DataGridTextColumn { Header = s });

Итак, теперь просто нужно выяснить, как привязать столбцы к значениям IDictionary.

4b9b3361

Ответ 1

В конечном счете мне нужно было сделать две вещи:

  • Генерировать столбцы вручную из списка свойств, возвращаемых запросом
  • Настроить объект DataBinding

После этого встроенная привязка данных ударила и работала нормально и, похоже, не вызывала проблем с получением значений свойств из ExpandoObject.

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Results}" />

и

// Since there is no guarantee that all the ExpandoObjects have the 
// same set of properties, get the complete list of distinct property names
// - this represents the list of columns
var rows = dataGrid1.ItemsSource.OfType<IDictionary<string, object>>();
var columns = rows.SelectMany(d => d.Keys).Distinct(StringComparer.OrdinalIgnoreCase);

foreach (string text in columns)
{
    // now set up a column and binding for each property
    var column = new DataGridTextColumn 
    {
        Header = text,
        Binding = new Binding(text)
    };

    dataGrid1.Columns.Add(column);
}

Ответ 2

Проблема заключается в том, что clr будет создавать столбцы для самого ExpandoObject, но нет гарантии, что группа ExpandoObjects имеет одни и те же свойства друг с другом, а не правила для того, чтобы движок знал, какие столбцы должны быть созданы.

Возможно, что-то вроде анонимных типов Linq будет работать лучше для вас. Я не знаю, какой тип данных вы используете, но привязка должна быть одинаковой для всех из них. Вот простой пример для telatik datagrid.
ссылка на форумы telerik

Это не действительно действительно динамично, типы должны быть известны во время компиляции, но это простой способ установить что-то подобное во время выполнения.

Если вы действительно не знаете, какие поля вы будете отображать, проблема становится немного более волосатой. Возможные решения:

С динамическим linq вы можете создавать анонимные типы, используя строку во время выполнения, которую вы можете собрать из результатов вашего запроса. Пример использования со второй ссылки:

var orders = db.Orders.Where("OrderDate > @0", DateTime.Now.AddDays(-30)).Select("new(OrderID, OrderDate)");

В любом случае основная идея состоит в том, чтобы каким-то образом установить itemgrid в коллекцию объектов, общедоступные свойства которых shared могут быть найдены путем отражения.

Ответ 3

мой ответ из Динамическое связывание столбцов в Xaml

Я использовал подход, который следует за шаблоном этого псевдокода

columns = New DynamicTypeColumnList()
columns.Add(New DynamicTypeColumn("Name", GetType(String)))
dynamicType = DynamicTypeHelper.GetDynamicType(columns)

DynamicTypeHelper.GetDynamicType() генерирует тип с простыми свойствами. См. этот пост для получения подробной информации о том, как сгенерировать такой тип

Затем, чтобы фактически использовать тип, сделайте что-то вроде этого

Dim rows as List(Of DynamicItem)
Dim row As DynamicItem = CType(Activator.CreateInstance(dynamicType), DynamicItem)
row("Name") = "Foo"
rows.Add(row)
dataGrid.DataContext = rows

Ответ 4

Несмотря на то, что OP принимает принятый ответ, он использует AutoGenerateColumns="False", что не совсем то, что задал первоначальный вопрос. К счастью, его можно решить с помощью автогенерированных столбцов. Ключом к решению является DynamicObject, который может иметь как статические, так и динамические свойства:

public class MyObject : DynamicObject, ICustomTypeDescriptor {
  // The object can have "normal", usual properties if you need them:
  public string Property1 { get; set; }
  public int Property2 { get; set; }

  public MyObject() {
  }

  public override IEnumerable<string> GetDynamicMemberNames() {
    // in addition to the "normal" properties above,
    // the object can have some dynamically generated properties
    // whose list we return here:
    return list_of_dynamic_property_names;
  }

  public override bool TryGetMember(GetMemberBinder binder, out object result) {
    // for each dynamic property, we need to look up the actual value when asked:
    if (<binder.Name is a correct name for your dynamic property>) {
      result = <whatever data binder.Name means>
      return true;
    }
    else {
      result = null;
      return false;
    }
  }

  public override bool TrySetMember(SetMemberBinder binder, object value) {
    // for each dynamic property, we need to store the actual value when asked:
    if (<binder.Name is a correct name for your dynamic property>) {
      <whatever storage binder.Name means> = value;
      return true;
    }
    else
      return false;
  }

  public PropertyDescriptorCollection GetProperties() {
    // This is where we assemble *all* properties:
    var collection = new List<PropertyDescriptor>();
    // here, we list all "standard" properties first:
    foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(this, true))
      collection.Add(property);
    // and dynamic ones second:
    foreach (string name in GetDynamicMemberNames())
      collection.Add(new CustomPropertyDescriptor(name, typeof(property_type), typeof(MyObject)));
    return new PropertyDescriptorCollection(collection.ToArray());
  }

  public PropertyDescriptorCollection GetProperties(Attribute[] attributes) => TypeDescriptor.GetProperties(this, attributes, true);
  public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(this, true);
  public string GetClassName() => TypeDescriptor.GetClassName(this, true);
  public string GetComponentName() => TypeDescriptor.GetComponentName(this, true);
  public TypeConverter GetConverter() => TypeDescriptor.GetConverter(this, true);
  public EventDescriptor GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(this, true);
  public PropertyDescriptor GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(this, true);
  public object GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(this, editorBaseType, true);
  public EventDescriptorCollection GetEvents() => TypeDescriptor.GetEvents(this, true);
  public EventDescriptorCollection GetEvents(Attribute[] attributes) => TypeDescriptor.GetEvents(this, attributes, true);
  public object GetPropertyOwner(PropertyDescriptor pd) => this;
}

Для реализации ICustomTypeDescriptor вы можете в основном использовать статические функции TypeDescriptor тривиально. GetProperties() - это тот, который требует реальной реализации: чтение существующих свойств и добавление динамических.

Поскольку PropertyDescriptor является абстрактным, вы должны наследовать его:

public class CustomPropertyDescriptor : PropertyDescriptor {
  private Type componentType;

  public CustomPropertyDescriptor(string propertyName, Type componentType)
    : base(propertyName, new Attribute[] { }) {
    this.componentType = componentType;
  }

  public CustomPropertyDescriptor(string propertyName, Type componentType, Attribute[] attrs)
    : base(propertyName, attrs) {
    this.componentType = componentType;
  }

  public override bool IsReadOnly => false;

  public override Type ComponentType => componentType;
  public override Type PropertyType => typeof(property_type);

  public override bool CanResetValue(object component) => true;
  public override void ResetValue(object component) => SetValue(component, null);

  public override bool ShouldSerializeValue(object component) => true;

  public override object GetValue(object component) {
    return ...;
  }

  public override void SetValue(object component, object value) {
    ...
  }