Writing a Custom BindingList for Mapping between DataObjects andDataTables

J

jehugaleahsa

Hello:

I have a mapping class that can take generic data objects and locate/
add/remove/update DataRows in a DataTable. I associate them using
attributes: TableAttribute and ColumnAttribute. I use reflection to
move the data between the data object and the DataTable.

I would like to make this extremely easy for my Forms developers.

I would like calling AddNew() to call mapper.AddDataRow(dataObject).

I would like changes to call mapper.UpdateDataRow(dataObject).

I would like removing to call mapper.DeleteDataRow(dataObject).

I am trying to implement this. Right now, I am inheriting from
BindingList<DataObject>. However, I am not sure which methods to
override and where to place my code. A quick example would be awesome.
I get the impression this should be easy to do; I just don't know how
to do it correctly.

Also, I would like to know what is the best method for preventing the
addition a bogus values to the BindingList<T>. How can I prevent them
from adding NULL or and instance of U?

Thanks!
~Travis
 
J

jehugaleahsa

This is what I have right now:

public class DataObjectBindingList<TDataRecord> :
BindingList<TDataRecord>
where TDataRecord : class, new()
{
private readonly IDataTableMapper<TDataRecord> mapper;

public DataObjectBindingList(IDataTableMapper<TDataRecord>
mapper)
{
Check.CheckArgumentNull(mapper, "mapper");
this.mapper = mapper;
}

protected override void OnAddingNew(AddingNewEventArgs e)
{
base.OnAddingNew(e);
object adding = e.NewObject;
if (!(adding is TDataRecord))
{
throw new InvalidOperationException("Items must be of
type "
+ typeof(TDataRecord).FullName
+ " and non-null");
}
}

public override void EndNew(int itemIndex)
{
TDataRecord record = this[itemIndex];
mapper.AddDataRow(record);
base.EndNew(itemIndex);
}

protected override void RemoveItem(int index)
{
TDataRecord record = this[index];
mapper.DeleteDataRow(record);
base.RemoveItem(index);
}

public void Update()
{
foreach (TDataRecord record in Items)
{
mapper.UpdateDataRow(record);
}
}
}
 
J

jehugaleahsa

I am currently forcing the developer to call Update(). This isn't good
because it will make it look like every record was changed. I could
change how UpdateDataRow works by having it check whether the updated
value is changing before altering the DataRow, but that could get
ugly.

There isn't really a way to tell whether an object is changed. I mean,
I could check whether the class supports INotifyPropertyChanged and
listen, but managing the events doesn't sound like much fun either.
 
J

jehugaleahsa

Here is the latest version. I realized that most of the code I wrote
before wasn't necessary.

public class DataObjectBindingList<TDataRecord> :
BindingList<TDataRecord>
where TDataRecord : class, new()
{
private readonly IDataTableMapper<TDataRecord> mapper;
private readonly Dictionary<TDataRecord,
PropertyChangedEventHandler> handlers;

public DataObjectBindingList(IDataTableMapper<TDataRecord>
mapper)
{
Check.CheckArgumentNull(mapper, "mapper");
this.mapper = mapper;
if
(typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(TDataRecord)))
{
handlers = new Dictionary<TDataRecord,
PropertyChangedEventHandler>();
}
}

protected override void InsertItem(int index, TDataRecord
item)
{
if (handlers != null && !handlers.ContainsKey(item))
{
PropertyChangedEventHandler handler = new
PropertyChangedEventHandler(itemChanged);
((INotifyPropertyChanged)item).PropertyChanged +=
handler;
handlers[item] = handler;
}
mapper.AddDataRow(item);
base.InsertItem(index, item);
}

protected override void SetItem(int index, TDataRecord item)
{
if (handlers != null && !handlers.ContainsKey(item))
{
PropertyChangedEventHandler handler = new
PropertyChangedEventHandler(itemChanged);
((INotifyPropertyChanged)item).PropertyChanged +=
handler;
handlers[item] = handler;
}
base.SetItem(index, item);
}

protected override void RemoveItem(int index)
{
TDataRecord record = this[index];
mapper.DeleteDataRow(record);
PropertyChangedEventHandler handler;
if (handlers != null && handlers.TryGetValue(record, out
handler))
{
((INotifyPropertyChanged)record).PropertyChanged -=
handler;
}
base.RemoveItem(index);
}

private void itemChanged(object sender,
PropertyChangedEventArgs e)
{
mapper.UpdateDataRow((TDataRecord)sender);
}
}
 
J

jehugaleahsa

I realized that if the code modifies the primay key, I can't map back
to the DataRow. I had to think about this one.

I have written a Binding class that will bind a property to a column
in a DataRow. It works but worries me.

public class Binding
{
private readonly object instance;
private readonly PropertyInfo property;
private readonly DataRow row;
private readonly string columnName;
private readonly DataRowChangeEventHandler rowChangedHandler;
private readonly PropertyChangedEventHandler
propertyChangedHandler;

public Binding(object instance, PropertyInfo property, DataRow
row, string columnName)
{
this.instance = instance;
this.property = property;
this.row = row;
this.columnName = columnName;
rowChangedHandler = new
DataRowChangeEventHandler(rowChanged);
row.Table.RowChanged += rowChangedHandler;
if (instance is INotifyPropertyChanged)
{
INotifyPropertyChanged item =
(INotifyPropertyChanged)instance;
propertyChangedHandler = new
PropertyChangedEventHandler(propertyChanged);
item.PropertyChanged += propertyChangedHandler;
}
}

private void updateRecord()
{
INotifyPropertyChanged item = instance as
INotifyPropertyChanged;
try
{
if (item != null)
{
item.PropertyChanged -= propertyChangedHandler;
}
object value = row[columnName];
value = TypeCaster.ConvertTo(value,
property.PropertyType);
property.SetValue(item, value, null);
}
finally
{
if (item != null)
{
item.PropertyChanged += propertyChangedHandler;
}
}
}
private void updateRow()
{
try
{
row.Table.RowChanged -= rowChangedHandler;
row[columnName] = property.GetValue(instance, null);
}
finally
{
row.Table.RowChanged += rowChangedHandler;
}
}

private void rowChanged(object sender, DataRowChangeEventArgs
e)
{
if (e.Action == DataRowAction.Change && e.Row == row)
{
updateRecord();
}
}

private void propertyChanged(object sender,
PropertyChangedEventArgs e)
{
if (e.PropertyName == property.Name)
{
updateRow();
}
}
}
 
J

jehugaleahsa

This ended up being a bigger problem than I envisioned. There are lot
of issues with binding to the same record or row.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top