VBIDE API公开了奇妙的神秘_dispVBComponentsEvents
界面(以及其他界面),看起来像我可以用来捕捉VBE中各种有趣事件的东西。
所以我在一个类中实现了接口,该类打算捕获事件并为我的应用程序的其余部分引发一个“正常”.net事件,如下所示:
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));
}
}
}
我希望能像这样使用这个类:
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));
}
但它不起作用,我没有调试输出,并且没有命中断点。显然我不知道我在做什么。 MSDN在这个问题上完全没用,找到关于这个的文档比找到亨利八世的第三任妻子的婚前姓更难。
我做错了什么,如何让它发挥作用?我是在正确的轨道上吗?
答案 0 :(得分:5)
我是在正确的轨道上吗?
是。您在事件接收器中拥有的内容 - 您缺少一些代码来使用COM服务器注册接收器。
VBProjects
和VBComponents
接口实现(某处非常深)IConnectionPointContainer
接口 - 您需要使用它来收集IConnectionPoint
个实例。要取消注册接收器,您需要一个数据结构来记住注册步骤为您提供的int cookie
。
这是一个粗略的例子 - 假设您有一个App
类,其中包含以下字段:
private readonly IConnectionPoint _projectsEventsConnectionPoint;
private readonly int _projectsEventsCookie;
private readonly IDictionary<VBComponents, Tuple<IConnectionPoint, int>> _componentsEventsConnectionPoints =
new Dictionary<VBComponents, Tuple<IConnectionPoint, int>>();
在构造函数中的某处,您将使用IConnectionPoint.Advise
注册接收器,并注册自定义事件处理程序:
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);
然后,当添加项目时,您将使用IConnectionPoint.Advise
为每个组件注册一个接收器,然后您可以注册自定义事件处理程序,并在您的字典中添加一个条目:
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));
}
删除项目后,使用IConnectionPoint.Unadvise
取消注册接收器,并删除字典条目:
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);
}
}
然后你可以在你的处理程序中运行你想要的任何代码:
void sink_ComponentAdded(object sender, DispatcherEventArgs<VBComponent> e)
{
_parser.State.OnParseRequested(e.Item);
}
如果您的Dispose
课程中有App
方法,那么这将是清理任何遗留物的好地方:
public void Dispose()
{
_projectsEventsConnectionPoint.Unadvise(_projectsEventsCookie);
foreach (var item in _componentsEventsConnectionPoints)
{
item.Value.Item1.Unadvise(item.Value.Item2);
}
}
答案 1 :(得分:1)
System.Runtime.InteropServices
命名空间公开了一个静态ComEventsHelper
类,用于将托管代理连接到非托管调度源。这基本上与other answer完全相同,但连接点在运行时可调用包装器中处理,而不必从调用代码显式管理(从而使其更加健壮)。我怀疑这就是PIA在内部处理源接口的方式(对问题Microsoft.Vbe.Interop
进行反编译已经足够严重,以至于很难说明。)
在这种情况下,由于某些不可思议的原因,所讨论的接口未被声明为源接口,因此PIA构建没有在运行时包装器中连接事件处理程序。所以...你可以在包装器类中手动连接处理程序并将它们作为包装事件转发,但仍然需要处理连接点到RCW的繁重(和线程安全管理)。请注意,您需要来自引用类型库的2条信息 - _dispVBComponentsEvents
界面的guid以及您有兴趣收听的非托管事件的DispId
:
private static readonly Guid VBComponentsEventsGuid = new Guid("0002E116-0000-0000-C000-000000000046");
private enum ComponentEventDispId
{
ItemAdded = 1,
ItemRemoved = 2,
ItemRenamed = 3,
ItemSelected = 4,
ItemActivated = 5,
ItemReloaded = 6
}
然后,将它们连接到类包装器的ctor(为简洁起见只显示一个)......
private delegate void ItemAddedDelegate(VB.VBComponent vbComponent);
private readonly ItemAddedDelegate _componentAdded;
public VBComponents(VB.VBComponents target)
{
_target = target;
_componentAdded = OnComponentAdded;
ComEventsHelper.Combine(_target,
VBComponentsEventsGuid,
(int)ComponentEventDispId.ItemAdded,
_componentAdded);
}
...并转发事件:
public event EventHandler<DispatcherEventArgs<IVBComponent>> ComponentAdded;
private void OnComponentAdded(VB.VBComponent vbComponent)
{
OnDispatch(ComponentAdded, VBComponent);
}
private void OnDispatch(EventHandler<DispatcherEventArgs<IVBComponent>> dispatched, VB.VBComponent component)
{
var handler = dispatched;
if (handler != null)
{
handler.Invoke(this, new DispatcherEventArgs<IVBComponent>(new VBComponent(component)));
}
}
完成后,请致电ComEventsHelper.Remove
取消注册代表:
ComEventsHelper.Remove(_target,
VBComponentsEventsGuid,
(int)ComponentEventDispId.ItemAdded,
_componentAdded);
上面的示例根据问题使用了一个包装类,但是如果您需要在处理COM事件或将其传递给其他侦听器之前将其他功能附加到COM事件,则可以从任何地方使用相同的方法。