The VBIDE API exposes the wonderfully cryptic _dispVBComponentsEvents
interface (among others), which look like something that I could use to capture various interesting events in the VBE.
So I implemented the interface in a class that intends to capture the event and raise a "normal" .net event for the rest of my application to handle, like this:
public class VBComponentsEventDispatcher : _dispVBComponentsEvents
{
public event EventHandler<DispatcherEventArgs<VBComponent>> ComponentAdded;
public void ItemAdded(VBComponent VBComponent)
{
OnDispatch(ComponentAdded, VBComponent);
}
public event EventHandler<DispatcherEventArgs<VBComponent>> ComponentRemoved;
public void ItemRemoved(VBComponent VBComponent)
{
OnDispatch(ComponentRemoved, VBComponent);
}
public event EventHandler<DispatcherRenamedEventArgs<VBComponent>> ComponentRenamed;
public void ItemRenamed(VBComponent VBComponent, string OldName)
{
var handler = ComponentRenamed;
if (handler != null)
{
handler.Invoke(this, new DispatcherRenamedEventArgs<VBComponent>(VBComponent, OldName));
}
}
public event EventHandler<DispatcherEventArgs<VBComponent>> ComponentSelected;
public void ItemSelected(VBComponent VBComponent)
{
OnDispatch(ComponentSelected, VBComponent);
}
public event EventHandler<DispatcherEventArgs<VBComponent>> ComponentActivated;
public void ItemActivated(VBComponent VBComponent)
{
OnDispatch(ComponentActivated, VBComponent);
}
public event EventHandler<DispatcherEventArgs<VBComponent>> ComponentReloaded;
public void ItemReloaded(VBComponent VBComponent)
{
OnDispatch(ComponentReloaded, VBComponent);
}
private void OnDispatch(EventHandler<DispatcherEventArgs<VBComponent>> dispatched, VBComponent component)
{
var handler = dispatched;
if (handler != null)
{
handler.Invoke(this, new DispatcherEventArgs<VBComponent>(component));
}
}
}
I'm hoping to use the class like this:
var componentsEvents = new VBComponentsEventDispatcher();
componentsEvents.ComponentAdded += componentsEvents_ComponentAdded;
componentsEvents.ComponentActivated += componentsEvents_ComponentActivated;
//...
void componentsEvents_ComponentAdded(object sender, DispatcherEventArgs<VBComponent> e)
{
Debug.WriteLine(string.Format("Component '{0}' was added.", e.Item.Name));
}
void componentsEvents_ComponentActivated(object sender, DispatcherEventArgs<VBComponent> e)
{
Debug.WriteLine(string.Format("Component '{0}' was activated.", e.Item.Name));
}
But it doesn't work, I get no debug output and a breakpoint isn't hit. Clearly I don't know what I'm doing. MSDN is completely useless on the subject, and finding documentation about this is harder than finding the maiden name of the third wife of Henry VIII.
What am I doing wrong, and how do I get this to work? Am I on the right track?
Yes. What you have in an event sink - you're missing a bit of code to register the sink with the COM servers.
The
VBProjects
andVBComponents
interfaces implement (somewhere very deep) theIConnectionPointContainer
interface - you need to use that to collectIConnectionPoint
instances. And to un-register the sink, you'll need a data structure to remember theint cookie
that the registration step gives you.Here's a rough example - say you have an
App
class with these fields:Somewhere in the constructor, you'll register the sink using
IConnectionPoint.Advise
, and register your custom event handlers:Then, when a project is added, you'll register a sink for each component using
IConnectionPoint.Advise
, then you can register your custom event handlers, and add an entry to your dictionary:When a project is removed, you un-register the sinks using
IConnectionPoint.Unadvise
, and remove the dictionary entry:And then you can run any code you want in your handler:
If you have a
Dispose
method in yourApp
class, that would be a good place to clean up any remnants:The
System.Runtime.InteropServices
namespace exposes a staticComEventsHelper
class to connect managed delegates to unmanaged dispatch sources. This basically does the same thing as the other answer, but the connection points are handled within the runtime callable wrapper instead of having to be managed explicitly from the calling code (thus making it somewhat more robust). I suspect that this is how PIAs are handling source interfaces internally (decompiling theMicrosoft.Vbe.Interop
in question mangled it enough that it's hard to tell).In this case, for some unfathomable reason the interface in question isn't declared as a source interface, so the PIA build didn't connect the event handlers in the runtime wrapper. So... you can wire up the handlers manually in the wrapper class and forward them as wrapped events, but still leave the heavy lifting (and thread safety management) of dealing with the connection points to the RCW. Note that you need 2 pieces of information from the referenced type library - the guid of the
_dispVBComponentsEvents
interface and theDispId
's of the unmanaged events that you're interested in listening to:Then, wire them up in the ctor of the class wrapper (only one shown for the sake of brevity)...
...and forward the events:
When you're done, un-register the delegate by calling
ComEventsHelper.Remove
:The example above uses a wrapper class per the question, but the same method could be used from anywhere if you need to attach additional functionality to a COM event before handling it or passing it on to other listeners.