如何在 VSPackage 加载解决方案时收到通知?

3

当解决方案完全加载时,我希望能收到通知。受这个答案启发,我尝试实现IVsSolutionEvents

当我加载一个包含两个C#项目的解决方案,并等待加载完成后最终关闭Visual Studio 2017,输出只显示以下跟踪消息:

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

这是期望的行为吗?为什么没有调用OnAfterOpenSolution?

以下是包的实现:

[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
}

1
为什么不使用UICONTEXT_SolutionExists? - Simon Mourier
我正在开发的包仅为具有多个项目的解决方案提供功能。我没有意识到[ProvideAutoLoad]参数对解决方案事件的影响。感谢您的提示。 - CodeFox
2个回答

7
是的,这是设计上的。观察到这种行为的原因是因为所涉及的事件在您的包加载之前触发。您可以通过观察到,当您关闭解决方案,然后重新打开它(在加载您的包之后),事件确实会触发。第二次尝试时,您将看到该事件被触发。
您的示例正在使用SolutionHasMultipleProjects上下文guid,这可以确保仅在解决方案具有多个项目时才加载您的包。IDE确定此内容的唯一方法是首先让解决方案加载,然后设置UI上下文。因此,您设置事件处理程序时晚了一点。
如果要确保收到特定通知,则可以注册要使用NoSolution_string和SolutionExists_string加载的包。但这有点邪恶,因为这会强制始终加载您的包(即使不需要它),这是一个不太理想的解决方案。
使用SolutionExistsAndFullyLoadedContext可能是更好的方法。当您的包最初加载时,您将知道已满足该条件,并且可以在从包的Initialize重写返回之前运行处理程序代码。并且将在后续解决方案加载时调用原始IVsSolutionEvents处理程序。
您还可以考虑注册/使用基于规则的UI上下文,如下所述:

如何使用基于规则的 UI 上下文来开发 Visual Studio 扩展

真诚地, Ed Dore


非常好的解释 - 我之前不知道 [ProvideAutoLoad] 中的 UIContext 如何影响事件处理。还要感谢您提到了_基于规则的 UI 上下文_! - CodeFox

0

我知道答案,但不知道该如何将类代码放入这里

添加一个异步包调用VSPackageEvents,并将此代码放入文件中, 并将dte替换为您的包dte

using System;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.Win32;
using Task = System.Threading.Tasks.Task;

namespace VSIXProject
{
    /// <summary>
    /// This is the class that implements the package exposed by this assembly.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The minimum requirement for a class to be considered a valid package for Visual Studio
    /// is to implement the IVsPackage interface and register itself with the shell.
    /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
    /// to do it: it derives from the Package class that provides the implementation of the
    /// IVsPackage interface and uses the registration attributes defined in the framework to
    /// register itself and its components with the shell. These attributes tell the pkgdef creation
    /// utility what data to put into .pkgdef file.
    /// </para>
    /// <para>
    /// To get loaded into VS, the package must be referred by &lt;Asset Type="Microsoft.VisualStudio.VsPackage" ...&gt; in .vsixmanifest file.
    /// </para>
    /// </remarks>
    [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
    [InstalledProductRegistration("#1110", "#1112", "1.0", IconResourceID = 1400)] // Info on this package for Help/About
    [Guid(VSPackageEvents.PackageGuidString)]
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")]
#pragma warning disable VSSDK004 // Use BackgroundLoad flag in ProvideAutoLoad attribute for asynchronous auto load.
    [ProvideAutoLoad(UIContextGuids80.SolutionExists)]
#pragma warning restore VSSDK004 // Use BackgroundLoad flag in ProvideAutoLoad attribute for asynchronous auto load.
    public sealed class VSPackageEvents : AsyncPackage
    {
        EnvDTE.DTE dte = null;
        EnvDTE.Events events = null;
        EnvDTE.SolutionEvents solutionEvents = null;
        /// <summary>
        /// VSPackageEvents GUID string. Replace this Guid
        /// </summary>
        public const string PackageGuidString = "12135331-70d8-48bb-abc7-5e5ffc65e041";

        /// <summary>
        /// Initializes a new instance of the <see cref="VSPackageEvents"/> class.
        /// </summary>
        public VSPackageEvents()
        {
            // Inside this method you can place any initialization code that does not require
            // any Visual Studio service because at this point the package object is created but
            // not sited yet inside Visual Studio environment. The place to do all the other
            // initialization is the Initialize method.
        }
        private void SolutionEvents_Opened()
        {
// put your code here after opened event
        }
        private void SolutionEvents_AfterClosing()
        {
// put your code here after closed event
        }

        /// <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>
        /// <param name="cancellationToken">A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down.</param>
        /// <param name="progress">A provider for progress updates.</param>
        /// <returns>A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method.</returns>
        protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
        {
            // When initialized asynchronously, the current thread may be a background thread at this point.
            // Do any initialization that requires the UI thread after switching to the UI thread.
            await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
            // replace dte from next line with your dte package
            // dte = Utilities.Utility.GetEnvDTE(this);
            events = dte.Events;       
            solutionEvents = events.SolutionEvents;
            solutionEvents.Opened += SolutionEvents_Opened;
            solutionEvents.AfterClosing += SolutionEvents_AfterClosing;
        }    

    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接