将DataGrid绑定到ObservableCollection<Dictionary>

12

我有一个ObservableCollection<Dictionary>,想要将其绑定到一个DataGrid上。

ObservableDictionary<String,Object> NewRecord1 = new ObservableDictionary<string,object>();

Dictionary<String,Object> Record1 = new Dictionary<string,object>();
Record1.Add("FirstName", "FName1");
Record1.Add("LastName", "LName1");
Record1.Add("Age", "32");

DictRecords.Add(Record1);

Dictionary<String, Object> Record2 = new Dictionary<string, object>();
NewRecord2.Add("FirstName", "FName2");
NewRecord2.Add("LastName", "LName2");
NewRecord2.Add("Age", "42");

DictRecords.Add(Record2);

我希望把 DataGrid 的表头设为键,每个 Dictionary 项的值作为行。但是设置 ItemsSource 无效。

Translated:

我想将DataGrid的标题设置为键,并且将每个Dictionary项的值设置为行。设置ItemsSource不起作用。


1
DataGrid 简单地不支持这个。如果你需要动态列,还有其他的方法。 - H H
@HenkHolterman 我确实需要动态列。你能给我指出其他做法吗? - Manoj
在你的例子中,看起来你正在将人员添加到网格中。我猜你需要动态列是因为你将来需要在同一个网格中显示其他东西而不仅仅是人员?如果是这样的话,是否可以创建一个Person类,并为所有你需要显示的其他项目创建类(而不是使用Dictionary<>)? - Sphinxxx
@Sphinxxx 是的,你说得对。实际上这将是测试结果 - 多个测试按顺序运行,当前正在运行的测试结果将显示在数据网格中。我考虑为每个测试结果添加一个结果类。我困惑的是如何根据正在运行的测试更新数据绑定。我不想在视图中添加选择语句以选择要绑定的适当对象 - 因为这将要求我每次添加新测试时都要更新它。我认为这不是一个好的设计。有更好的处理方法吗? - Manoj
你真的需要为不同的测试编写不同的类吗?你能否通过添加这样一个类的示例来更新你的帖子? - Sphinxxx
@Sphinxxx 是的,不同类型测试生成的数据将是特定于该特定测试类型的唯一数据。我们有一个开/关测试,它测量设备达到可用状态所需的时间 - 在许多周期内进行测量。然后我们测试设备的功能,根据正在测试的功能,测量完全不同的特征。 - Manoj
2个回答

24
你可以使用一个可绑定的动态字典。这将把每个字典条目公开为一个属性。
/// <summary>
/// Bindable dynamic dictionary.
/// </summary>
public sealed class BindableDynamicDictionary : DynamicObject, INotifyPropertyChanged
{
    /// <summary>
    /// The internal dictionary.
    /// </summary>
    private readonly Dictionary<string, object> _dictionary;

    /// <summary>
    /// Creates a new BindableDynamicDictionary with an empty internal dictionary.
    /// </summary>
    public BindableDynamicDictionary()
    {
        _dictionary = new Dictionary<string, object>();
    }

    /// <summary>
    /// Copies the contents of the given dictionary to initilize the internal dictionary.
    /// </summary>
    /// <param name="source"></param>
    public BindableDynamicDictionary(IDictionary<string, object> source)
    {
        _dictionary = new Dictionary<string, object>(source);
    }
    /// <summary>
    /// You can still use this as a dictionary.
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public object this[string key]
    {
        get
        {
            return _dictionary[key];
        }
        set
        {
            _dictionary[key] = value;
            RaisePropertyChanged(key);
        }
    }

    /// <summary>
    /// This allows you to get properties dynamically.
    /// </summary>
    /// <param name="binder"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        return _dictionary.TryGetValue(binder.Name, out result);
    }

    /// <summary>
    /// This allows you to set properties dynamically.
    /// </summary>
    /// <param name="binder"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        _dictionary[binder.Name] = value;
        RaisePropertyChanged(binder.Name);
        return true;
    }

    /// <summary>
    /// This is used to list the current dynamic members.
    /// </summary>
    /// <returns></returns>
    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return _dictionary.Keys;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        var propChange = PropertyChanged;
        if (propChange == null) return;
        propChange(this, new PropertyChangedEventArgs(propertyName));
    }
}

然后你可以像这样使用它:

    private void testButton1_Click(object sender, RoutedEventArgs e)
    {
        // Creating a dynamic dictionary.
        var dd = new BindableDynamicDictionary();

        //access like any dictionary
        dd["Age"] = 32;

        //or as a dynamic
        dynamic person = dd;

        // Adding new dynamic properties.  
        // The TrySetMember method is called.
        person.FirstName = "Alan";
        person.LastName = "Evans";

        //hacky for short example, should have a view model and use datacontext
        var collection = new ObservableCollection<object>();
        collection.Add(person);
        dataGrid1.ItemsSource = collection;
    }

Datagrid需要定制代码来创建列:

XAML:

<DataGrid AutoGenerateColumns="True" Name="dataGrid1" AutoGeneratedColumns="dataGrid1_AutoGeneratedColumns" />

自动生成列事件:

    private void dataGrid1_AutoGeneratedColumns(object sender, EventArgs e)
    {
        var dg = sender as DataGrid;
        var first = dg.ItemsSource.Cast<object>().FirstOrDefault() as DynamicObject;
        if (first == null) return;
        var names = first.GetDynamicMemberNames();
        foreach(var name in names)
        {
            dg.Columns.Add(new DataGridTextColumn { Header = name, Binding = new Binding(name) });            
        }            
    }

1
BindableDynamicDictionary非常适合我的需求。感谢您提供带注释的示例。 - Manoj
为什么在 RaisePropertyChanged 中使用本地变量 propChange,以及在 dataGrid1_AutoGeneratedColumns() 中使用 names - Sinatr
@Sinatr,它们并没有必要。我认为当时我以为它们会使代码更易读。 - weston
@weston 我正在尝试按照您提供的相同步骤进行操作,但我的数据网格没有显示数据,只显示列,并且还显示了一个名为“Items”的额外列,您有什么想法我可能做错了什么? - user3340627
@user3340627 不好意思,您可以提出问题。 - weston
显示剩余2条评论

1

基于Weston的答案,我想出了另一个解决方案,而不使用自定义的BindableDynamicDictionary类。

有一个名为ExpandoObject的类位于System.Dynamic命名空间中(在ASP.NET中广泛使用)。

它基本上与Weston的BindableDynamicDictionary做相同的事情,但缺点是没有索引运算符可用,因为它显式实现了接口IDictionary<string, object>

private void MyDataGrid_AutoGeneratedColumns(object sender, EventArgs e)
{
  var dg = sender as DataGrid;
  dg.Columns.Clear();
  var first = dg.ItemsSource.Cast<object>().FirstOrDefault() as IDictionary<string, object>;
  if (first == null) return;
  var names = first.Keys;
  foreach (var name in names)
  {
    dg.Columns.Add(new DataGridTextColumn { Header = name, Binding = new Binding(name) });
  }
}

注意,这里唯一的区别就是你需要将ExpandoObject强制转换为IDictionary<string, object>才能通过索引操作符访问/添加值或属性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接