Search code examples
c#eventscom-interopvbe

Where are the VBProjectsEvents?


Using Microsoft.Vbe.Interop in C#, I can access CommandBarEvents and ReferencesEvents via VBE.Events.

However the ever-so helpful MSDN documentation seems to indicate that there's a VBProjectsEvents that I could use to notify my add-in when a project is added or removed to/from the VBE... which is exactly what I'm trying to achieve here.

I can see that _VBProjectsEvents interface in the object browser, but no implementation for it (as opposed to the _CommandBarControlsEvents interface, which is implemented by the CommandBarEventsClass), using ReSharper's go to implementation feature.

Is there an implementation of the _VBProjectsEvents interface anywhere? If not, then how does one go about being notified of a VBProject being removed from the IDE?


Solution

  • You need to create a sink for these events.

    Implement the _dispVBProjectsEvents dispatch interface - here's an implementation that responds to these events by invoking regular managed .net events, effectively "wrapping" the VBProjects events:

    public class VBProjectsEventsSink : _dispVBProjectsEvents
    {
        public event EventHandler<DispatcherEventArgs<VBProject>> ProjectAdded;
        public void ItemAdded(VBProject VBProject)
        {
            if (VBProject.Protection == vbext_ProjectProtection.vbext_pp_none)
            {
                OnDispatch(ProjectAdded, VBProject);
            }
        }
    
        public event EventHandler<DispatcherEventArgs<VBProject>> ProjectRemoved;
        public void ItemRemoved(VBProject VBProject)
        {
            if (VBProject.Protection == vbext_ProjectProtection.vbext_pp_none)
            {
                OnDispatch(ProjectRemoved, VBProject);
            }
        }
    
        public event EventHandler<DispatcherRenamedEventArgs<VBProject>> ProjectRenamed;
        public void ItemRenamed(VBProject VBProject, string OldName)
        {
            var handler = ProjectRenamed;
            if (handler != null && VBProject.Protection == vbext_ProjectProtection.vbext_pp_none)
            {
                handler.Invoke(this, new DispatcherRenamedEventArgs<VBProject>(VBProject, OldName));
            }
        }
    
        public event EventHandler<DispatcherEventArgs<VBProject>> ProjectActivated;
        public void ItemActivated(VBProject VBProject)
        {
            if (VBProject.Protection == vbext_ProjectProtection.vbext_pp_none)
            {
                OnDispatch(ProjectActivated, VBProject);
            }
        }
    
        private void OnDispatch(EventHandler<DispatcherEventArgs<VBProject>> dispatched, VBProject project)
        {
            var handler = dispatched;
            if (handler != null)
            {
                handler.Invoke(this, new DispatcherEventArgs<VBProject>(project));
            }
        }
    }
    

    The DispatcherEventArgs class is just a convenient way to expose the VBProject item involved with the event, and it can be reused for other sinks:

    public class DispatcherEventArgs<T> : EventArgs 
        where T : class
    {
        private readonly T _item;
    
        public DispatcherEventArgs(T item)
        {
            _item = item;
        }
    
        public T Item { get { return _item; } }
    }
    

    The client code needs to register the sink - and for that you need to keep an IConnectionPoint field and its int cookie:

    private readonly IConnectionPoint _projectsEventsConnectionPoint;
    private readonly int _projectsEventsCookie;
    

    The VBProjects collection implements the IConnectionPointContainer interface, which you need to use to find the connection point:

    var sink = new VBProjectsEventsSink();
    var connectionPointContainer = (IConnectionPointContainer)_vbe.VBProjects;
    var interfaceId = typeof (_dispVBProjectsEvents).GUID;
    connectionPointContainer.FindConnectionPoint(ref interfaceId, out _projectsEventsConnectionPoint);
    

    Once you have the IConnectionPoint, use the Advise method to "connect" your sink and retrieve a cookie:

    _projectsEventsConnectionPoint.Advise(sink, out _projectsEventsCookie);
    

    And then you can handle the sink events as you would any "normal" .net events:

    sink.ProjectAdded += sink_ProjectAdded;
    sink.ProjectRemoved += sink_ProjectRemoved;
    sink.ProjectActivated += sink_ProjectActivated;
    sink.ProjectRenamed += sink_ProjectRenamed;
    

    When you want to disconnect your sink, you pass the cookie to the Unadvise method of the IConnectionPoint instance:

    _projectsEventsConnectionPoint.Unadvise(_projectsEventsCookie);
    

    "Simple as that!"