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

StackOverflow https://stackoverflow.com/questions/1983033

Вопрос

Я пытаюсь отобразить результаты запроса в данных WPF. ITSSOURCE TYPE I обязательно IEnumerable<dynamic>. Анкет Поскольку возвращаемые поля не определяются до тех пор, пока время выполнения я не знаю типа данных до оценки запроса. Каждая «строка» возвращается как ExpandoObject с динамическими свойствами, представляющими поля.

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

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

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

РЕДАКТИРОВАТЬ

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

// 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 });

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

Это было полезно?

Решение

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

  1. Создайте столбцы вручную из списка свойств, возвращаемых запросом
  2. Настройка объекта 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);
}

Другие советы

Проблема здесь заключается в том, что CLR создаст столбцы для самого Expandoobject - но нет никакой гарантии, что группа Expandoobjects имеет одинаковые свойства между друг другу, нет правила, чтобы двигатель знал, какие столбцы необходимо создать.

Возможно, что -то вроде анонимных типов LINQ будет лучше для вас. Я не знаю, какой тип данных вы используете, но привязка должна быть идентичной для всех из них. Вот простой пример для DataGrid Telerik.
Ссылка на форумы TeleRik

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

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

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

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

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

Мой ответ от Динамическое связывание столбца в 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

Хотя 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) {
    ...
  }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top