wpf 将 DataGrid 绑定到 ObservableCollection<Dictionary>

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14171098/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 06:54:35  来源:igfitidea点击:

Binding DataGrid to ObservableCollection<Dictionary>

c#wpfdata-bindingdictionary

提问by Manoj

I have a ObservableCollection<Dictionary>and want to bind it to a DataGrid.

我有 aObservableCollection<Dictionary>并且想将它绑定到 a 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);

I wanted the keys to become the header of the DataGridand the values of each Dictionaryitem to be the rows. Setting the ItemsSourcedoes not work.

我希望键成为标题DataGrid,每个Dictionary项目的值成为行。设置ItemsSource不起作用。

回答by weston

You could use a bindable dynamic dictionary. This will expose each dictionary entry as a property.

您可以使用可绑定的动态字典。这会将每个字典条目公开为一个属性。

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

Then you can use it like this:

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

    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 needs custom code for building the columns up:

Datagrid 需要自定义代码来构建列:

XAML:

XAML:

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

AutoGeneratedColumns event:

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

回答by Thomas Eggemann

Based on westons answer i came up with another solution without using a custom BindableDynamicDictionary class.

根据威斯顿的回答,我想出了另一种解决方案,而不使用自定义的 BindableDynamicDictionary 类。

There is a class called ExpandoObjectin the namespace System.Dynamic(which is heavily used in ASP.NET).

ExpandoObject在命名空间中调用了一个类System.Dynamic(在 ASP.NET 中大量使用)。

It basically does the same thing as westons BindableDynamicDictionary with the drawback of not having the index operator available since it explicitly implements the interface IDictionary<string, object>

它基本上与 westons 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) });
  }
}

Notice that the only difference here is that you have to cast the ExpandoObjectto IDictionary<string, object>to access/add values or properties via the index operator.

请注意,这里唯一的区别是您必须将 to 强制ExpandoObject转换IDictionary<string, object>为通过索引运算符访问/添加值或属性。