Open astrohart opened 1 year ago
@madskristensen See also: https://social.msdn.microsoft.com/Forums/en-US/7bead8ae-a365-4dea-9c24-7fedf55fd58b/how-to-get-custom-context-menu-item-hiddenvisible-in-run-time?forum=vsx
UPDATE
I am referring to the post that is linked above.
I enabled my extension to just not be loaded unless a Solution is currently open, which, in principle, addressed the issue I was concerned about. However, the preferred UI/UX is to always show the command on the Edit menu, but just gray it out when it does not apply.
@madskristensen
UPDATE
I located this Stack Overflow post but I cannot find the OnBeforeQueryStatus
method to override, that is mentioned.
I do see a BeforeQueryStatus
method that I can override, defined in your BaseCommand
class:
using Community.VisualStudio.Toolkit;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System;
using Task = System.Threading.Tasks.Task;
namespace CopyActiveProjectFilename
{
[Command(PackageIds.CopyActiveProjectFilenameCommand)]
internal sealed class
CopyActiveProjectFilenameCommand : BaseCommand<
>
{
/// <summary>Override this method to control the commands visibility and other properties.</summary>
protected override void BeforeQueryStatus(EventArgs e)
=> base.BeforeQueryStatus(e);
/* ... */
}
}
However, this does me no good, since -- and I am not trying to criticize -- but you hide the sender
parameter in the BaseCommand
implementation...basically you have a line
// Decompiled with JetBrains decompiler
// Type: Community.VisualStudio.Toolkit.BaseCommand
// Assembly: Community.VisualStudio.Toolkit, Version=16.0.430.0, Culture=neutral, PublicKeyToken=79441d341a79572c
// MVID: DE52B160-B802-48FF-B8F4-0A0035C0AB3F
// Assembly location: C:\Users\Brian Hart\.nuget\packages\community.visualstudio.toolkit.16\16.0.430\lib\net472\Community.VisualStudio.Toolkit.dll
// XML documentation location: C:\Users\Brian Hart\.nuget\packages\community.visualstudio.toolkit.16\16.0.430\lib\net472\Community.VisualStudio.Toolkit.xml
using Microsoft.VisualStudio.Shell;
using System;
#nullable enable
namespace Community.VisualStudio.Toolkit
{
/// <summary>A base class that makes it easier to handle commands.</summary>
/// <example>
/// <code>
/// [Command("489ba882-f600-4c8b-89db-eb366a4ee3b3", 0x0100)]
/// public class TestCommand : BaseCommand<TestCommand>
/// {
/// protected override Task ExecuteAsync(OleMenuCmdEventArgs e)
/// {
/// return base.ExecuteAsync(e);
/// }
/// }
/// </code>
/// </example>
public abstract class BaseCommand
{
/* ... */
internal virtual void BeforeQueryStatus(object sender, EventArgs e) => this.BeforeQueryStatus(e);
/// <summary>Override this method to control the commands visibility and other properties.</summary>
protected virtual void BeforeQueryStatus(EventArgs e)
{
}
/* ... */
}
}
@madskristensen the overridable version of the method hides the sender
parameter, but that is what I need, because I have to cast that sender
to OleMenuCommand
to then call its Visible
, Enable
, Text
properties.
As an example, the code I'd write in my CopyActiveProjectFilenameCommand
class, were I able to override the correct method, would be the following:
using Community.VisualStudio.Toolkit;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System;
using Task = System.Threading.Tasks.Task;
namespace CopyActiveProjectFilename
{
[Command(PackageIds.CopyActiveProjectFilenameCommand)]
internal sealed class
CopyActiveProjectFilenameCommand : BaseCommand<
CopyActiveProjectFilenameCommand>
{
/// <summary>Override this method to control the commands visibility and other properties.</summary>
protected override async Task BeforeQueryStatusAsync(object sender, EventArgs e)
{
base.BeforeQueryStatus(sender, e);
if (sender is not OleMenuCommand menuCommand) return;
menuCommand.Enabled = await VS.Solutions.IsOpenAsync();
}
/* ... */
}
}
See my changes? First of all, make the internal virtual void
version of the method the protected virtual void
version that people can override -- so we can get at that sender
variable.
Secondly, I suggest you append Async
to the name of the method.
Thirdly, I suggest that you make the return type async Task
not void
.
The last change is needed so I can ask Visual Studio whether a solution is currently open using your VS.Solutions.IsOpenAsync()
method.
Thank you for your consideration.
so we can get at that
sender
variable.
The sender
variable is equivalent to this.Command
, which is why it's hidden.
this.Command.Enabled = ...
Thirdly, I suggest that you make the return type
async Task
notvoid
.
The problem with that is BeforeQueryStatus
cannot be async. The method is basically an event handler, and as soon as the event has been raised, the menu will be displayed. If it becomes async, then the menu can end up being shown before the async operation completes, which means the menu is shown with the wrong state.
If you need to call async methods from a synchronous method (like BeforeQueryStatus
), you should JoinableTaskFactory.Run
like so:
protected override void BeforeQueryStatus(EventArgs e)
{
Package.JoinableTaskFactory.Run(async () =>
{
Command.Enabled = await VS.Solutions.IsOpenAsync();
});
}
Alternatively, you can avoid the asynchronous call completely. VS.Solutions.IsOpenAsync()
is only async because it needs to get the IVsSolution
service asynchronously. That can be done immediately after the command is created. Then BeforeQueryStatus
just needs to call the IVsSolution.IsOpen()
method.
private IVsSolution? _solution;
protected async override Task InitializeCompletedAsync()
{
_solution = await VS.Services.GetSolutionAsync();
}
protected override void BeforeQueryStatus(EventArgs e)
{
ThreadHelper.ThrowIfNotOnUIThread();
Command.Enabled = _solution?.IsOpen() ?? false;
}
@madskristensen I am making a new extension with this toolkit. In my extension, I want to take a certain action but only if a Solution is currently loaded.
I have been following the tutorial: https://www.vsixcookbook.com/getting-started/your-first-extension.html
Great tutorial by the way, and I am also thrilled that this package has been put together.
One recipe I think you forgot to add to your "cookbook" was graying out/ungraying commands, showing/hiding menu commands, and/or changing the text of a menu command. I also am struggling to implement those tips that I've Googled, with your package.
My command appears on the Edit menu in Visual Studio. I'd like to gray out my command if there is no solution currently open.
I am drawing a blank as to how to correctly implement this. Any assistance would be appreciated. Thank you.