<$BlogRSDURL$>
My Blog Links
Site Links
Currently Reading
Blogroll
Archives
Tools
  • This page is powered by Blogger. Isn't yours?
  • Weblog Commenting and Trackback by HaloScan.com
  • 2RSS.com :: RSS directory
  • Listed on BlogShares
  • Blog Directory, Find A Blog, Submit A Blog, Search For The Best Blogs

Friday, March 26, 2004

ArrayList with Events (Part 2) 

Now that the ArrayList fires events, what about when the data within one of its contained components changes. Any UI you have (or any other listener for that matter) would need to be updated. So, we need events fired when that happens too. The problem is that the ArrayList doesn't know when you've changed the data object itself because lots of time, you call it like this:
         myArrayList[index].Name = "new name";
All the ArrayList knows is that the object has been accessed, but not why. So, the object itself needs to be able to fire the change event. To do this, we exposed a public ArrayItemsChanged event and OnArrayItemsChanged method, so that it can be called from the object not just within the EventedArrayList.

Note: I tried using a static ArrayItemsChanged event on the EventedListItem class itself so that any instance of it could fire this event. But, then the listeners list would be static too, so any item change would fire the event to all listeners regardless of what instance of the ArrayList is was in. That wasn't exactly what I wanted because it could cause a lot of "noise" events to be fired, and the handlers would need more intelligent code to figure out whether or not it cared about that particular object's event. I decided to let the ArrayList manage the listener list because then you'd only have to connect to the right instance of the ArrayList rather than each individual instance of an object that you wanted to listen to.


So, now the problem was for an object to fire the event, it needs a pointer back to the appropriate EventedArrayList that it belongs to, so I created the following interface and class to handle that:

///
/// Interface required for data objects that want to fire events whenever their
/// data changes.
///

public interface IEventedListItem
{
// used to associate an ArrayList with this item.
void AssociateWith(EventedArrayList parent);

// checks if the specified ArrayList is associated with this item.
bool IsAssociatedWith(EventedArrayList parent);

// used to disassociate the specified ArrayList with this item.
void DisassociateFrom(EventedArrayList parent);

// used as helper method to fire events to all associated ArrayLists.
void FireAllChangedEvents(object oldValue, object newValue);
}

///
/// EventedListItem are data objects that use the default implementation of
/// IEventedListItem to fire events whenever their data changes.
///

public abstract class EventedListItem : IEventedListItem
{
private ArrayList parents = new ArrayList();

public EventedListItem()
{
}

public void AssociateWith(EventedArrayList parent)
{
if (parent != null)
{
// only add the parent if it's not null, and it's not already
// associated with this item.
if (this.IsAssociatedWith(parent) == false)
{
this.parents.Add(parent);
}
}
}

public bool IsAssociatedWith(EventedArrayList parent)
{
return this.parents.Contains(parent);
}

public void DisassociateFrom(EventedArrayList parent)
{
if (parent != null)
{
// only remove the parent if it's not null, and it is already
// associated with this item.
if (this.IsAssociatedWith(parent) == true)
{
this.parents.Remove(parent);
}
}
}

public void FireAllChangedEvents(object oldValue, object newValue)
{
if (oldValue != newValue)
{
ArrayListEventArgs e = new ArrayListEventArgs(this);
foreach (EventedArrayList parent in this.parents)
{
// then, use parents to fire an event with each ArrayList
// that we're associated with.
parent.OnArrayItemsChanged(e);
}
}
}
}

 


The EventedListItem keeps track of its parent EventedArrayList, so that it knows which ones to use to fire events through. It supports a list of parents that this item has been added to, that way you can put this item in multiple EventedArrayLists. If it hasn't been associated to an ArrayList yet, then it doesn't fire any events.

Also, any item that wants this functionality needs to either implement the IEventedListItem interface or derive from EventedListItem (which contains the default implementation for that interface). I implemented it through an interface, in case you have issues where you need to derive from a specific base class. And, provided the base implementation for the cases where you can derive from it (in this case, no additional coding needed on your end).

Note: If multiple class derivation is an issue for your class, then you must implement IEventedListItem on your class rather than deriving directly from EventedListItem (you can copy its code for your implementation, if you want the same functionality though).


Now that we have these two classes (EventedArrayList and EventedListItem), we need to associate them. I did this in the EventedArrayList class. The OnArrayItemsAdded method associates any newly added items with this EventedArrayList. If the item does not implement IEventedListItem then it's not associated.

protected virtual void OnArrayItemsAdded(ArrayListEventArgs e)
{
arrayItemsAdded temp = ArrayItemsAdded;
if (temp != null)
{
// make sure all items are associated with this array before firing event.
AssociateItemsWithArrayList(e.Items);
ArrayItemsAdded(this, e);
}
}

protected virtual void OnArrayItemsRemoved(ArrayListEventArgs e)
{
arrayItemsRemoved temp = ArrayItemsRemoved;
if (temp != null)
{
// make sure anything that was associated with this array is now disassociated.
DisassociateItemsWithArrayList(e.Items);
ArrayItemsRemoved(this, e);
}
}

private void AssociateItemsWithArrayList(ICollection items)
{
foreach (object obj in items)
{
// hook into items that are evented, so they can fire
// their change events through this ArrayList.
if (obj is IEventedListItem)
{
IEventedListItem temp = obj as IEventedListItem;
if (temp != null)
temp.AssociateWith(this);
}
}
}

private void DisassociateItemsWithArrayList(ICollection items)
{
foreach (object obj in items)
{
// hook into items that are evented, so they can disassociate
//objects that are associated with this ArrayList.
if (obj is IEventedListItem)
{
IEventedListItem temp = obj as IEventedListItem;
if (temp != null)
temp.DisassociateFrom(this);
}
}
}

 


As you can see, we also have a corresponding DisassociateItemsWithArrayList that's called from OnArrayItemsRemoved to get rid of any existing associations.

Now, on to how we handle these new events...

You can download the full sample code from GotDotNet or look at it online and report bugs at the workspace.

Comments: Post a Comment