Search code examples
c#vsixvspackage

How to be notified when a solution has been loaded for a VSPackage?


I would like to be notified when a solution has been completely loaded. Inspired by this answer I tried implementing IVsSolutionEvents.

When I load a solution with two C# projects, wait until loading has finished and finally close Visual Studio 2017, the output shows only the following trace messages:

VSTestPackage1: OnAfterOpenProject
VSTestPackage1: OnQueryCloseSolution
VSTestPackage1: OnQueryCloseProject
VSTestPackage1: OnQueryCloseProject
VSTestPackage1: OnBeforeCloseSolution
VSTestPackage1: OnQueryCloseProject
VSTestPackage1: OnBeforeCloseProject
VSTestPackage1: OnQueryCloseProject
VSTestPackage1: OnBeforeCloseProject
VSTestPackage1: OnAfterCloseSolution

Is this the expected behavior? Why is OnAfterOpenSolution not being invoked?

This is the package implementation:

[PackageRegistration(UseManagedResourcesOnly = true)]
[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
[Guid(PackageGuidString)]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly",
    Justification = "pkgdef, VS and vsixmanifest are valid VS terms")]
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionHasMultipleProjects_string)]
public sealed class VSPackage1 : Package, IVsSolutionEvents
{
    public const string PackageGuidString = "2e655097-9510-4cf8-b9d4-ceeacebbaf3c";

    private DTE _dte;
    private uint _hSolutionEvents = uint.MaxValue;
    private IVsSolution _solution;

    /// <summary>
    ///     Initialization of the package; this method is called right after the package is sited, so this is the place
    ///     where you can put all the initialization code that rely on services provided by VisualStudio.
    /// </summary>
    protected override void Initialize()
    {
        base.Initialize();

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

        AdviseSolutionEvents();
    }

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

        base.Dispose(disposing);
    }

    private void AdviseSolutionEvents()
    {
        UnadviseSolutionEvents();

        _solution = GetService(typeof(SVsSolution)) as IVsSolution;

        _solution?.AdviseSolutionEvents(this, out _hSolutionEvents);
    }

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

        _solution = null;
    }

    #region Implementation of IVsSolutionEvents

    int IVsSolutionEvents.OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded)
    {
        Trace.WriteLine("OnAfterOpenProject", "VSTestPackage1");
        return VSConstants.S_OK;
    }

    int IVsSolutionEvents.OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel)
    {
        Trace.WriteLine("OnQueryCloseProject", "VSTestPackage1");
        return VSConstants.S_OK;
    }

    int IVsSolutionEvents.OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved)
    {
        Trace.WriteLine("OnBeforeCloseProject", "VSTestPackage1");
        return VSConstants.S_OK;
    }

    int IVsSolutionEvents.OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy)
    {
        Trace.WriteLine("OnAfterLoadProject", "VSTestPackage1");
        return VSConstants.S_OK;
    }

    int IVsSolutionEvents.OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel)
    {
        Trace.WriteLine("OnQueryUnloadProject", "VSTestPackage1");
        return VSConstants.S_OK;
    }

    int IVsSolutionEvents.OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy)
    {
        Trace.WriteLine("OnBeforeUnloadProject", "VSTestPackage1");
        return VSConstants.S_OK;
    }

    int IVsSolutionEvents.OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
    {
        Trace.WriteLine("OnAfterOpenSolution", "VSTestPackage1");
        return VSConstants.S_OK;
    }

    int IVsSolutionEvents.OnQueryCloseSolution(object pUnkReserved, ref int pfCancel)
    {
        Trace.WriteLine("OnQueryCloseSolution", "VSTestPackage1");
        return VSConstants.S_OK;
    }

    int IVsSolutionEvents.OnBeforeCloseSolution(object pUnkReserved)
    {
        Trace.WriteLine("OnBeforeCloseSolution", "VSTestPackage1");
        return VSConstants.S_OK;
    }

    int IVsSolutionEvents.OnAfterCloseSolution(object pUnkReserved)
    {
        Trace.WriteLine("OnAfterCloseSolution", "VSTestPackage1");
        return VSConstants.S_OK;
    }

    #endregion
}

Solution

  • Yes, this by design. The reason for the observed behavior, is because the event in question is firing before your package is loaded. You can readily test by observing that the event does fire, when you close the solution, and then reopen it (after your package is loaded). On the 2nd go around, you'll see the event fire.

    Your example is using the SolutionHasMultipleProjects context guid, which ensures your package will only load if a solution has multiple projects. The only way for the IDE to determine that, would be to first have the solution load, and then set the UI context. So basically, you're setting up the event handler just a little too late.

    If you want to ensure you receive that particular notification, you can register your package to load with NoSolution_string and SolutionExists_string. But that's somewhat evil, as this forces your package to always load (even when it isn't needed), which is a less that desirable solution.

    Using SolutionExistsAndFullyLoadedContext might be a better way to go. When your package is initially loaded, you'll know that condition has been met, and you can run your handler code just before returning from your package's Initialize override. And your original IVsSolutionEvents handler will be invoked on subsequent solution loads.

    You might also want to consider registering/using a rule-based UI context as described below:

    How to: Use Rule-based UI Context for Visual Studio Extensions

    Sincerely, Ed Dore