Groups | Blog | Home
all groups > dotnet windows forms databinding > october 2006 >

dotnet windows forms databinding : Binding to indexed property



David Clegg
10/31/2006 5:48:15 PM
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
currently researching that technique to see whether it will give me the
desired result. Any pointers or example code to speed up this process
greatfully received though.

Below is the code for the classes I'm using for testing, which exhibits
the same behaviour as my intended code, followed by the code I'm using
to perform the data binding :-

public class ReportDataRow
{
private Dictionary<string, string> fields;

public ReportDataRow()
{
fields = new Dictionary<string, string>();
}

public Dictionary<string, string> Fields { get { return fields; } }

public string this[string fieldName]
{
get
{
if (fields.ContainsKey(fieldName))
{
return fields[fieldName];
}
else
{
return null;
}
}
}
}

public class ReportDataSet : DataSet
{
public ReportDataSet()
{
DataTable table = new DataTable("ReportDataTable");
table.Columns.Add("One");
table.Columns.Add("Two");
table.Rows.Add(new string[] { "DS1", "DS2" });
this.Tables.Add(table);
}
}

Databinding code:-

List<ReportDataRow> rows = new List<ReportDataRow>();
ReportDataRow row = new ReportDataRow();
row.Fields.Add("One", "RDR1");
row.Fields.Add("Two", "RDR2");
rows.Add(row);

BindingSource source = new BindingSource(this.components);
source.DataSource = rows;
source.Position = 0;
try
{
label1.DataBindings.Add(new System.Windows.Forms.Binding("Text",
source, "One", true));
}
catch (ArgumentException exc)
{
MessageBox.Show(exc.Message);
}

ReportDataSet ds = new ReportDataSet();
source = new BindingSource(this.components);
source.DataMember = "ReportDataTable";
source.DataSource = ds;
source.Position = 0;
label3.DataBindings.Add(new System.Windows.Forms.Binding("Text",
source, "Two", true));


--
Cheers,
David Clegg
dclegg@gmail.com
http://cc.borland.com/Author.aspx?ID=72299

QualityCentral. The best way to bug Borland about bugs.
http://qc.borland.com

"Vampires are make believe, just like elves and gremlins and eskimos."
David Clegg
11/1/2006 11:17:25 AM
[quoted text, click to view]

Thanks. I did actually figure this out not long after posting, and I
did post a reply to my original post with my solution. For some reason
that post didn't turn up (I had the same problem with my first post).

Nevertheless, your post had lots of additional and very helpful
information, so thanks a bunch for that.

--
Cheers,
David Clegg
dclegg@gmail.com
http://cc.borland.com/Author.aspx?ID=72299

QualityCentral. The best way to bug Borland about bugs.
http://qc.borland.com

Bart Mermuys
11/1/2006 12:55:37 PM
Hi,

[quoted text, click to view]

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


AddThis Social Bookmark Button