How do I subscribe to solution and project events

2019-02-03 07:16发布

问题:

I'm developing a language service for Visual Studio through a VSPackage. I need to update my parse data whenever files get added/removed from the solution's projects.

I want to subscribe to solution and project events.

I tried as follows, but none of these events get fired when I add/remove projects to the solution or add/remove items to projects.

DTE dte = (DTE)languageService.GetService(typeof(DTE));
if (dte == null)
    return;

((Events2)dte.Events).SolutionEvents.ProjectAdded += SolutionEvents_ProjectAdded;
((Events2)dte.Events).SolutionEvents.ProjectRemoved += SolutionEvents_ProjectRemoved;
((Events2)dte.Events).ProjectItemsEvents.ItemAdded += ProjectItemsEvents_ItemAdded;
((Events2)dte.Events).ProjectItemsEvents.ItemRemoved += ProjectItemsEvents_ItemRemoved;

What's the best way to subscribe to these events from a VSPackage? Any help appreciated!

回答1:

Alternatively you could use IVsSolutionEvents3, which has much better events

[PackageRegistration( UseManagedResourcesOnly = true )]
[InstalledProductRegistration( "#110", "#112", "1.0", IconResourceID = 400 )]
// add these 2 Annotations to execute Initialize() immediately when a project is loaded
[ProvideAutoLoad( VSConstants.UICONTEXT.SolutionHasSingleProject_string )]
[ProvideAutoLoad( VSConstants.UICONTEXT.SolutionHasMultipleProjects_string )]
[Guid( GuidList.XYZ )]
public sealed class UnityProjectUpdateHandlerPackage : Package, IVsSolutionEvents3
{
    private DTE _dte;
    private IVsSolution solution = null;
    private uint _hSolutionEvents = uint.MaxValue;

    protected override void Initialize()
    {
        Trace.WriteLine( string.Format( CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString() ) );
        base.Initialize();

        this._dte = (DTE) this.GetService( typeof( DTE ) );

        AdviseSolutionEvents();
    }

    protected override void Dispose( bool disposing )
    {
        UnadviseSolutionEvents();

        base.Dispose( disposing );
    }

    private void AdviseSolutionEvents()
    {
        UnadviseSolutionEvents();

        solution = this.GetService( typeof( SVsSolution ) ) as IVsSolution;

        if ( solution != null )
        {
            solution.AdviseSolutionEvents( this, out _hSolutionEvents );
        }
    }

    private void UnadviseSolutionEvents()
    {
        if ( solution != null )
        {
            if ( _hSolutionEvents != uint.MaxValue )
            {
                solution.UnadviseSolutionEvents( _hSolutionEvents );
                _hSolutionEvents = uint.MaxValue;
            }

            solution = null;
        }
    }

    private Project[] GetProjects()
    {
        return _dte.Solution.Projects
            .Cast<Project>()
            .Select( x => ( (VSProject) x.Object ).Project )
            .ToArray();
    }

    public int OnAfterLoadProject( IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy )
    {
        // Do something
        return VSConstants.S_OK;
    }

    public int OnAfterOpenSolution( object pUnkReserved, int fNewSolution )
    {
        foreach ( var project in GetProjects() )
            ; // Do something

        return VSConstants.S_OK;
    }

    public int OnBeforeUnloadProject( IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy )
    {
        // Do something
        return VSConstants.S_OK;
    }

    public int OnAfterCloseSolution( object pUnkReserved )
    { return VSConstants.S_OK; }

    public int OnAfterClosingChildren( IVsHierarchy pHierarchy )
    { return VSConstants.S_OK; }

    public int OnAfterMergeSolution( object pUnkReserved )
    { return VSConstants.S_OK; }

    public int OnAfterOpenProject( IVsHierarchy pHierarchy, int fAdded )
    { return VSConstants.S_OK; }

    public int OnAfterOpeningChildren( IVsHierarchy pHierarchy )
    { return VSConstants.S_OK; }

    public int OnBeforeCloseProject( IVsHierarchy pHierarchy, int fRemoved )
    { return VSConstants.S_OK; }

    public int OnBeforeClosingChildren( IVsHierarchy pHierarchy )
    { return VSConstants.S_OK; }

    public int OnBeforeOpeningChildren( IVsHierarchy pHierarchy )
    { return VSConstants.S_OK; }

    public int OnBeforeCloseSolution( object pUnkReserved )
    { return VSConstants.S_OK; }

    public int OnQueryCloseProject( IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel )
    { return VSConstants.S_OK; }

    public int OnQueryCloseSolution( object pUnkReserved, ref int pfCancel )
    { return VSConstants.S_OK; }

    public int OnQueryUnloadProject( IVsHierarchy pRealHierarchy, ref int pfCancel )
    { return VSConstants.S_OK; }
}


回答2:

DTE Events are a little weird, you need to cache the event source object (SolutionEvents and ProjectItemEvents in your case), so that COM Interop knows to keep them alive.

public class MyClass
{
    SolutionEvents solutionEvents; 

    public void ConnectToEvents()
    {
        solutionEvents = ((Events2)dte.Events).SolutionEvents; 
        solutionEvents.ProjectAdded += OnProjectAdded; 
        // Etc 
    }
}

More on this @ http://msdn.microsoft.com/en-us/library/ms165650(v=vs.80).aspx



回答3:

Lets focus on ProjectAdded event (although described issue is exactly the same for the rest of the events).

The code sample you've shown attempts to register the SolutionEvents_ProjectAdded handler for the ProjectAdded event. However, the SolutionEvents object exposing the event, has lifetime scope limited to the closure of its wrapping method (you haven't shown its signature - let's call it Connect). After the control flow had left that scope, local object has already been garbage collected, so its event is never called:

Broken code:

public class Connector
{
    public void Connect()
    {
        ((Events2)dte.Events).SolutionEvents.ProjectAdded += SolutionEvents_ProjectAdded;
    }
    void SolutionEvents_ProjectAdded() 
    { 
        // callback is dead
    }
}

To fix that, you need to assign the SolutionEvents object to some variable, whose lifetime spans over the SolutionEvents_ProjectAdded handler - e.g. over the entire wrapping class. In the example below, the scope extends over the entire type (let's call it Connector), and ensures that the handler is accessible during the lifetime of that type:

Fixed code:

public class Connector
{
    SolutionEvents _solutionEvents;
    public void Connect()
    {
        _solutionEvents = ((Events2)dte.Events).SolutionEvents; 
        _solutionEvents.ProjectAdded += SolutionEvents_ProjectAdded;
    }
    void SolutionEvents_ProjectAdded() 
    { 
        // callback works
    }
}

To be more precise, check this MSDN reference - Scoping Variables Appropriately in Event Handlers:

A common mistake in programming event handlers is connecting the event handler to an object that has been declared with a scope too limited for the purpose of handling the event. The object must have a life that spans not just over the function that connects the callback method as an event handler of the object, but also over the callback method itself where the event is actually handled. Otherwise, if the object is out of scope and is no longer defined in the callback method, the callback method is not called and the event is not handled as desired.