2008-05-28

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


  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.

No comments: