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
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