Skip to content

Commit d2911d3

Browse files
committed
docs(ui): add Context Menus documentation
Add comprehensive documentation for context menus: - Quick start with Community Toolkit - Context menu GUIDs for Solution Explorer, Editor, Tool Windows - VSCT structure for adding commands - Common context menu IDs reference - CommandPlacements for multiple locations - Dynamic visibility with BeforeQueryStatus - UI Context visibility constraints - Creating submenus - Complete file processing example - Best practices Closes #32
1 parent f8a0433 commit d2911d3

1 file changed

Lines changed: 371 additions & 0 deletions

File tree

src/content/docs/context-menus.mdx

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
---
2+
title: Context Menus
3+
description: Learn how to add commands to Visual Studio context menus and their GUIDs.
4+
category: fundamentals
5+
order: 13
6+
---
7+
8+
import Callout from '@components/Callout.astro';
9+
10+
Context menus (right-click menus) appear throughout Visual Studio. Your extension can add commands to existing context menus or create custom ones.
11+
12+
## Quick Start with Community Toolkit
13+
14+
Add a command to Solution Explorer's context menu:
15+
16+
```csharp
17+
[Command(PackageIds.MyCommand)]
18+
internal sealed class MyCommand : BaseCommand<MyCommand>
19+
{
20+
protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
21+
{
22+
await VS.MessageBox.ShowAsync("Command executed!");
23+
}
24+
}
25+
```
26+
27+
In your `.vsct` file:
28+
29+
```xml
30+
<Button guid="guidMyPackageCmdSet" id="MyCommand" priority="0x0100" type="Button">
31+
<Parent guid="guidSHLMainMenu" id="IDG_VS_CTXT_SOLUTION_BUILD"/>
32+
<Strings>
33+
<ButtonText>My Command</ButtonText>
34+
</Strings>
35+
</Button>
36+
```
37+
38+
## Context Menu GUIDs
39+
40+
### Solution Explorer
41+
42+
| Menu | GUID | Group ID | Description |
43+
|------|------|----------|-------------|
44+
| Solution node | `guidSHLMainMenu` | `IDG_VS_CTXT_SOLUTION_BUILD` | Solution context menu |
45+
| Project node | `guidSHLMainMenu` | `IDG_VS_CTXT_PROJECT_BUILD` | Project context menu |
46+
| Folder node | `guidSHLMainMenu` | `IDG_VS_CTXT_FOLDER_ADD` | Folder context menu |
47+
| Item/File node | `guidSHLMainMenu` | `IDG_VS_CTXT_ITEM_OPEN` | File context menu |
48+
| References node | `guidSHLMainMenu` | `IDG_VS_CTXT_REFROOT_ADD` | References context menu |
49+
| Reference item | `guidSHLMainMenu` | `IDG_VS_CTXT_REFERENCE` | Single reference |
50+
51+
### Code Editor
52+
53+
| Menu | GUID | Group ID | Description |
54+
|------|------|----------|-------------|
55+
| Editor context | `guidSHLMainMenu` | `IDG_VS_CODEWIN_NAVIGATETOLOCATION` | Code editor right-click |
56+
| Editor margin | `guidSHLMainMenu` | `IDG_VS_EDITOR_OUTLINING_CMDS` | Left margin |
57+
58+
### Tool Windows
59+
60+
| Menu | GUID/Group | Description |
61+
|------|------------|-------------|
62+
| Error List | `CMDSETID_StandardCommandSet2K` / `IDG_VS_ERRORLIST_ERRORGROUP` | Error List items |
63+
| Output Window | `guidSHLMainMenu` / `IDG_VS_OUTWINDOW_OUTPUTCMDS` | Output window |
64+
| Task List | `guidSHLMainMenu` / `IDG_VS_TASKLIST_CLIENTGROUP` | Task List items |
65+
66+
## Adding Commands to Context Menus
67+
68+
### VSCT Structure
69+
70+
```xml
71+
<?xml version="1.0" encoding="utf-8"?>
72+
<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable">
73+
74+
<Extern href="stdidcmd.h"/>
75+
<Extern href="vsshlids.h"/>
76+
77+
<Commands package="guidMyPackage">
78+
79+
<!-- Define a group to hold your commands -->
80+
<Groups>
81+
<Group guid="guidMyPackageCmdSet" id="MyMenuGroup" priority="0x0600">
82+
<!-- Parent this group to Solution Explorer's solution context menu -->
83+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_SOLNNODE"/>
84+
</Group>
85+
</Groups>
86+
87+
<Buttons>
88+
<Button guid="guidMyPackageCmdSet" id="MyCommandId" priority="0x0100" type="Button">
89+
<Parent guid="guidMyPackageCmdSet" id="MyMenuGroup"/>
90+
<Icon guid="guidImages" id="myIcon"/>
91+
<Strings>
92+
<ButtonText>My Context Command</ButtonText>
93+
</Strings>
94+
</Button>
95+
</Buttons>
96+
97+
</Commands>
98+
99+
<Symbols>
100+
<GuidSymbol name="guidMyPackage" value="{your-package-guid}"/>
101+
<GuidSymbol name="guidMyPackageCmdSet" value="{your-cmdset-guid}">
102+
<IDSymbol name="MyMenuGroup" value="0x1020"/>
103+
<IDSymbol name="MyCommandId" value="0x0100"/>
104+
</GuidSymbol>
105+
</Symbols>
106+
107+
</CommandTable>
108+
```
109+
110+
### Common Context Menu IDs
111+
112+
```xml
113+
<!-- Solution Explorer -->
114+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_SOLNNODE"/> <!-- Solution -->
115+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_PROJNODE"/> <!-- Project -->
116+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_FOLDERNODE"/> <!-- Folder -->
117+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE"/> <!-- File/Item -->
118+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_REFERENCEROOT"/> <!-- References -->
119+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_REFERENCE"/> <!-- Reference -->
120+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_WEBREFFOLDER"/> <!-- Web References -->
121+
122+
<!-- Code Editor -->
123+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN"/> <!-- Editor -->
124+
125+
<!-- Class View -->
126+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CV_PROJECT"/> <!-- CV Project -->
127+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CV_ITEM"/> <!-- CV Item -->
128+
129+
<!-- Other -->
130+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_NOCOMMANDS"/> <!-- No selection -->
131+
```
132+
133+
## Multiple Context Menus
134+
135+
Add your command to multiple locations using `CommandPlacements`:
136+
137+
```xml
138+
<CommandPlacements>
139+
<!-- Add to Solution context menu -->
140+
<CommandPlacement guid="guidMyPackageCmdSet" id="MyCommandId" priority="0x0100">
141+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_SOLNNODE"/>
142+
</CommandPlacement>
143+
144+
<!-- Also add to Project context menu -->
145+
<CommandPlacement guid="guidMyPackageCmdSet" id="MyCommandId" priority="0x0100">
146+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_PROJNODE"/>
147+
</CommandPlacement>
148+
149+
<!-- And File context menu -->
150+
<CommandPlacement guid="guidMyPackageCmdSet" id="MyCommandId" priority="0x0100">
151+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE"/>
152+
</CommandPlacement>
153+
</CommandPlacements>
154+
```
155+
156+
## Dynamic Visibility
157+
158+
Control when your command appears:
159+
160+
### With Community Toolkit
161+
162+
```csharp
163+
[Command(PackageIds.MyCommand)]
164+
internal sealed class MyCommand : BaseCommand<MyCommand>
165+
{
166+
protected override void BeforeQueryStatus(EventArgs e)
167+
{
168+
// Only show for .cs files
169+
var item = VS.Solutions.GetActiveItemAsync().Result;
170+
Command.Visible = item?.FullPath?.EndsWith(".cs") == true;
171+
}
172+
173+
protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
174+
{
175+
// Command implementation
176+
}
177+
}
178+
```
179+
180+
### Traditional OleMenuCommand
181+
182+
```csharp
183+
private void InitializeCommand()
184+
{
185+
var commandId = new CommandID(PackageGuids.guidMyPackageCmdSet, PackageIds.MyCommand);
186+
var command = new OleMenuCommand(Execute, commandId);
187+
command.BeforeQueryStatus += OnBeforeQueryStatus;
188+
189+
var commandService = (IMenuCommandService)GetService(typeof(IMenuCommandService));
190+
commandService.AddCommand(command);
191+
}
192+
193+
private void OnBeforeQueryStatus(object sender, EventArgs e)
194+
{
195+
ThreadHelper.ThrowIfNotOnUIThread();
196+
197+
var command = (OleMenuCommand)sender;
198+
199+
// Get selected items in Solution Explorer
200+
var monitorSelection = (IVsMonitorSelection)GetService(typeof(SVsShellMonitorSelection));
201+
monitorSelection.GetCurrentSelection(
202+
out IntPtr hierarchyPtr,
203+
out uint itemId,
204+
out IVsMultiItemSelect multiSelect,
205+
out IntPtr containerPtr);
206+
207+
// Show only for single selection
208+
command.Visible = multiSelect == null && hierarchyPtr != IntPtr.Zero;
209+
}
210+
```
211+
212+
### UI Context Visibility
213+
214+
Show commands only in specific contexts:
215+
216+
```xml
217+
<VisibilityConstraints>
218+
<VisibilityItem guid="guidMyPackageCmdSet" id="MyCommandId"
219+
context="UICONTEXT_SolutionHasSingleProject"/>
220+
</VisibilityConstraints>
221+
```
222+
223+
Common UI contexts:
224+
225+
| Context | When Visible |
226+
|---------|--------------|
227+
| `UICONTEXT_SolutionExists` | A solution is open |
228+
| `UICONTEXT_SolutionHasSingleProject` | Solution has exactly one project |
229+
| `UICONTEXT_SolutionHasMultipleProjects` | Solution has multiple projects |
230+
| `UICONTEXT_SolutionBuilding` | Build in progress |
231+
| `UICONTEXT_Debugging` | Debug session active |
232+
| `UICONTEXT_DesignMode` | Not debugging |
233+
| `UICONTEXT_CodeWindow` | Code editor has focus |
234+
| `UICONTEXT_NoSolution` | No solution open |
235+
236+
## Submenus
237+
238+
Create a submenu in a context menu:
239+
240+
```xml
241+
<Menus>
242+
<Menu guid="guidMyPackageCmdSet" id="MySubmenu" priority="0x0100" type="Menu">
243+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE"/>
244+
<Strings>
245+
<ButtonText>My Extension</ButtonText>
246+
</Strings>
247+
</Menu>
248+
</Menus>
249+
250+
<Groups>
251+
<Group guid="guidMyPackageCmdSet" id="MySubmenuGroup" priority="0x0100">
252+
<Parent guid="guidMyPackageCmdSet" id="MySubmenu"/>
253+
</Group>
254+
</Groups>
255+
256+
<Buttons>
257+
<Button guid="guidMyPackageCmdSet" id="SubCommand1" priority="0x0100" type="Button">
258+
<Parent guid="guidMyPackageCmdSet" id="MySubmenuGroup"/>
259+
<Strings>
260+
<ButtonText>Sub Command 1</ButtonText>
261+
</Strings>
262+
</Button>
263+
264+
<Button guid="guidMyPackageCmdSet" id="SubCommand2" priority="0x0200" type="Button">
265+
<Parent guid="guidMyPackageCmdSet" id="MySubmenuGroup"/>
266+
<Strings>
267+
<ButtonText>Sub Command 2</ButtonText>
268+
</Strings>
269+
</Button>
270+
</Buttons>
271+
```
272+
273+
## Complete Example
274+
275+
A context menu command that processes selected files:
276+
277+
```csharp
278+
[Command(PackageIds.ProcessFilesCommand)]
279+
internal sealed class ProcessFilesCommand : BaseCommand<ProcessFilesCommand>
280+
{
281+
protected override void BeforeQueryStatus(EventArgs e)
282+
{
283+
ThreadHelper.JoinableTaskFactory.Run(async () =>
284+
{
285+
var items = await VS.Solutions.GetActiveItemsAsync();
286+
var hasFiles = items.Any(i => !string.IsNullOrEmpty(i.FullPath) &&
287+
File.Exists(i.FullPath));
288+
Command.Visible = hasFiles;
289+
Command.Enabled = hasFiles;
290+
});
291+
}
292+
293+
protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
294+
{
295+
var items = await VS.Solutions.GetActiveItemsAsync();
296+
var files = items
297+
.Where(i => File.Exists(i.FullPath))
298+
.Select(i => i.FullPath)
299+
.ToList();
300+
301+
if (!files.Any())
302+
{
303+
await VS.MessageBox.ShowWarningAsync("No files selected");
304+
return;
305+
}
306+
307+
await VS.StatusBar.ShowProgressAsync("Processing files...", 0, files.Count);
308+
309+
for (int i = 0; i < files.Count; i++)
310+
{
311+
await ProcessFileAsync(files[i]);
312+
await VS.StatusBar.ShowProgressAsync(
313+
$"Processing {Path.GetFileName(files[i])}...",
314+
i + 1,
315+
files.Count);
316+
}
317+
318+
await VS.StatusBar.ShowMessageAsync($"Processed {files.Count} files");
319+
}
320+
321+
private async Task ProcessFileAsync(string filePath)
322+
{
323+
await Task.Delay(100); // Your processing logic
324+
}
325+
}
326+
```
327+
328+
VSCT for the command:
329+
330+
```xml
331+
<Groups>
332+
<Group guid="guidMyPackageCmdSet" id="SolutionExplorerGroup" priority="0x0600">
333+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE"/>
334+
</Group>
335+
</Groups>
336+
337+
<Buttons>
338+
<Button guid="guidMyPackageCmdSet" id="ProcessFilesCommand" priority="0x0100" type="Button">
339+
<Parent guid="guidMyPackageCmdSet" id="SolutionExplorerGroup"/>
340+
<Icon guid="ImageCatalogGuid" id="Process"/>
341+
<CommandFlag>IconIsMoniker</CommandFlag>
342+
<CommandFlag>DynamicVisibility</CommandFlag>
343+
<Strings>
344+
<ButtonText>Process Files</ButtonText>
345+
</Strings>
346+
</Button>
347+
</Buttons>
348+
```
349+
350+
## Best Practices
351+
352+
1. **Use groups** - Don't parent directly to menus; create a group first
353+
2. **Set priorities** - Control order with priority values (lower = higher in menu)
354+
3. **Dynamic visibility** - Hide commands that don't apply to the selection
355+
4. **Use submenus** - Group related commands under one submenu to reduce clutter
356+
5. **Consistent naming** - Match VS naming conventions
357+
6. **Add icons** - Commands with icons are easier to find
358+
359+
<Callout type="tip">
360+
Use the `DynamicVisibility` command flag in VSCT when implementing `BeforeQueryStatus` to control visibility programmatically.
361+
</Callout>
362+
363+
<Callout type="warning">
364+
Context menu commands should execute quickly. For long operations, show a progress indicator and consider running asynchronously.
365+
</Callout>
366+
367+
## See Also
368+
369+
- [Commands](commands) - Command basics and VSCT
370+
- [Dynamic Menus](dynamic-menus) - Runtime-generated menu items
371+
- [Tool Window GUIDs](tool-window-guids) - Related GUIDs reference

0 commit comments

Comments
 (0)