Hooking into ECO multi-association events
Although I have not (yet) needed this myself I can see myself needing it in the future and the question has been asked before.
"Setting HasUserCode=True on a Child.Parent single role does what I want, but how do I handle the scenario where Parent.Children.Add(item) is called on a multirole?"
By default you can’t, but with the addition of a single class and a small amount of tweaking you can get it to do what you want! Here is how to do it:
01: Mark Parent.Children’s association end with HasUserCode=True in the modeler and then generate code.
02: In the source code of your class (not within an ECO region) add the following
This is a class that does not yet exist, I will show the source code for it later.
02: In the source code locate the "Children" property and change it like so
Note that I have put an #if around the original code that will never be true. You cannot remove the MM_ECO_Generated section due to the source code generator expecting to find it, but you can make sure it is never even compiled! The parameters for the EcoMultiAssociation<T> constructor were just copied directly from the ObjectListAdapter constructor below.
(Just a small note, I don’t usually name my private members m_Name, I just did it in this case to make it easier to spot the difference between the Children property and the m_Children private member).
03: The event handlers are implemented like so
The args parameter has a reference to the old object and also the new object in the case of ItemChanged which is executed when you do this...
Finally here is the source code for the EcoMultiAssociation<T> class. There’s quite a bit here, but that’s really because I have to implement so many interfaces, the actual code is very small.
Note that these events will not be executed if you do Child.Parent = p; For this case you have to set HasUserCode=True on Child.Parent as normal and react accordingly.
"Setting HasUserCode=True on a Child.Parent single role does what I want, but how do I handle the scenario where Parent.Children.Add(item) is called on a multirole?"
By default you can’t, but with the addition of a single class and a small amount of tweaking you can get it to do what you want! Here is how to do it:
01: Mark Parent.Children’s association end with HasUserCode=True in the modeler and then generate code.
02: In the source code of your class (not within an ECO region) add the following
private EcoMultiAssociation<Child> m_Children;
This is a class that does not yet exist, I will show the source code for it later.
02: In the source code locate the "Children" property and change it like so
public IEcoList<Child> Children
{
get
{
if (m_Children == null)
{
m_Children= new EcoMultiAssociation<Child>((IList)(this.eco_Content.get_MemberByIndex(Eco_LoopbackIndices.Children_MemberIndex)));
m_Children+= new AssociationItemChangedEventHandler<Child>(result_ItemChanged);
m_Children+= new AssociationChangedEventHandler<Child>(result_ItemInserted);
m_Children+= new AssociationChangedEventHandler<Child>(result_ItemRemoved);
}
return m_Children
#if NeverDoThis
#region MM_ECO_Generated
return new ObjectListAdapter<Child>((IList) (this.eco_Content.get_MemberByIndex(Eco_LoopbackIndices.Children_MemberIndex)));
#endregion
#endif
}
}
Note that I have put an #if around the original code that will never be true. You cannot remove the MM_ECO_Generated section due to the source code generator expecting to find it, but you can make sure it is never even compiled! The parameters for the EcoMultiAssociation<T> constructor were just copied directly from the ObjectListAdapter constructor below.
(Just a small note, I don’t usually name my private members m_Name, I just did it in this case to make it easier to spot the difference between the Children property and the m_Children private member).
03: The event handlers are implemented like so
void result_ItemRemoved(object sender, AssociationChangedEventArgs<Child> args)
{
System.Diagnostics.Debug.WriteLine(string.Format("Removed index {0}", args.Index));
}
void result_ItemInserted(object sender, AssociationChangedEventArgs<Child> args)
{
System.Diagnostics.Debug.WriteLine(string.Format("Inserted at index {0}", args.Index));
}
void result_ItemChanged(object sender, AssociationItemChangedEventArgs<Child> args)
{
System.Diagnostics.Debug.WriteLine(string.Format("Changed object at index {0}", args.Index));
}
The args parameter has a reference to the old object and also the new object in the case of ItemChanged which is executed when you do this...
Association[x] = y;
Finally here is the source code for the EcoMultiAssociation<T> class. There’s quite a bit here, but that’s really because I have to implement so many interfaces, the actual code is very small.
public delegate void AssociationChangedEventHandler<T>(object sender, AssociationChangedEventArgs<T> args);
public class AssociationChangedEventArgs<T> : EventArgs
{
public readonly T Item;
public readonly int Index;
public AssociationChangedEventArgs(int index, T item)
{
Item = item;
Index = index;
}
}
public delegate void AssociationItemChangedEventHandler<T>(object sender, AssociationItemChangedEventArgs<T> args);
public class AssociationItemChangedEventArgs<T> : AssociationChangedEventArgs<T>
{
public readonly T OriginalItem;
public AssociationItemChangedEventArgs(int index, T newItem, T originalItem)
: base(index, newItem)
{
OriginalItem = originalItem;
}
}
public class EcoMultiAssociation<T> : IEcoList<T>, IList
{
private readonly IList Adaptee;
public EcoMultiAssociation(IList adaptee)
{
if (adaptee == null)
throw new ArgumentNullException("Adaptee");
Adaptee = adaptee;
}
public void Add(T item)
{
if (Adaptee.IndexOf(item) == -1)
{
Adaptee.Add(item);
OnItemInserted(Count - 1, item);
}
}
public void Clear()
{
List<T> originals = new List<T>(this);
Adaptee.Clear();
for (int index = 0; index < originals.Count; index++)
OnItemRemoved(index, originals[index]);
}
public bool Contains(T item)
{
return Adaptee.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
Adaptee.CopyTo(array, arrayIndex);
}
public int Count
{
get { return Adaptee.Count; }
}
public IEnumerator<T> GetEnumerator()
{
return new ObjectEnumeratorAdapter<T>(Adaptee.GetEnumerator());
}
public int IndexOf(T item)
{
return Adaptee.IndexOf(item);
}
public void Insert(int index, T item)
{
if (Adaptee.IndexOf(item) == -1)
{
Adaptee.Insert(index, item);
OnItemInserted(index, item);
}
}
public bool IsReadOnly
{
get { return Adaptee.IsReadOnly; }
}
public bool Remove(T item)
{
int index = Adaptee.IndexOf(item);
if (index == -1)
return false;
Adaptee.RemoveAt(index);
return true;
}
public void RemoveAt(int index)
{
T item = this[index];
Adaptee.RemoveAt(index);
OnItemRemoved(index, item);
}
public T this[int index]
{
get
{
return (T)Adaptee[index];
}
set
{
T originalItem = (T)Adaptee[index];
Adaptee[index] = value;
OnItemChanged(index, originalItem, value);
}
}
public event AssociationChangedEventHandler<T> ItemInserted;
protected void OnItemInserted(int index, T item)
{
AssociationChangedEventHandler<T> handler = ItemInserted;
if (handler != null)
{
AssociationChangedEventArgs<T> args = new AssociationChangedEventArgs<T>(index, item);
handler(this, args);
}
}
public event AssociationChangedEventHandler<T> ItemRemoved;
protected void OnItemRemoved(int index, T item)
{
AssociationChangedEventHandler<T> handler = ItemRemoved;
if (handler != null)
{
AssociationChangedEventArgs<T> args = new AssociationChangedEventArgs<T>(index, item);
handler(this, args);
}
}
public event AssociationItemChangedEventHandler<T> ItemChanged;
protected void OnItemChanged(int index, T originalItem, T newItem)
{
AssociationItemChangedEventHandler<T> handler = ItemChanged;
if (handler != null)
{
AssociationItemChangedEventArgs<T> args = new AssociationItemChangedEventArgs<T>(index, newItem, originalItem);
handler(this, args);
}
}
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region IList Members
int IList.Add(object value)
{
Add((T)value);
return IndexOf((T)value);
}
bool IList.Contains(object value)
{
return Contains((T)value);
}
int IList.IndexOf(object value)
{
return IndexOf((T)value);
}
void IList.Insert(int index, object value)
{
Insert(index, (T)value);
}
bool IList.IsFixedSize
{
get { return Adaptee.IsFixedSize; }
}
void IList.Remove(object value)
{
Remove((T)value);
}
object IList.this[int index]
{
get
{
return this[index];
}
set
{
this[index] = (T)value;
}
}
#endregion
#region ICollection Members
void ICollection.CopyTo(Array array, int index)
{
Adaptee.CopyTo(array, index);
}
bool ICollection.IsSynchronized
{
get { return Adaptee.IsSynchronized; }
}
object ICollection.SyncRoot
{
get { return Adaptee.SyncRoot; }
}
#endregion
}
Note that these events will not be executed if you do Child.Parent = p; For this case you have to set HasUserCode=True on Child.Parent as normal and react accordingly.
Comments