Recently, I have been trying to wrap my mind around the BindingList<T> and INotifyPropertChanged.
More specifically - How do I make a collection of objects (having objects as properties) which will allow me to subscribe to events throughout the tree?
To that end, I have examined the code offered as examples by others. One such project that I downloaded was Nested Property Binding - CodeProject by "seesharper".
Now, the article explains the implementation, but there was a question by "Someone@AnotherWorld" about "INotifyPropertyChanged in nested objects".
His question was:
Hi,
nice stuff! But after a couple of time using your solution I realize the ObjectBindingSource ignores the PropertyChanged event of nested objects.
E.g. I've got a class 'Foo' with two properties named 'Name' and 'Bar'. 'Name' is a string an 'Bar' reference an instance of class 'Bar', which has a 'Name' property of type string too and both classes implements INotifyPropertyChanged.
With your binding source reading and writing with both properties ('Name' and 'Bar_Name') works fine but the PropertyChanged event works only for the 'Name' property, because the binding source listen only for events of 'Foo'.
One workaround is to retrigger the PropertyChanged event in the appropriate class (here 'Foo'). What's very unclean! The other approach would be to extend ObjectBindingSource so that all owner of nested property which implements INotifyPropertyChanged get used for receive changes, but how?
Thanks!
I had asked about BindingList<T> yesterday and received a good answer from Aaronaught.
In my question, I had a similar point as "Someone@AnotherWorld":
if Keywords were to implement INotifyPropertyChanged, would changes be accessible to the BindingList through the ScannedImage object?
To which Aaronaught's response was:
No, they will not. BindingList only looks at the specific object in the list, it has no ability to scan all dependencies and monitor everything in the graph (nor would that always be a good idea, if it were possible).
I understand Aaronaught's comment regarding this behavior not necessarily being a good idea. Additionally, his suggestion to have my bound object "relay" events on behalf of it's member objects works fine and is perfectly acceptable.
For me, "re-triggering" the PropertyChanged event does not seem so unclean as "Someone@AnotherWorld" laments. I do understand why he protests - in the interest of loosely coupled objects. However, I believe that coupling between objects that are part of a composition is logical and not so undesirable as this may be in other scenarios. (I am a newb, so I could be waaayyy off base.)
Anyway, in the interest of exploring an answer to the question by "Someone@AnotherWorld", I altered the MainForm.cs file of the example project from Nested Property Binding - CodeProject by "seesharper" to the following:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Core.ComponentModel;
using System.Windows.Forms;
namespace ObjectBindingSourceDemo
{
public partial class MainForm : Form
{
private readonly List<Customer> _customers = new List<Customer>();
private readonly List<Product> _products = new List<Product>();
private List<Order> orders;
public MainForm()
{
InitializeComponent();
dataGridView1.AutoGenerateColumns = false;
dataGridView2.AutoGenerateColumns = false;
CreateData();
}
private void CreateData()
{
_customers.Add(
new Customer(1, "Jane Wilson",
new Address("98104",
"6657 Sand Pointe Lane",
"Seattle",
"USA")));
_customers.Add(
new Customer(1, "Bill Smith",
new Address("94109",
"5725 Glaze Drive",
"San Francisco",
"USA")));
_customers.Add(
new Customer(1, "Samantha Brown", null));
_products.Add(new Product(1, "Keyboard", 49.99));
_products.Add(new Product(2, "Mouse", 10.99));
_products.Add(new Product(3, "PC", 599.99));
_products.Add(new Product(4, "Monitor", 299.99));
_products.Add(new Product(5, "LapTop", 799.99));
_products.Add(new Product(6, "Harddisc", 89.99));
customerBindingSource.DataSource = _customers;
productBindingSource.DataSource = _products;
orders = new List<Order>();
orders.Add(new Order(1, DateTime.Now, _customers[0]));
orders.Add(new Order(2, DateTime.Now, _customers[1]));
orders.Add(new Order(3, DateTime.Now, _customers[2]));
#region Added by me
OrderLine orderLine1 = new OrderLine(_products[0], 1);
OrderLine orderLine2 = new OrderLine(_products[1], 3);
orderLine1.PropertyChanged +=
new PropertyChangedEventHandler(OrderLineChanged);
orderLine2.PropertyChanged +=
new PropertyChangedEventHandler(OrderLineChanged);
orders[0].OrderLines.Add(orderLine1);
orders[0].OrderLines.Add(orderLine2);
#endregion
// Removed by me in lieu of region above.
//orders[0].OrderLines.Add(new OrderLine(_products[0], 1));
//orders[0].OrderLines.Add(new OrderLine(_products[1], 3));
ordersBindingSource.DataSource = orders;
}
#region Added by me
// Have to wait until the form is Shown to wire up the events
// for orderDetailsBindingSource. Otherwise, they are triggered
// during MainForm().InitializeComponent().
private void MainForm_Shown(object sender, EventArgs e)
{
orderDetailsBindingSource.AddingNew +=
new AddingNewEventHandler(orderDetailsBindSrc_AddingNew);
orderDetailsBindingSource.CurrentItemChanged +=
new EventHandler(orderDetailsBindSrc_CurrentItemChanged);
orderDetailsBindingSource.ListChanged +=
new ListChangedEventHandler(orderDetailsBindSrc_ListChanged);
}
private void orderDetailsBindSrc_AddingNew(
object sender, AddingNewEventArgs e)
{
}
private void orderDetailsBindSrc_CurrentItemChanged(
object sender, EventArgs e)
{
}
private void orderDetailsBindSrc_ListChanged(
object sender, ListChangedEventArgs e)
{
ObjectBindingSource bindingSource = (ObjectBindingSource)sender;
if (!(bindingSource.Current == null))
{
// Unsure if GetType().ToString() is required b/c ToString()
// *seems*
// to return the same value.
if (bindingSource.Current.GetType().ToString() ==
"ObjectBindingSourceDemo.OrderLine")
{
if (e.ListChangedType == ListChangedType.ItemAdded)
{
// I wish that I knew of a way to determine
// if the 'PropertyChanged' delegate assignment is null.
// I don't like the current test, but it seems to work.
if (orders[
ordersBindingSource.Position].OrderLines[
e.NewIndex].Product == null)
{
orders[
ordersBindingSource.Position].OrderLines[
e.NewIndex].PropertyChanged +=
new PropertyChangedEventHandler(
OrderLineChanged);
}
}
if (e.ListChangedType == ListChangedType.ItemDeleted)
{
// Will throw exception when leaving
// an OrderLine row with unitialized properties.
//
// I presume this is because the item
// has already been 'disposed' of at this point.
// *but*
// Will it be actually be released from memory
// if the delegate assignment for PropertyChanged
// was never removed???
if (orders[
ordersBindingSource.Position].OrderLines[
e.NewIndex].Product != null)
{
orders[
ordersBindingSource.Position].OrderLines[
e.NewIndex].PropertyChanged -=
new PropertyChangedEventHandler(
OrderLineChanged);
}
}
}
}
}
private void OrderLineChanged(object sender, PropertyChangedEventArgs e)
{
MessageBox.Show(e.PropertyName, "Property Changed:");
}
#endregion
}
}
In the method private void orderDetailsBindSrc_ListChanged(object sender, ListChangedEventArgs e) I am able to hook up the PropertyChangedEventHandler to the OrderLine object as it is being created.
However, I cannot seem to find a way to unhook the PropertyChangedEventHandler from the OrderLine object before it is being removed from the orders[i].OrderLines list.
So, my questions are:
Am I simply trying to do something that is very, very wrong here?
Will the OrderLines object that I add the delegate assignments to ever be released from memory if the assignment is not removed?
Is there a "sane" method of achieving this scenario?
Also, note that this question is not specifically related to my prior. I have actually solved the issue which had prompted me to inquire before.
However, I have reached a point with this particular topic of discovery where my curiosity has exceeded my patience - hopefully someone here can shed some light on this?