I have a MVVM project with a DataGrid, that uses DataTable with a dynamic number of columns.
That means DataGrid has to auto generate its columns.
I want to change one generated DataGridTextColumn ("Col B" in code, for typeof(PKValue)) to a DataGridComboBoxColumn.
But I can't get it to display column's data.
DataTable is created like this:
while (i < this.ColumnsCount1)
{
dataTable.Columns.Add(new DataColumn("Col A", typeof(bool)));
i++;
dataTable.Columns.Add(new DataColumn("Col B", typeof(PKValue)));
i++;
dataTable.Columns.Add(new DataColumn("Col C", typeof(string)));
i++;
}
IDItem class:
public class PKValue : ICloneable
{
public object Clone()
{
return this.MemberwiseClone();
}
public int PK { get; set; }
public string Value { get; set; }
}
With this code - DataGrid creates "Col B" as DataGridTextColumn.
Then I use this code, trying to change auto-generated text column to a combobox column:
protected override void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyType == typeof(PKValue))
{
MainViewModel mainViewModel = null;
UserControl userControl = FindParent<UserControl>(this) as UserControl;
Window window = FindParent<Window>(this) as Window;
if (userControl != null && userControl.DataContext.GetType().Equals(typeof(MainViewModel)))
{
mainViewModel = userControl.DataContext as MainViewModel;
}
else if (window != null && window.DataContext.GetType().Equals(typeof(MainViewModel)))
{
mainViewModel = window.DataContext as MainViewModel;
}
if (mainViewModel != null)
{
DataGridComboBoxColumn dataGridComboBoxColumn = new DataGridComboBoxColumn();
Binding bindingItemsSource = new Binding();
bindingItemsSource.Source = mainViewModel.PKValueItemsSource;
BindingOperations.SetBinding(dataGridComboBoxColumn, ItemsSourceProperty, bindingItemsSource);
Binding bindingSelectedItem = new Binding(e.PropertyName);
bindingSelectedItem.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(dataGridComboBoxColumn, SelectedItemProperty, bindingSelectedItem);
dataGridComboBoxColumn.DisplayMemberPath = "Value";
dataGridComboBoxColumn.SelectedValuePath = "PK";
e.Column = dataGridComboBoxColumn;
}
base.OnAutoGeneratingColumn(e);
}
}
DataGrid does change "Col B" to DataGridComboBoxColumn without any warnings, shows ItemsSource properly, but it doesn't display existing value of the field.
When I select an item from a dropdown - it gives a warning "Cannot create default converter to perform 'two-way' conversions between types 'WPFGridMatrix.Models.PKValue' and 'System.String'". And when I create a converter that returns a simple string "abc" - still, nothing is displayed in the column. My guess is that using simply "e.Column = dataGridComboBoxColumn;" is not enough, but how to do that properly?
Related
Here's how I am making my GridView:
The ListView will contain Entry objects which looks like this:
public class Entry
{
public Entry(BitmapImage icon = null, List<EntryKeyValuePair> entryKeyValuePairs = null)
{
Icon = icon;
EntryKeyValuePairs = entryKeyValuePairs ?? new List<EntryKeyValuePair>();
}
public BitmapImage Icon { get; set; }
public List<EntryKeyValuePair> EntryKeyValuePairs { get; }
}
EntryKeyValuePair is just a KeyValuePair<string,string> where Key is the Column and Value is the value of the column. I used a List of KeyValuePair because I want to preserve insertion order. Anyway, here's how I am constructing the GridView.
GridView = new GridView();
foreach (Column column in Category.Columns.Where(c => c.IsVisibleInTable)) {
var gridViewColumn = new GridViewColumn {
Header = column.Name,
DisplayMemberBinding = new Binding($"EntryKeyValuePairs[{column.Name}].Value")
};
GridView.Columns.Add(gridViewColumn);
}
I don't know what binding to set in DisplayMemberBinding. The above binding would work if EntryKeyValuePairs was a dictionary. But in my case it is not.
If I had access to the data object somehow, I could do
DisplayMemberBinding = new Binding($"EntryKeyValuePairs[{entry.EntryKeyValuePairs.FindIndex(p => p.Key == column.Name)}].Value")
How can I access the current Data Object which the ListView is holding while binding?
I found a solution. I used the GridViewColumn's CellTemplateSelector so that I can get a reference to the ListViews bound object. Here is how the CellTemplateSelector looks like. I had to create the DataTemplates in code.
class GridViewCellTemplateSelector : DataTemplateSelector
{
private readonly string _columnName;
public GridViewCellTemplateSelector(string columnName)
{
_columnName = columnName;
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var entry = (Entry)item;
var dataTemplate = new DataTemplate {
DataType = typeof (Entry)
};
var stackPanelFactory = new FrameworkElementFactory(typeof(StackPanel));
stackPanelFactory.SetValue(StackPanel.OrientationProperty, Orientation.Vertical);
var text = new FrameworkElementFactory(typeof(TextBlock));
text.SetBinding(TextBlock.TextProperty, new Binding($"EntryKeyValuePairs[{entry.EntryKeyValuePairs.FindIndex(p => p.Key == _columnName)}].Value"));
stackPanelFactory.AppendChild(text);
dataTemplate.VisualTree = stackPanelFactory;
return dataTemplate;
}
}
Instead of DisplayMemberBinding, I used this TemplateSelector:
CellTemplateSelector = new GridViewCellTemplateSelector(column.Name)
All good. Hope this helps someone :) I still hope to see a better solution than this.
Im a bit stuck here.. been searching for two days and found no solution.
What i would like to achieve is the behavior of DataGridComboBoxColumn .
I need its DisplayMemberPath, SelectedValuePath and SelectedValueBinding properties..
Im having my UserControl similar to ComboBox in the CellEditingTemplate
and TextBlock in CellTemplate.
Im doing all these in code, since these columns may not exist necessarily..
This is where i define the TemplateColumn :
DataGridTemplateColumn tcol = new DataGridTemplateColumn();
tcol.Header = "accCust";
FrameworkElementFactory texttablF = new FrameworkElementFactory(typeof(TextTableBox));
texttablF.SetValue(TextTableBox.TableSourceProperty, accTable.DefaultView);
FrameworkElementFactory tb = new FrameworkElementFactory(typeof(TextBlock));
texttablF.SetBinding(TextBlock.TextProperty, new Binding("Account"));
tcol.CellTemplate = new DataTemplate(typeof(DataGridCell)) { VisualTree = tb };
tcol.CellEditingTemplate = new DataTemplate(typeof(DataGridCell)) { VisualTree = texttablF };
I have two issues :
TextBlock shows the ID present in the DataGrid's Table, I want it to show the value specified in the DisplayMemberPath, which i have no idea how to implement.
The DependencyProperty TableSource isnt working when done via FrameworkElement.setValue.. It works when it is done in the normal way,
ie
TextTableBox ttb = new TextTableBox();
ttb.TableSource = src_table.DefaultView;
I hope its clear to you what my problem is.. All i really want is to replace the ComboBox in DataGridComboColumn with my UserControl..
Thanks in advance :)
Im sorry if it is a poor question.. Im new to WPF and doing my HighSchools..
Because i wanted the Properties of ComboBox, i had to inherit from DataGridComboBoxColumn.
Heres how the code looks like :
public class DataGridTCBColumn : DataGridComboBoxColumn
{
private TableComboBox comboBox;
public DataGridTCBColumn(bool Editable)
{
comboBox = new TableComboBox() { IsEditable = Editable };
}
// Requires extra field..
public string SelectedValuePathX
{
get
{
return (string)this.GetValue(SelectedValuePathXProperty);
}
set
{
this.SetValue(SelectedValuePathXProperty, value);
}
}
static FrameworkPropertyMetadata SelectedValuePathXPropertyMeta = new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(SelectedValuePathXPropertyChanged));
public static readonly DependencyProperty SelectedValuePathXProperty =
DependencyProperty.Register("SelectedValueX", typeof(string), typeof(TableComboBox), SelectedValuePathXPropertyMeta);
private static void SelectedValuePathXPropertyChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs e)
{}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property == DataGridTCBColumn.ItemsSourceProperty)
{
comboBox.ItemsSource = ItemsSource;
}
else if (e.Property == DataGridTCBColumn.SelectedValuePathProperty)
{
comboBox.SelectedValuePath = SelectedValuePath;
}
else if (e.Property == DataGridTCBColumn.DisplayMemberPathProperty)
{
comboBox.DisplayMemberPath = DisplayMemberPath;
}
base.OnPropertyChanged(e);
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
return comboBox;
}
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
DataGridCell cell = editingEventArgs.Source as DataGridCell;
if (cell != null && !string.IsNullOrEmpty(this.SelectedValuePathX))
{
//For Typed DataSet
object obj = ((DataRowView)editingElement.DataContext).Row[this.SelectedValuePathX];
comboBox.SelectedValue = obj;
}
comboBox.Focus();
return comboBox.SelectedItem;
}
protected override bool CommitCellEdit(FrameworkElement editingElement)
{
if (!string.IsNullOrEmpty(this.SelectedValuePathX) && comboBox.SelectedValue != null)
((DataRowView)editingElement.DataContext).Row[this.SelectedValuePathX] = comboBox.SelectedValue;
return true;
}
}
Had to make another dependency propertry SelectedValuePathX. Actually, it does the job of SelectedValueBinding. However that Binding never did work (tried goin through the source code of ComboBox (Microsoft Reference Source) but dint work out well either.
Hopes it would help somebody :)
My WPF application generates sets of data which may have a different number of columns each time. Included in the output is a description of each column that will be used to apply formatting. A simplified version of the output might be something like:
class Data
{
IList<ColumnDescription> ColumnDescriptions { get; set; }
string[][] Rows { get; set; }
}
This class is set as the DataContext on a WPF DataGrid but I actually create the columns programmatically:
for (int i = 0; i < data.ColumnDescriptions.Count; i++)
{
dataGrid.Columns.Add(new DataGridTextColumn
{
Header = data.ColumnDescriptions[i].Name,
Binding = new Binding(string.Format("[{0}]", i))
});
}
Is there any way to replace this code with data bindings in the XAML file instead?
Here's a workaround for Binding Columns in the DataGrid. Since the Columns property is ReadOnly, like everyone noticed, I made an Attached Property called BindableColumns which updates the Columns in the DataGrid everytime the collection changes through the CollectionChanged event.
If we have this Collection of DataGridColumn's
public ObservableCollection<DataGridColumn> ColumnCollection
{
get;
private set;
}
Then we can bind BindableColumns to the ColumnCollection like this
<DataGrid Name="dataGrid"
local:DataGridColumnsBehavior.BindableColumns="{Binding ColumnCollection}"
AutoGenerateColumns="False"
...>
The Attached Property BindableColumns
public class DataGridColumnsBehavior
{
public static readonly DependencyProperty BindableColumnsProperty =
DependencyProperty.RegisterAttached("BindableColumns",
typeof(ObservableCollection<DataGridColumn>),
typeof(DataGridColumnsBehavior),
new UIPropertyMetadata(null, BindableColumnsPropertyChanged));
private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
DataGrid dataGrid = source as DataGrid;
ObservableCollection<DataGridColumn> columns = e.NewValue as ObservableCollection<DataGridColumn>;
dataGrid.Columns.Clear();
if (columns == null)
{
return;
}
foreach (DataGridColumn column in columns)
{
dataGrid.Columns.Add(column);
}
columns.CollectionChanged += (sender, e2) =>
{
NotifyCollectionChangedEventArgs ne = e2 as NotifyCollectionChangedEventArgs;
if (ne.Action == NotifyCollectionChangedAction.Reset)
{
dataGrid.Columns.Clear();
foreach (DataGridColumn column in ne.NewItems)
{
dataGrid.Columns.Add(column);
}
}
else if (ne.Action == NotifyCollectionChangedAction.Add)
{
foreach (DataGridColumn column in ne.NewItems)
{
dataGrid.Columns.Add(column);
}
}
else if (ne.Action == NotifyCollectionChangedAction.Move)
{
dataGrid.Columns.Move(ne.OldStartingIndex, ne.NewStartingIndex);
}
else if (ne.Action == NotifyCollectionChangedAction.Remove)
{
foreach (DataGridColumn column in ne.OldItems)
{
dataGrid.Columns.Remove(column);
}
}
else if (ne.Action == NotifyCollectionChangedAction.Replace)
{
dataGrid.Columns[ne.NewStartingIndex] = ne.NewItems[0] as DataGridColumn;
}
};
}
public static void SetBindableColumns(DependencyObject element, ObservableCollection<DataGridColumn> value)
{
element.SetValue(BindableColumnsProperty, value);
}
public static ObservableCollection<DataGridColumn> GetBindableColumns(DependencyObject element)
{
return (ObservableCollection<DataGridColumn>)element.GetValue(BindableColumnsProperty);
}
}
I've continued my research and have not found any reasonable way to do this. The Columns property on the DataGrid isn't something I can bind against, in fact it's read only.
Bryan suggested something might be done with AutoGenerateColumns so I had a look. It uses simple .Net reflection to look at the properties of the objects in ItemsSource and generates a column for each one. Perhaps I could generate a type on the fly with a property for each column but this is getting way off track.
Since this problem is so easily sovled in code I will stick with a simple extension method I call whenever the data context is updated with new columns:
public static void GenerateColumns(this DataGrid dataGrid, IEnumerable<ColumnSchema> columns)
{
dataGrid.Columns.Clear();
int index = 0;
foreach (var column in columns)
{
dataGrid.Columns.Add(new DataGridTextColumn
{
Header = column.Name,
Binding = new Binding(string.Format("[{0}]", index++))
});
}
}
// E.g. myGrid.GenerateColumns(schema);
I have found a blog article by Deborah Kurata with a nice trick how to show variable number of columns in a DataGrid:
Populating a DataGrid with Dynamic Columns in a Silverlight Application using MVVM
Basically, she creates a DataGridTemplateColumn and puts ItemsControl inside that displays multiple columns.
I managed to make it possible to dynamically add a column using just a line of code like this:
MyItemsCollection.AddPropertyDescriptor(
new DynamicPropertyDescriptor<User, int>("Age", x => x.Age));
Regarding to the question, this is not a XAML-based solution (since as mentioned there is no reasonable way to do it), neither it is a solution which would operate directly with DataGrid.Columns. It actually operates with DataGrid bound ItemsSource, which implements ITypedList and as such provides custom methods for PropertyDescriptor retrieval. In one place in code you can define "data rows" and "data columns" for your grid.
If you would have:
IList<string> ColumnNames { get; set; }
//dict.key is column name, dict.value is value
Dictionary<string, string> Rows { get; set; }
you could use for example:
var descriptors= new List<PropertyDescriptor>();
//retrieve column name from preprepared list or retrieve from one of the items in dictionary
foreach(var columnName in ColumnNames)
descriptors.Add(new DynamicPropertyDescriptor<Dictionary, string>(ColumnName, x => x[columnName]))
MyItemsCollection = new DynamicDataGridSource(Rows, descriptors)
and your grid using binding to MyItemsCollection would be populated with corresponding columns. Those columns can be modified (new added or existing removed) at runtime dynamically and grid will automatically refresh it's columns collection.
DynamicPropertyDescriptor mentioned above is just an upgrade to regular PropertyDescriptor and provides strongly-typed columns definition with some additional options. DynamicDataGridSource would otherwise work just fine event with basic PropertyDescriptor.
Made a version of the accepted answer that handles unsubscription.
public class DataGridColumnsBehavior
{
public static readonly DependencyProperty BindableColumnsProperty =
DependencyProperty.RegisterAttached("BindableColumns",
typeof(ObservableCollection<DataGridColumn>),
typeof(DataGridColumnsBehavior),
new UIPropertyMetadata(null, BindableColumnsPropertyChanged));
/// <summary>Collection to store collection change handlers - to be able to unsubscribe later.</summary>
private static readonly Dictionary<DataGrid, NotifyCollectionChangedEventHandler> _handlers;
static DataGridColumnsBehavior()
{
_handlers = new Dictionary<DataGrid, NotifyCollectionChangedEventHandler>();
}
private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
DataGrid dataGrid = source as DataGrid;
ObservableCollection<DataGridColumn> oldColumns = e.OldValue as ObservableCollection<DataGridColumn>;
if (oldColumns != null)
{
// Remove all columns.
dataGrid.Columns.Clear();
// Unsubscribe from old collection.
NotifyCollectionChangedEventHandler h;
if (_handlers.TryGetValue(dataGrid, out h))
{
oldColumns.CollectionChanged -= h;
_handlers.Remove(dataGrid);
}
}
ObservableCollection<DataGridColumn> newColumns = e.NewValue as ObservableCollection<DataGridColumn>;
dataGrid.Columns.Clear();
if (newColumns != null)
{
// Add columns from this source.
foreach (DataGridColumn column in newColumns)
dataGrid.Columns.Add(column);
// Subscribe to future changes.
NotifyCollectionChangedEventHandler h = (_, ne) => OnCollectionChanged(ne, dataGrid);
_handlers[dataGrid] = h;
newColumns.CollectionChanged += h;
}
}
static void OnCollectionChanged(NotifyCollectionChangedEventArgs ne, DataGrid dataGrid)
{
switch (ne.Action)
{
case NotifyCollectionChangedAction.Reset:
dataGrid.Columns.Clear();
foreach (DataGridColumn column in ne.NewItems)
dataGrid.Columns.Add(column);
break;
case NotifyCollectionChangedAction.Add:
foreach (DataGridColumn column in ne.NewItems)
dataGrid.Columns.Add(column);
break;
case NotifyCollectionChangedAction.Move:
dataGrid.Columns.Move(ne.OldStartingIndex, ne.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Remove:
foreach (DataGridColumn column in ne.OldItems)
dataGrid.Columns.Remove(column);
break;
case NotifyCollectionChangedAction.Replace:
dataGrid.Columns[ne.NewStartingIndex] = ne.NewItems[0] as DataGridColumn;
break;
}
}
public static void SetBindableColumns(DependencyObject element, ObservableCollection<DataGridColumn> value)
{
element.SetValue(BindableColumnsProperty, value);
}
public static ObservableCollection<DataGridColumn> GetBindableColumns(DependencyObject element)
{
return (ObservableCollection<DataGridColumn>)element.GetValue(BindableColumnsProperty);
}
}
You can create a usercontrol with the grid definition and define 'child' controls with varied column definitions in xaml. The parent needs a dependency property for columns and a method for loading the columns:
Parent:
public ObservableCollection<DataGridColumn> gridColumns
{
get
{
return (ObservableCollection<DataGridColumn>)GetValue(ColumnsProperty);
}
set
{
SetValue(ColumnsProperty, value);
}
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("gridColumns",
typeof(ObservableCollection<DataGridColumn>),
typeof(parentControl),
new PropertyMetadata(new ObservableCollection<DataGridColumn>()));
public void LoadGrid()
{
if (gridColumns.Count > 0)
myGrid.Columns.Clear();
foreach (DataGridColumn c in gridColumns)
{
myGrid.Columns.Add(c);
}
}
Child Xaml:
<local:parentControl x:Name="deGrid">
<local:parentControl.gridColumns>
<toolkit:DataGridTextColumn Width="Auto" Header="1" Binding="{Binding Path=.}" />
<toolkit:DataGridTextColumn Width="Auto" Header="2" Binding="{Binding Path=.}" />
</local:parentControl.gridColumns>
</local:parentControl>
And finally, the tricky part is finding where to call 'LoadGrid'.
I am struggling with this but got things to work by calling after InitalizeComponent in my window constructor (childGrid is x:name in window.xaml):
childGrid.deGrid.LoadGrid();
Related blog entry
You might be able to do this with AutoGenerateColumns and a DataTemplate. I'm not positive if it would work without a lot of work, you would have to play around with it. Honestly if you have a working solution already I wouldn't make the change just yet unless there's a big reason. The DataGrid control is getting very good but it still needs some work (and I have a lot of learning left to do) to be able to do dynamic tasks like this easily.
There is a sample of the way I do programmatically:
public partial class UserControlWithComboBoxColumnDataGrid : UserControl
{
private Dictionary<int, string> _Dictionary;
private ObservableCollection<MyItem> _MyItems;
public UserControlWithComboBoxColumnDataGrid() {
_Dictionary = new Dictionary<int, string>();
_Dictionary.Add(1,"A");
_Dictionary.Add(2,"B");
_MyItems = new ObservableCollection<MyItem>();
dataGridMyItems.AutoGeneratingColumn += DataGridMyItems_AutoGeneratingColumn;
dataGridMyItems.ItemsSource = _MyItems;
}
private void DataGridMyItems_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var desc = e.PropertyDescriptor as PropertyDescriptor;
var att = desc.Attributes[typeof(ColumnNameAttribute)] as ColumnNameAttribute;
if (att != null)
{
if (att.Name == "My Combobox Item") {
var comboBoxColumn = new DataGridComboBoxColumn {
DisplayMemberPath = "Value",
SelectedValuePath = "Key",
ItemsSource = _ApprovalTypes,
SelectedValueBinding = new Binding( "Bazinga"),
};
e.Column = comboBoxColumn;
}
}
}
}
public class MyItem {
public string Name{get;set;}
[ColumnName("My Combobox Item")]
public int Bazinga {get;set;}
}
public class ColumnNameAttribute : Attribute
{
public string Name { get; set; }
public ColumnNameAttribute(string name) { Name = name; }
}
I am trying to create a DataGrid which is populated by setting the ItemsSource property to an ObservableCollection of PropertyGroup objects where each PropertyGroup object contains an ObservableCollection of Property Objects. All the PropertyGroups have the same number of Property Objects, and so I am binding to them via a path using and array subscript. Every thing works fine, except that I get the following binding error AFTER I remove a PropertyGroup object from the DataGrid.
System.Windows.Data Error: 17 : Cannot get 'Item[]' value (type 'PropertyElement')
from 'Children' (type 'ObservableCollection`1'). BindingExpression:Path=Children[3]
.Value; DataItem='PropertyGroupImpl' (HashCode=23661558); target element
is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
ArgumentOutOfRangeException:'System.ArgumentOutOfRangeException:
Specified argument was out of the range of valid values.
Parameter name: index'
My code:
public class DataGridView : UserControl
{
public DataGridView()
{
Rows = new ObservableCollection<PropertyGroup>();
m_DataGrid = new DataGrid();
m_DataGrid.AutoGenerateColumns = false;
m_DataGrid.ItemsSource = Rows;
Content = m_DataGrid;
}
public ObservableCollection<PropertyGroup> Rows { get; set; }
public void AddRowGroup(PropertyGroup propertyGroup)
{
if(Rows.Count == 0)
InitDataGrid(propertyGroup);
Rows.Add(propertyGroup);
}
public void RemoveRowGroup(PropertyGroup propertyGroup)
{
Rows.Remove(propertyGroup);
}
void InitDataGrid(PropertyGroup firstGroup)
{
for(int i = 0; i < firstGroup.Children.Count; ++i)
{
Property prop = firstGroup.Children[i] as Property;
DataGridColumn dgCol = null;
Binding bnd = new Binding();
bnd.Path = new PropertyPath("Children[" + i + "].Value");
if(prop.Type == Property.EnumType.eBool)
dgCol = CreateBooleanColumn(bnd);
else
dgCol = CreateTextColumn(bnd, prop.Value.GetType());
dgCol.Header = prop.Name;
m_DataGrid.Columns.Add(dgCol);
}
}
DataGridColumn CreateTextColumn(Binding bnd, Type propType)
{
var textCol = new DataGridTextColumn();
// Styling code removed for brevity
textCol.Binding = bnd;
return textCol;
}
DataGrid m_DataGrid;
DataGridColumn CreateBooleanColumn(Binding bnd)
{
var chkBoxCol = new DataGridCheckBoxColumn();
chkBoxCol.Binding = bnd;
return chkBoxCol;
}
}
public class PropertyGroup
{
public PropertyGroup()
{
Children = new ObservableCollection<PropertyElement>();
}
public ObservableCollection<PropertyElement> Children { get; set; }
}
public class Property : PropertyElement
{
public enum EnumType {eBool, eInt, eUInt, eFloat, eDouble, eString,
eVector2, eVector3, eVector4, eEnum};
public EnumType Type { get; set; }
public object Value { get; set; }
}
public class PropertyElement
{
public string Name { get; set; }
}
The binding error occurs after RemoveRowGroup() is called for a PropertyGroup when the child Property objects are being removed from the PropertyGroup's Children ObservableCollection.
It seems as though the BindingExpressions binding the DataGrid's cells to Property.Value are still trying to update after the object has been removed from the DataGrid.
Any ideas?
How about assign the Source property without index in PropertyPath?
Binding bnd = new Binding();
bnd.Path = new PropertyPath("Children[" + i + "].Value");
=>
Binding bnd = new Binding();
bnd.Source = firstGroup.Children[i];
bnd.Path = new PropertyPath("Value");
PS: I'm not good at english
I'm trying to bind a List<T> to a DataGridView control, and I'm not having any luck creating custom bindings.
I have tried:
gvProgramCode.DataBindings.Add(new Binding("Opcode",code,"Opcode"));
It throws an exception, saying that nothing was found by that property name.
The name of the column in question is "Opcode". The name of the property in the List<T> is Opcode.
ANSWER EDIT: the problem was that I did not have the bindable fields in my class as properties, just public fields...Apparently it doesn't reflect on fields, just properties.
Is the property on the grid you are binding to Opcode as well?.. if you want to bind directly to List you would just DataSource = list. The databindings allows custom binding. are you trying to do something other than the datasource?
You are getting a bunch of empty rows? do the auto generated columns have names? Have you verified data is in the object (not just string.empty) ?
class MyObject
{
public string Something { get; set; }
public string Text { get; set; }
public string Other { get; set; }
}
public Form1()
{
InitializeComponent();
List<MyObject> myList = new List<MyObject>();
for (int i = 0; i < 200; i++)
{
string num = i.ToString();
myList.Add(new MyObject { Something = "Something " + num , Text = "Some Row " + num , Other = "Other " + num });
}
dataGridView1.DataSource = myList;
}
this should work fine...
I can't really tell what you're trying to do with the example you included, but binding to a generic list of objects is fairly straightforward if you just want to list the objects:
private BindingSource _gridSource;
private BindingSource GridSource
{
get
{
if (_gridSource == null)
_gridSource = new BindingSource();
return _gridSource;
}
}
private void Form1_Load(object sender, EventArgs e)
{
List<FluffyBunny> list = new List<FluffyBunny>();
list.Add(new FluffyBunny { Color = "White", EarType = "Long", Name = "Stan" });
list.Add(new FluffyBunny { Color = "Brown", EarType = "Medium", Name = "Mike" });
list.Add(new FluffyBunny { Color = "Mottled", EarType = "Short", Name = "Torvald" });
GridSource.DataSource = list;
dataGridView1.Columns["EarType"].Visible = false; //Optionally hide a column
dataGridView1.DataSource = GridSource;
}
If you only want to display specific properties of the List's type you should be able to make the unwanted column(s) invisible.
Technically, you don't really need to create the BindingSource, but I find it's a whole lot easier when I'm doing updates or changes if I have it.
Hope this helps.
Had the same issue... I had a struct with public fields obviously. nothing in the grid. provided public getters, worked.
Another solution I've found is to use the BindingList collection.
private void Form1_Load(object sender, EventArgs e)
{
BindingList people= new BindingList {
new Person {Name="John",Age=23},
new Person {Name="Lucy",Age=16}
};
dataGridView1.DataSource= people;
}
It works fine for me,