EventSample VB v2.1 – Exposing Generic Events in Entry Point classes


posted by Melody
10-08-2008

Downloads: 59
File size: 275.3kB
Views: 247
EventSample VB v2.1 – Exposing Generic Events in Entry Point classes

At this time there is no direct way to expose an EventHandler<T> in the host to VSTA add-ins.  This sample uses a workaround which involves using non-generic event handling between the host and proxy, and generic event handling between the proxy and add-in.  This is accomplished by raising the non-generic events in the host and adding methods to the proxy to translate the events and event args.  The host and proxy must be modified; however, the host item provider and host type map provider do not require any alterations.

This workaround only works for generic events raised by the entry point class the add-in is based on.  There is a separate workaround for generic events raised by other classes which is more involved. 

This sample is a modified version of the Event Sample VB v 2.  To run this sample, extract the zip file and run the included setup file SetupEventSample VB v 2.1.js.  Build the solution to move the proxy to the GAC and build the included sample add-in to load the assembly.  Both events hooked into the by the add-in will be triggered by entering a message into the input box and selecting “Add”.

The proxy and add-ins use tight versioning, the proxy assembly is marked as version 2.1 and the project templates included use this assembly as version specific.  To use the included AutoProxyGen Input.xml file to regenerate the main proxy file, update the file locations (the proxy included will work so this is not necessary).  Only one modification was done to the auto-generated proxy file, in the entry point class and it’s base the non-generic events were changed from public to private so that only the generic events are visible to add-ins. 

Workaround Components:

  • Host

1)       Define custom event args. 

2)       Define an EventHandler<T> in the EntryPoint class using the custom event args (must be the entry point the add-in will be based on).

3)       Define a delegate using the custom event args.  This will be used to communicate between the host and proxy.

4)       Define an event based on the delegate defined in 3 above.  This will be used to communicate between the host and proxy. 

5)       Anytime the EventHandler<T> is raised, raise the event based on the delegate, this event will trigger the EventHandler<T> in the proxy/add-in.

 

  • Proxy

6)       Create the proxy as normal.  This will define in the proxy:

a.        The custom event args from 1.

b.       The delegate using the custom event args from 3.

c.        The event based on the delegate from 4 in both the class and the entry point class.

 

This will skip:

a.        The EventHandler<T> (all generics are skipped). 

ProxyGen.exe Warning: 21040 : The following event type MainApplication contains generic type information and will not be parsed: cEventT.

 

7)       Re-implement the custom EventArgs in the proxy while keeping the auto-generated custom EventArgs.  Do this by using a different name for the re-implemented custom event args, or nesting them in a class.

 

8)       Expand the partially defined entry point class in the proxy (suggest to do this is a separate code file) adding:

a.        A private event EventHandler<T> for the custom event args for internal use by the proxy.  Do not define the add and remove methods for this event. 

b.       A flag for the private event defined above initialized to false.

c.        The EventHandler<T> defined in the host skipped by ProxyGen using the re-implemented event args deined in 7 above.  Define the add and remove methods for this event.  In the Add method, hook the value passed in to the private event defined in 8a above.  Then, if the flag defined in 8b is false, hook the auto-generated generic event handler defined in the main proxy file to a new method to be defined in the expanded entry point class (defined below) and set the flag to true.  In the remove method, remove the value from the private event defined in 8a above.

d.       Add a method which satisfies the signature for the generic event handling, using the original event args.  In this method, translate the original event args to the re-implemented event args.  Use the re-implemented event args to raise the private event defined in 3a.

 

9)       Expand the partially defined class in the proxy adding the EventHandler<T> (suggest to do this in the same separate code file).

 

Code sample Host:

'1) Define the custom event args

Public Class cEventArgs

    Inherits System.EventArgs

 

    Private _myObject As MainObject

    Public Property MyObject() As MainObject

        Get

            Return _myObject

        End Get

        Set(ByVal value As MainObject)

            _myObject = value

        End Set

    End Property

 

    Public Sub New(ByVal nObject As MainObject)

        _myObject = nObject

    End Sub

 

End Class

 

'3) Define a delegate using the custom event args. 

'   This will be used to communicate between the host and proxy.

Public Delegate Sub cEventHandler(ByVal sender As Object, ByVal e As cEventArgs)

 

