Binding Datagrid to Observablecollection<Dictionary>

Binding DataGrid to ObservableCollectionDictionary

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:

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

How do I bind an ObservableCollection of Dictionaries to DataGrid?

Based on BionicCode's comment it seems that I tried to reinvent the wheel by creating DataTable, DataColumn, DataRow and other classes.

I removed my classes and based my code around System.Data classes for tables.

Besides, DataTable perfectly binds to a DataGrid and it solves all my issues.

C# WPF ObservableCollection with dictionary inside to MVVM Datagrid

Use

    <DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!-- Here you can add items control or whatever and bound its ItemSource to your
Stocks -->
</DataTemplate>
</DataGridTemplateColumn>
</DataGridTemplateColumn.CellTemplate>

But I don't recommend you to use Dictionary. Make ObservableCollection instead. It's pretty hard to Bind Dictionary<key, val> with observe on Items changed.

Databinding ObservableDictionary to DataGrid not working

When you enumerate a Dictionary<string, string>, you get an enumeration of KeyValuePair<String, String>. That's right there in your ObservableDictionary class:

public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return Dictionary.GetEnumerator();
}

So you're binding DataGrid.ItemsSource to Categories:

    ItemsSource="{Binding Categories}" 

The DataGrid says "here's an IEnumerable of some random thing. Let's enumerate it and use one item for each row".

Therefore, your grid row items are KeyValuePair<String, String>. That has two readonly properties: Key and Value. You're binding to Keys in your column: Binding="{Binding Keys}". No such property. When a binding fails, or appears to fail, check out the Output pane in Visual Studio at runtime.

Here's the error you were getting in the Output pane:

System.Windows.Data Error: 40 : BindingExpression path error: 'Keys' property not found on 'object' ''KeyValuePair`2' (HashCode=-1894483822)'. BindingExpression:Path=Keys; DataItem='KeyValuePair`2' (HashCode=-1894483822); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

It's telling you that it's trying to find Keys on KeyValuePair, with no success. It's simplicity itself for you to go look at KeyValuePair, find out what it does have, and bind to that instead.

Finally: If the user double clicks on a cell, the DataGrid will try to edit the cell. But Key and Value are read only, so it'll throw an exception. I'm making your grid readonly to prevent that from happening. I also cleaned up the element style, to save on copying and pasting with multiple columns.

<DataGrid 
Name="testGrid"
ItemsSource="{Binding Categories}"
Margin="21,22,370,50"
AutoGenerateColumns="False"
IsReadOnly="True"
>
<DataGrid.Resources>
<Style TargetType="TextBlock" x:Key="TextElementStyle">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn
Header="Name"
Width="220"
Binding="{Binding Key}"
ElementStyle="{StaticResource TextElementStyle}"
/>
<DataGridTextColumn
Header="Value"
Width="220"
Binding="{Binding Value}"
ElementStyle="{StaticResource TextElementStyle}"
/>
</DataGrid.Columns>
</DataGrid>

Note that if you replace _categories with a new instance of ObservableDictionary, the UI will never know about it. It'll keep the old dictionary, because you are not raising any change notification when the value of Categories changes. That's fine if you don't need to, but be aware of it. A better way to code that would be this, which prevents that mistake from being made:

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();

DataContext = this;

Categories.Add("Island", "IS");
Categories.Add("Land", "LD");
Categories.Add("Sea", "SE");
}

public ObservableDictionary<string, string> Categories { get; }
= new ObservableDictionary<string, string>();
}

You're going to run into problems using margins for layout. It's much better to use Grid columns, StackPanels, etc. and use margins only for defining relative spacing between elements in a Grid or StackPanel. It becomes relatively simple to create dynamically resizable UIs, and to adjust the layout: If you want to make something wider, just set a Width on it and the rest of the layout will adjust, because it's relative instead of fixed.

Using ObservableCollectionDictionaryString, Object as DataGrid Itemsource in WPF

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

Binding DataGridComboBoxColumn to a Dictionary

But I'm currently not able to map the DataGridComboBoxColumn using the DataType of the object contained into the ListView item

You can't do this in XAML. XAML is a markup language and won't be able to resolve the type of the underlying object in the ItemsSource of the ListView using [DataType]. This is not supported.

You could use a multi value converter that takes the Dictionary<string, ObservableCollection<string>> and the data object and returns the correct ObservableCollection<string> based on the type of the object:

<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource">
<Setter.Value>
<MultiBinding Converter="{StaticResource conv}">
<Binding Path="DataContext.WhereFieldConditions" RelativeSource="{RelativeSource AncestorType={x:Type Window}}" />
<Binding Path="DataContext" RelativeSource="{RelativeSource AncestorType=ListViewItem}" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>

DataGrid Column Binding with Dictionary

From my comment: try dgtc2.Binding = new Binding(@"ResourceDic2[Name_100].Name");



Related Topics



Leave a reply



Submit