Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to hook up a COM event dispatcher?

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?

like image 362
Mathieu Guindon Avatar asked Oct 26 '25 09:10

Mathieu Guindon


1 Answers

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 and VBComponents interfaces implement (somewhere very deep) the IConnectionPointContainer interface - you need to use that to collect IConnectionPoint instances. And to un-register the sink, you'll need a data structure to remember the int cookie that the registration step gives you.

Here's a rough example - say you have an App class with these fields:

private readonly IConnectionPoint _projectsEventsConnectionPoint;
private readonly int _projectsEventsCookie;

private readonly IDictionary<VBComponents, Tuple<IConnectionPoint, int>>  _componentsEventsConnectionPoints = 
    new Dictionary<VBComponents, Tuple<IConnectionPoint, int>>(); 

Somewhere in the constructor, you'll register the sink using IConnectionPoint.Advise, and register your custom event handlers:

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

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

_projectsEventsConnectionPoint.Advise(sink, out _projectsEventsCookie);

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:

void sink_ProjectAdded(object sender, DispatcherEventArgs<VBProject> e)
{
    var connectionPointContainer = (IConnectionPointContainer)e.Item.VBComponents;
    Guid interfaceId = typeof(_dispVBComponentsEvents).GUID;

    IConnectionPoint connectionPoint;
    connectionPointContainer.FindConnectionPoint(ref interfaceId, out connectionPoint);

    var sink = new VBComponentsEventsSink();
    sink.ComponentActivated += sink_ComponentActivated;
    sink.ComponentAdded += sink_ComponentAdded;
    sink.ComponentReloaded += sink_ComponentReloaded;
    sink.ComponentRemoved += sink_ComponentRemoved;
    sink.ComponentRenamed += sink_ComponentRenamed;
    sink.ComponentSelected += sink_ComponentSelected;

    int cookie;
    connectionPoint.Advise(sink, out cookie);

    _componentsEventsConnectionPoints.Add(e.Item.VBComponents, Tuple.Create(connectionPoint, cookie));
}

When a project is removed, you un-register the sinks using IConnectionPoint.Unadvise, and remove the dictionary entry:

void sink_ProjectRemoved(object sender, DispatcherEventArgs<VBProject> e)
{
    Tuple<IConnectionPoint, int> value;
    if (_componentsEventsConnectionPoints.TryGetValue(e.Item.VBComponents, out value))
    {
        value.Item1.Unadvise(value.Item2);
        _componentsEventsConnectionPoints.Remove(e.Item.VBComponents);
    }
}

And then you can run any code you want in your handler:

void sink_ComponentAdded(object sender, DispatcherEventArgs<VBComponent> e)
{
    _parser.State.OnParseRequested(e.Item);
}

If you have a Dispose method in your App class, that would be a good place to clean up any remnants:

public void Dispose()
{
    _projectsEventsConnectionPoint.Unadvise(_projectsEventsCookie);
    foreach (var item in _componentsEventsConnectionPoints)
    {
        item.Value.Item1.Unadvise(item.Value.Item2);
    }
}
like image 107
Mathieu Guindon Avatar answered Oct 28 '25 22:10

Mathieu Guindon



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!