Public Class MainApplication

    '2) Define an EventHandler<T> in the EntryPoint class using the custom event args

    Public Event cEventT As EventHandler(Of cEventArgs)

 

    '4) Define an event based on the delegate defined above. 

    '   This will be used to communicate between the host and proxy. 

    Public Event cEventX As cEventHandler

 

    ' 5) Anytime the EventHandler<T> is raised, raise the event based on the delegate. 

    '    This event will trigger the EventHandler<T> in the proxy/add-in.

    Public Sub HookUpEvents()

        AddHandler Me.cEventT, AddressOf RaiseGenericEventForVSTA_cEventT

    End Sub

 

    Private Sub RaiseGenericEventForVSTA_cEventT(ByVal sender As Object, ByVal e As cEventArgs)

        RaiseEvent cEventX(Me, e)

    End Sub

 

End Class

 

Code sample Auto-generated Proxy (attributes and delayed event hookups removed):

namespace EventSample

{

    //6a) auto-generated custom event args from 1

    public abstract partial class cEventArgs : global::System.MarshalByRefObject

    {

        public virtual global::EventSample.MainObject MyObject

        { //auto-generated not implemented code

        }

    }

    //6b) auto-generated delegate using the event args from 3

    public delegate void cEventHandler(object sender, global::EventSample.cEventArgs e);

 

    //6c) auto-generated event based on the delegate from 4

    public abstract partial class MainApplication : global::System.MarshalByRefObject

    {

        virtual public event global::EventSample.cEventHandler cEventX

        {

            add { throw new global::System.NotImplementedException(); }

            remove { throw new global::System.NotImplementedException(); }

        }

    }

 

    //6c) auto-generated event based on the delegate from 4

    public partial class MainApplicationEntryPoint : global::Microsoft.VisualStudio.Tools.Applications.Runtime.IExtendedEntryPoint

    {

        public event global::EventSample.cEventHandler cEventX

        {

            add

            {

                if (this.remoteObject != null)

                   ((EventSample.MainApplication)(this.remoteObject)).cEventX += value;

            }

 

            remove

            {

                if (this.remoteObject != null)

                    ((EventSample.MainApplication)(this.remoteObject)).cEventX -= value;

            }

        }

 

Code sample Expanded Proxy:

    //7) Re-implemnted custom event args for internal use by the proxy.

    //   Defined in as a nested class to avoid naming collision

    public partial class EventSampleEventArgs

    {

        public partial class cEventArgs : System.EventArgs

        {

            public MainObject MyObject { get; set; }

 

            public cEventArgs(MainObject inObject)

            {

                MyObject = inObject;

            }

        }

    }

 

    //8) Expand the partially defined entry point class

    public partial class MainApplicationEntryPoint

    {

        //8a) Add a private EventHandler<T> for internal use

        private event System.EventHandler<EventSampleEventArgs.cEventArgs> _cEventT;

 

        //8b) Add a flag to determine if the private event has been hooked up yet

        private bool cEventArgsHookedFlag = false;

 

        //8c) Add a public EventHandler<T> skipped by ProxyGen

        //    In the add method, hook the value to the private EventHandler<T>

        //    Hook the auto-generated non-generic event handler to method which

        //    translates the event args and raises the private EventHandler<T>

        public event System.EventHandler<EventSampleEventArgs.cEventArgs> cEventT

        {

            add

            {

                //add the add-in hookup to the private event handler

                this. _cEventT += value;

 

                //If the non-generic event isn’t hooked into yet, hook into it

                if (!cEventArgsHookedFlag)

                {

                    cEventArgsHookedFlag = true;

 

                    this.cEventX += new MessageAddedEventHandler(MainApplicationEntryPoint_ cEventX);

                   

                }

             }

            remove

            {

                //remove the add-in hookup from the private event handler

                this. _cEventT -= value;

            }

        }

 

        //8d) Add method with the signature for the generic event handling,

        //    using the auto-generated custom event args.

        void MainApplicationEntryPoint_ cEventX (object sender, EventSample.MessageAddedEventArgs e)

        {

            //Raise the private generic event       

            if (this. _cEventT != null)

            {

                EventSampleEventArgs.MessageAddedEventArgs args = new EventSampleEventArgs.MessageAddedEventArgs(e.MyObject);

                _cEventT (sender, args);

            }

        }

    }

 

    //9) Expand the partially defined entry point class base class

    //   Adding the EventHandler<T> event.

    public abstract partial class MainApplication : global::System.MarshalByRefObject

    {       

        virtual public event System.EventHandler<EventSampleEventArgs.cEventArgs> cEventT;

    }

Copyright Summit Software Company, 2008. All rights reserved.