Hi,
[quoted text, click to view] "David Clegg" <dclegg@gmail.com> wrote in message
news:xn0et7doj2s5kdo00f@msnews.microsoft.com...
>I am attempting to data bind to a custom collection class
> (ReportDataRowCollection) which contains a list of custom objects
> (ReportDataRow) which in turn exposes a list of custom objects
> (ReportDataField) via an indexed property which takes a string
> parameter to identify the ReportDataField instance to return. This is a
> similar approach as the one used to data bind to a System.Data.DataSet,
> but I cannot get it to work with my custom class hierarchy. Every time
> I attempt to bind to the indexed property, I get a
> System.ArgumentException thrown with the message "Cannot bind to the
> property or column {index id} on the DataSource. Parameter name:
> dataMember".
>
> Using the same techniques I can successfully databind to a DataSet, so
> figure there is some fundamental step in the databinding process or
> architecture that I'm missing here. Perhaps there is an interface that
> needs to be implemented (ICustomTypeDescriptor perhaps?), and I'm
Yes, you need to implement :
ICustomTypeDescriptor on the ReportDataRow,
ITypedList on ReportDataRowCollection or
both
But consider if you use a BindingSource, then the BindingSource will take
care of ITypedList, so you only need to implement ICustomTypeDescriptor on
the ReportDataRow.
Both ITypedList and ICustomTypeDescriptor need to return a set of
PropertyDescriptors which describe the columns/fields. They don't
necesairly contain any data (altough they could) but at the least they know
how to get and set the data.
It's a lot to explain, so maybe this (very) basic example might get you
going:
ReportDataRowCollection : contains columns and rows (items)
ReportDataRow : contains data keyed by ReportDataColumn, implements
ICustomTypeDescriptor
ReportDataColumn : describes a column, inherits PropertyDescriptor
ReportDataColumnCollection : collection of columns, indexable by index and
name
In this example the rows contain the data indexed by the column, BUT it's
not impossible for the columns to contain the data indexed by the rows.
That's how it's done with DataTable, since one column usually contains the
same type of data it's more efficient. You can easily modify the example to
switch data storage from the rows to the columns.
================================================
public class ReportDataRowCollection : BindingList<ReportDataRow>
{
private ReportDataColumnCollection columns =
new ReportDataColumnCollection();
public ReportDataColumnCollection Columns
{
get { return columns; }
}
public ReportDataRow NewRow()
{
ReportDataRow row = new ReportDataRow();
row.owner = this;
return row;
}
protected override void InsertItem(int index, ReportDataRow item)
{
base.InsertItem(index, item);
item.owner = this;
}
}
================================================
public class ReportDataRow : ICustomTypeDescriptor
{
internal ReportDataRowCollection owner;
// store values using ReportDataColumn as key
private Dictionary<ReportDataColumn, object> values
= new Dictionary<ReportDataColumn, object>();
internal ReportDataRow()
{
}
public ReportDataRowCollection Owner
{
get { return owner; }
}
public object this[ReportDataColumn Col]
{
set { values[Col] = value; }
get
{
if (values.ContainsKey(Col))
return values[Col];
else
return null;
}
}
public object this[string Name]
{
set { this[owner.Columns[Name]] = value; }
get { return this[owner.Columns[Name]]; }
}
public object this[int Index]
{
set { this[owner.Columns[Index]] = value; }
get { return this[owner.Columns[Index]]; }
}
#region ICustomTypeDescriptor Members
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection pdc = new PropertyDescriptorCollection(
owner.Columns.ToArray());
return pdc;
}
public PropertyDescriptorCollection GetProperties()
{
return GetProperties(null);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
public string GetClassName()
{
return "ReportDataRow";
}
public string GetComponentName()
{
return "ReportDataRow";
}
public TypeConverter GetConverter()
{
return null;
}
public EventDescriptor GetDefaultEvent()
{
return null;
}
public PropertyDescriptor GetDefaultProperty()
{
return null;
}
public AttributeCollection GetAttributes()
{
return null;
}
public object GetEditor(Type editorBaseType)
{
return null;
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return null;
}
public EventDescriptorCollection GetEvents()
{
return GetEvents(null);
}
#endregion
}
================================================
public class ReportDataColumnCollection : List<ReportDataColumn>
{
public ReportDataColumn this[string Name]
{
get
{
return Find(delegate(ReportDataColumn r)
{ return r.Name == Name; });
}
}
}
================================================
public class ReportDataColumn : PropertyDescriptor
{
public ReportDataColumn(string Name) : base(Name,null)
{
}
#region PropertyDescriptor
public override Type ComponentType
{
get { return typeof(ReportDataRow); }
}
public override object GetValue(object component)
{
// get data from row
ReportDataRow row = component as ReportDataRow;
return row[this];
}
public override void SetValue(object component, object value)
{
// set data to row
ReportDataRow row = component as ReportDataRow;
row[this] = value;
}
public override bool IsReadOnly
{
get { return false; }
}
public override Type PropertyType
{
get { return typeof(object); }
}
public override bool CanResetValue(object component)
{
return false;
}
public override void ResetValue(object component)
{
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
#endregion
}
================================================
Usage:
ReportDataRowCollection rowCollection = new ReportDataRowCollection();
rowCollection.Columns.Add(new ReportDataColumn("Field1"));
ReportDataRow row = rowCollection.NewRow();
row["Field1"] = "test";
rowCollection.Add(row);
BindingSource bs = new BindingSource(rowCollection, "");
textBox1.DataBindings.Add("Text", bs, "Field1");
HTH,
Greetings