2

I have a DataGrid and I set ItemsSource is a ObservableCollection<Dictionary<String, object>> object.

Usually, I just define a ClassA and set ObservableCollection<ClassA> to ItemsSource, then I can binding properties name to columns(DataGridTextColumn) of ClassA.

But I don't know how to binding a key/value of a Dictionary.

Have any support?

Noam M
  • 3,156
  • 5
  • 26
  • 41
Po-Sen Huang
  • 445
  • 6
  • 24
  • You must put back ticks around generics with angle brackets, or the browser will think they are tags and swallow them. Please read your questions after you submit, so you will find errors like that. Your question made no sense until I fixed it for you. – 15ee8f99-57ff-4f92-890c-b56153 Jun 21 '17 at 02:48

1 Answers1

4

What youre asking is rather complex, in order to create ObservableDictionary<TKey, TValue> one should create a class that implements:

IDictionary
INotifyCollectionChanged
INotifyPropertyChanged 
ICollection<KeyValuePair<TKey,TValue>>
IEnumerable<KeyValuePair<TKey,TValue>>
IEnumerable

interfaces. More in depth in here. An example of such implemntion is:

class ObservableDictionary<TKey, TValue> : IDictionary, INotifyCollectionChanged, INotifyPropertyChanged
{
    private Dictionary<TKey, TValue> mDictionary;

    //Methods & Properties for IDictionary implementation would defer to mDictionary:
    public void Add(TKey key, TValue value)
    {
        mDictionary.Add(key, value);
        OnCollectionChanged(NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value)
        return;
    }

    //Implementation of INotifyCollectionChanged:
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    protected void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
    {
        //event fire implementation
    }

    //Implementation of INotifyProperyChanged:
    public event ProperyChangedEventHandler ProperyChanged;
    protected void OnPropertyChanged(PropertyChangedEventArgs args)
    {
        //event fire implementation
    }
}

An alterntive is this nice solution of a bindable dynamic dictionary, that expose each dictionary entry as a property.

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>
    public BindableDynamicDictionary(IDictionary<string, object> source)
    {
        _dictionary = new Dictionary<string, object>(source);
    }

    /// <summary>
    /// You can still use this as a dictionary.
    /// </summary>
    public object this[string key]
    {
        get { return _dictionary[key]; }
        set
        {
            _dictionary[key] = value;
            RaisePropertyChanged(key);
        }
    }

    /// <summary>
    /// This allows you to get properties dynamically.
    /// </summary>
    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>
    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>
    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)
{
    var dd = new BindableDynamicDictionary(); // Creating a dynamic dictionary.
    dd["Age"] = 32; //access like any dictionary

    dynamic person = dd; //or as a dynamic
    person.FirstName = "Alan"; // Adding new dynamic properties. The TrySetMember method is called.
    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:

XAML:

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

AutoGeneratedColumns event:

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) });            
    }            
}
Noam M
  • 3,156
  • 5
  • 26
  • 41