Menus: Structure, Ordering, and Context Menu Subscription
1. Main menu function
void AddMenuContribution(
int sort,
string menuPath,
Action<MenuCommandContext> callback,
Func<bool>? isEnabled = null)
2. Main menu path format
<Root>.<Group>.<Item>[.<SubMenu>...]
Examples:
File.openclose.Open
Effects.100.Amplitude.Amplify
View.pluginsgroup.Show Sample Editor
Rule: root is first token, group is second token, final token is the clickable menu item label. Any tokens between group and final token become submenu levels.
3. How ordering works in the host
3.1 Root menu order
- A root menu gets its root order from the first contribution sort value seen for that root.
- Later contributions under the same root do not replace that root order value.
- If roots tie on sort, earlier registration sequence wins.
- Then root text name is used for stable ordering.
3.2 Items inside a root
- First key:
sortascending. - Second key: group bucket ordering.
- Numeric group names sort before text group names.
- Numeric groups sort numerically (
100,200,300). - Text groups sort alphabetically.
- Final tie-break: registration sequence (first registered first).
Practical convention: many plugins use numeric group keys (for example
100, 200) as group+order buckets inside a root.3.3 Group separators
- When group value changes, host inserts a separator between groups.
- You can also add explicit separator item with label
-.
4. Context menu function
void AddContextMenuContribution(
string surfaceId,
int sort,
string menuPath,
Action<MenuCommandContext> callback,
Func<bool>? isEnabled = null)
5. Context menu path format
<Group>.<Item>[.<SubMenu>...]
Examples:
effectsgroup.Smooth
effectsgroup.Amplitude.Amplify
editgroup.Paste
6. Sample Editor context menu subscription
Sample editor wave surface id is:
SampleEditor.Wave
Minimal subscription example:
context.WindowManager.AddContextMenuContribution(
surfaceId: "SampleEditor.Wave",
sort: 200,
menuPath: "effectsgroup.Smooth",
callback: _ => OpenSmoothDialog(),
isEnabled: () => _context?.DocumentService.ActiveDocument is not null);
Nested submenu example:
context.WindowManager.AddContextMenuContribution(
"SampleEditor.Wave",
200,
"effectsgroup.Amplitude.Amplify",
_ => OpenAmplifyDialog());
7. Recommended menu conventions
- Use numeric groups in roots where deterministic ordering matters.
- Keep menuPath labels human-readable; do not lowercase everything.
- Use one consistent group key per logical section to avoid noisy separators.
- Guard actions with
isEnabledwhen no active document/buffer.
7.1 Known context-menu surface IDs
SampleEditor.Wave: waveform view context menu.
Use GetContextMenuContributions(surfaceId) to inspect what is currently registered for a surface.
8. Common mistakes
- Path too short: main menu must have at least 3 segments; context menu at least 2.
- Expecting callback context to include document service; it does not.
- Assuming group text is purely visual; it also drives grouping and ordering.
- Relying only on sequence order; sequence is tie-breaker, not primary sort.