سؤال

أحاول عرض نتائج الاستعلام في بيانات WPF. نوع heatssource الذي أنا ملزم به 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. قم بإعداد كائن ربط البيانات

بعد ذلك ، بدأ ربط البيانات المدمجة وعمل بشكل جيد ولا يبدو أن لديه أي مشكلة في إخراج قيم الممتلكات من 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 ستقوم بإنشاء أعمدة لـ ExployOoBject نفسها - ولكن ليس هناك ما يضمن أن مجموعة من compects المتوسطة تشترك في نفس الخصائص بين بعضها البعض ، ولا توجد قاعدة للمحرك لمعرفة الأعمدة التي يجب إنشاؤها.

ربما سيعمل شيء مثل LINQ المجهول أن يكون أفضل بالنسبة لك. لا أعرف نوع بيانات البيانات التي تستخدمها ، ولكن يجب أن يكون الربط متطابقًا معهم جميعًا. فيما يلي مثال بسيط لـ Telerik DataGrid.
رابط إلى منتديات Telerik

هذا ليس ديناميكيًا حقًا ، يجب أن تكون الأنواع معروفة في وقت الترجمة - ولكن هذه طريقة سهلة لوضع شيء مثل هذا في وقت التشغيل.

إذا لم يكن لديك أي فكرة حقًا عن نوع الحقول التي ستعرضها ، فستحصل على المزيد من الشعر. الحلول الممكنة هي:

باستخدام Dynamic LINQ ، يمكنك إنشاء أنواع مجهولة باستخدام سلسلة في وقت التشغيل - والتي يمكنك تجميعها من نتائج استعلامك. مثال الاستخدام من الرابط الثاني:

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

في أي حال ، فإن الفكرة الأساسية هي تعيين العنصر بطريقة ما على مجموعة من الكائنات التي مشترك يمكن العثور على الخصائص العامة عن طريق التفكير.

إجابتي من ربط العمود الديناميكي في 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

على الرغم من وجود إجابة مقبولة من قبل البروتوكول الاختياري ، إلا أنها تستخدم 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