Creating async tool windows is a pain. There are three methods in the AsyncPackage
that you need to override, and it's not obvious that you should override all three of those methods. Plus there are some overridable methods for non-async tool windows, which adds to the confusion.
There's also some boilerplate code for creating/showing the tool window (which differs between VS 15 and 16) that could be put in a helper method.
Proposed API
Registering a Tool Window
We will need a new base class for packages to inherit from. This will provide helper methods for registering the tool window.
This base class would override the GetAsyncToolWindowFactory
, GetToolWindowTitle
and InitializeToolWindowAsync
methods (and the equivalents for VS15) to take care of the work required to create a tool window.
abstract class ToolkitPackage : AsyncPackage {
void AddToolWindow<T>(string title);
void AddToolWindow<T>(string title, Func<object> initializer);
void AddToolWindow<T>(string title, Func<Taks<object>> initializer);
}
The AddToolWindow
method would be called by the derived class in the InitializeAsync
method (similar to how you would use the existing AsyncPackage.AddOptionKey
and AsyncPackage.AddService
methods).
The generic parameter T
is the type of the tool window. This would be used by GetAsyncToolWindowFactory
. The title
would be used by GetToolWindowTitle
, and the initializer
function would be used by InitializeToolWindowAsync
.
Opening a Tool Window
The boilerplate code for opening a tool window can also be put in the new ToolkitPackage
.
abstract class ToolkitPackage : AsyncPackage {
public Task<T> ShowToolWindowAsync<T>(CancellationToken cancellationToken = default);
}
Examples
Adding a Tool Window
public sealed class TestExtensionPackage : ToolkitPackage
{
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
{
AddToolWindow<RunnerWindow>(RunnerWindow.Title, async () =>
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
return await VS.GetDTEAsync();
});
AddToolWindow<ThemeWindow>(ThemeWindow.Title, () => new ThemeWindowControlViewModel());
}
}
Opening a Tool Window
protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
{
await Package.ShowToolWindowAsync<RunnerWindow>();
}
Notes
The ToolkitPackage
would need to be passed around everywhere instead of an AsyncPackage
so that the helper method to open a tool window is accessible. For example, CommandBase.InitializeAsync
would need to take a ToolkitPackage
rather than an AsyncPackage
so that the derived classes can use ShowToolWindowAsync
.
Prototype
I've thrown together a prototype here: master...reduckted:prototype/async-tool-window
If you like this idea, I'll clean it up and create a pull request.