Tool Windows
Tool windows are dockable panels in Visual Studio, like Solution Explorer, Error List, or Output. Your extension can create custom tool windows with any UI content.
Creating a Tool Window
With Community Toolkit
Create the tool window class:
using System.Runtime.InteropServices;
using System.Windows;
using Community.VisualStudio.Toolkit;
using Microsoft.VisualStudio.Imaging;
using Microsoft.VisualStudio.Shell;
[Guid("your-tool-window-guid")]
public class MyToolWindow : BaseToolWindow<MyToolWindow>
{
public override string GetTitle(int toolWindowId) => "My Tool Window";
public override Type PaneType => typeof(Pane);
public override Task<FrameworkElement> CreateAsync(int toolWindowId, CancellationToken cancellationToken)
{
return Task.FromResult<FrameworkElement>(new MyToolWindowControl());
}
internal class Pane : ToolWindowPane
{
public Pane()
{
BitmapImageMoniker = KnownMonikers.ToolWindow;
}
}
}
Create the WPF control:
<!-- MyToolWindowControl.xaml -->
<UserControl x:Class="MyExtension.MyToolWindowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="{DynamicResource VsBrush.Window}"
Foreground="{DynamicResource VsBrush.WindowText}">
<Grid Margin="10">
<StackPanel>
<TextBlock Text="My Tool Window" FontSize="16" FontWeight="Bold" Margin="0,0,0,10"/>
<TextBox x:Name="InputBox" Margin="0,0,0,10"/>
<Button Content="Click Me" Click="OnButtonClick"/>
</StackPanel>
</Grid>
</UserControl>
// MyToolWindowControl.xaml.cs
public partial class MyToolWindowControl : UserControl
{
public MyToolWindowControl()
{
InitializeComponent();
}
private void OnButtonClick(object sender, RoutedEventArgs e)
{
VS.MessageBox.Show("Hello", $"You entered: {InputBox.Text}");
}
}
Register in Package
[ProvideToolWindow(typeof(MyToolWindow.Pane), Style = VsDockStyle.Tabbed, Window = "3ae79031-e1bc-11d0-8f78-00a0c9110057")]
public sealed class MyPackage : ToolkitPackage { }
Create a Command to Show It
[Command(PackageIds.ShowMyToolWindow)]
internal sealed class ShowMyToolWindowCommand : BaseCommand<ShowMyToolWindowCommand>
{
protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
{
await MyToolWindow.ShowAsync();
}
}
Traditional Approach
Without the Community Toolkit:
[Guid("your-tool-window-guid")]
public class MyToolWindow : ToolWindowPane
{
public MyToolWindow() : base(null)
{
Caption = "My Tool Window";
BitmapImageMoniker = KnownMonikers.ToolWindow;
Content = new MyToolWindowControl();
}
}
Show the window:
var window = await package.FindToolWindowAsync(
typeof(MyToolWindow), 0, true, package.DisposalToken);
var frame = (IVsWindowFrame)window.Frame;
frame.Show();
Tool Window Options
Docking Style
[ProvideToolWindow(typeof(MyToolWindow.Pane),
Style = VsDockStyle.Tabbed, // Docking behavior
Window = EnvDTE.Constants.vsWindowKindOutput)] // Dock with Output window
Docking options:
| Style | Description |
|---|---|
VsDockStyle.Tabbed | Tab group with another window |
VsDockStyle.Linked | Linked to another window |
VsDockStyle.MDI | MDI child window |
VsDockStyle.Float | Floating window |
VsDockStyle.AlwaysFloat | Always floating |
Visibility
Control when the tool window appears:
[ProvideToolWindowVisibility(typeof(MyToolWindow.Pane), VSConstants.UICONTEXT.SolutionExists_string)]
Multiple Instances
Allow multiple instances of your tool window:
[ProvideToolWindow(typeof(MyToolWindow.Pane), MultiInstances = true)]
Show a specific instance:
await MyToolWindow.ShowAsync(toolWindowId: 1);
await MyToolWindow.ShowAsync(toolWindowId: 2);
VS Theme Integration
Use VS theme brushes for proper appearance:
<UserControl
Background="{DynamicResource VsBrush.Window}"
Foreground="{DynamicResource VsBrush.WindowText}">
<Button
Background="{DynamicResource VsBrush.Button}"
Foreground="{DynamicResource VsBrush.ButtonText}"/>
<TextBox
Background="{DynamicResource VsBrush.ComboBoxBackground}"
Foreground="{DynamicResource VsBrush.WindowText}"/>
</UserControl>
Tip
Always use dynamic resource brushes so your UI updates when users change VS themes.
Search Support
Add a search box to your tool window:
public class MyToolWindow : ToolWindowPane, IVsWindowSearch
{
public MyToolWindow() : base(null)
{
Caption = "My Tool Window";
Content = new MyToolWindowControl();
}
public IVsSearchTask CreateSearch(uint dwCookie, IVsSearchQuery pSearchQuery, IVsSearchCallback pSearchCallback)
{
return new MySearchTask(dwCookie, pSearchQuery, pSearchCallback, this);
}
public void ClearSearch()
{
// Clear search results
}
public void ProvideSearchSettings(IVsUIDataSource pSearchSettings)
{
// Configure search options
}
public bool OnNavigationKeyDown(uint dwNavigationKey, uint dwModifiers)
{
return false;
}
public bool SearchEnabled => true;
public Guid Category => Guid.Empty;
public IVsEnumWindowSearchFilters SearchFiltersEnum => null;
public IVsEnumWindowSearchOptions SearchOptionsEnum => null;
}
Toolbar in Tool Window
Add a toolbar to your tool window:
public class MyToolWindow : ToolWindowPane
{
public MyToolWindow() : base(null)
{
Caption = "My Tool Window";
Content = new MyToolWindowControl();
// Set the toolbar
ToolBar = new CommandID(PackageGuids.guidMyPackageCmdSet, PackageIds.MyToolWindowToolbar);
}
}
Define the toolbar in VSCT:
<Menus>
<Menu guid="guidMyPackageCmdSet" id="MyToolWindowToolbar" type="ToolWindowToolbar">
<Strings>
<ButtonText>Tool Window Toolbar</ButtonText>
</Strings>
</Menu>
</Menus>
<Groups>
<Group guid="guidMyPackageCmdSet" id="MyToolWindowToolbarGroup">
<Parent guid="guidMyPackageCmdSet" id="MyToolWindowToolbar"/>
</Group>
</Groups>
InfoBar in Tool Window
Show informational banners:
public async Task ShowInfoBarAsync()
{
var model = new InfoBarModel(new[]
{
new InfoBarTextSpan("This is an info bar. "),
new InfoBarHyperlink("Click here", "action1")
}, KnownMonikers.StatusInformation);
var infoBar = await VS.InfoBar.CreateAsync(MyToolWindow, model);
infoBar.ActionItemClicked += (s, e) =>
{
if (e.ActionItem.ActionContext == "action1")
{
// Handle click
}
};
await infoBar.TryShowInfoBarUIAsync();
}
Complete Example
// MyToolWindow.cs
[Guid("12345678-1234-1234-1234-123456789012")]
public class MyToolWindow : BaseToolWindow<MyToolWindow>
{
public override string GetTitle(int toolWindowId) => "Solution Explorer+";
public override Type PaneType => typeof(Pane);
public override async Task<FrameworkElement> CreateAsync(int toolWindowId, CancellationToken ct)
{
var solution = await VS.Solutions.GetCurrentSolutionAsync();
return new SolutionExplorerControl(solution);
}
internal class Pane : ToolWindowPane
{
public Pane()
{
BitmapImageMoniker = KnownMonikers.Solution;
ToolBar = new CommandID(PackageGuids.guidMyPackageCmdSet, PackageIds.SolutionToolbar);
}
}
}
// SolutionExplorerControl.xaml.cs
public partial class SolutionExplorerControl : UserControl
{
public SolutionExplorerControl(Solution solution)
{
InitializeComponent();
DataContext = new SolutionViewModel(solution);
}
}
Next Steps
Learn about extending the Code Editor with custom features.