如何在为 VSPackage 加载解决方案时收到通知?
How to be notified when a solution has been loaded for a VSPackage?
我希望在解决方案完全加载后收到通知。受此 answer 的启发,我尝试实施 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
}
是的,这是设计使然。观察到的行为的原因是因为相关事件在您的包加载之前触发。当您关闭解决方案然后重新打开它(在您的包加载之后)时,您可以通过观察事件是否触发来轻松地进行测试。 2号绕一圈,你会看到事件发生。
您的示例使用的是 SolutionHasMultipleProjects 上下文 GUID,它确保您的包仅在解决方案具有多个项目时才加载。 IDE 确定这一点的唯一方法是首先加载解决方案,然后设置 UI 上下文。所以基本上,您设置事件处理程序有点晚了。
如果您想确保收到该特定通知,您可以使用 NoSolution_string 和 SolutionExists_string 注册要加载的包。但这有点不好,因为这会强制您的包始终加载(即使不需要它),这是一个不太理想的解决方案。
使用 SolutionExistsAndFullyLoadedContext 可能是更好的方法。当你的包最初加载时,你会知道条件已经满足,你可以 运行 你的处理程序代码就在你的包的 Initialize 覆盖返回之前。并且您的原始 IVsSolutionEvents 处理程序将在后续解决方案加载时被调用。
您可能还想考虑 registering/using 基于规则的 UI 上下文,如下所述:
How to: Use Rule-based UI Context for Visual Studio Extensions
真诚的,
埃德·多尔
我知道答案,但我不知道如何将 class 代码放入
添加异步包调用 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 <Asset Type="Microsoft.VisualStudio.VsPackage" ...> 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;
}
}
}
我希望在解决方案完全加载后收到通知。受此 answer 的启发,我尝试实施 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
}
是的,这是设计使然。观察到的行为的原因是因为相关事件在您的包加载之前触发。当您关闭解决方案然后重新打开它(在您的包加载之后)时,您可以通过观察事件是否触发来轻松地进行测试。 2号绕一圈,你会看到事件发生。
您的示例使用的是 SolutionHasMultipleProjects 上下文 GUID,它确保您的包仅在解决方案具有多个项目时才加载。 IDE 确定这一点的唯一方法是首先加载解决方案,然后设置 UI 上下文。所以基本上,您设置事件处理程序有点晚了。
如果您想确保收到该特定通知,您可以使用 NoSolution_string 和 SolutionExists_string 注册要加载的包。但这有点不好,因为这会强制您的包始终加载(即使不需要它),这是一个不太理想的解决方案。
使用 SolutionExistsAndFullyLoadedContext 可能是更好的方法。当你的包最初加载时,你会知道条件已经满足,你可以 运行 你的处理程序代码就在你的包的 Initialize 覆盖返回之前。并且您的原始 IVsSolutionEvents 处理程序将在后续解决方案加载时被调用。
您可能还想考虑 registering/using 基于规则的 UI 上下文,如下所述:
How to: Use Rule-based UI Context for Visual Studio Extensions
真诚的, 埃德·多尔
我知道答案,但我不知道如何将 class 代码放入
添加异步包调用 VSPackageEvents,并将此代码放入文件中,然后 并用你的包 dte
替换 dteusing 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 <Asset Type="Microsoft.VisualStudio.VsPackage" ...> 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;
}
}
}