Chart toolbar menu
Chart toolbar menu INTERFACE
Give your indicator its own dropdown button on the chart's top toolbar — and a right-click context menu — for quick toggles without opening the settings dialog.
On this page
What it is
Implement IIndicatorToolbarMenu and your indicator gets its own dropdown button on the
chart's top toolbar — the natural home for fast toggles like "show POC" or "show value area". The
scope is per indicator instance, per chart tab: two charts each carrying a Volume Profile get two
independent buttons, each editing its own instance. The host does all the per-tab bookkeeping; the
indicator stays oblivious.
Implementing the menu
Provide MenuTitle (the button text) and BuildMenu(), which returns an
IReadOnlyList<ToolbarMenuItem>. BuildMenu runs each time the user opens
the dropdown, so build fresh items reflecting current state — allocating per open is expected.
Raise MenuChanged only when the menu structure changes (items appear/disappear);
a ToolbarCheckItem's checkmark is re-read on every open and needs no event.
VolumeProfile.cs (toolbar menu)using System;
using System.Collections.Generic;
using TradeStrike.Pipeline.Indicators;
[IndicatorDescription("Volume-at-price profile over each session, with POC and value-area overlays.")]
[IndicatorCategory(IndicatorCategory.OrderFlow)]
[IndicatorInput(IndicatorInputKind.None)]
[RequiresOrderFlow]
public sealed class VolumeProfile : IndicatorBase, IChartCustomRender, IRepaintNotifier, IIndicatorToolbarMenu
{
public event Action? RepaintRequested;
public event Action? MenuChanged; // structure rarely changes; fine to never raise it
private bool _showPoc = true;
private bool _showValueArea = true;
public string MenuTitle => "Volume Profile";
public IReadOnlyList<ToolbarMenuItem> BuildMenu() => new ToolbarMenuItem[]
{
new ToolbarCheckItem("Show POC",
IsChecked: () => _showPoc,
Toggle: on => { _showPoc = on; RepaintRequested?.Invoke(); }),
new ToolbarCheckItem("Show value area",
IsChecked: () => _showValueArea,
Toggle: on => { _showValueArea = on; RepaintRequested?.Invoke(); }),
};
public VolumeProfile(IIndicator? parent = null) : base(parent) { }
public void OnCustomRender(IIndicatorRenderContext ctx) { /* paint columns, POC, value area */ }
}
RequestRepaint() lives on
IRepaintNotifier, not on IndicatorBase — a menu whose actions repaint
must also implement IRepaintNotifier (as above). Second, ToolbarCheckItem
takes a Func<bool> IsChecked (re-queried each open) and an
Action<bool> Toggle (receives the new state) — not parameterless delegates.The menu item kinds
ToolbarMenuItem is a closed record hierarchy — pick the one that fits and the host
renders it. The same records are reused by the context menu below.
| Record | Renders as |
|---|---|
ToolbarCommand |
A click-to-invoke action (Invoke, optional IsEnabled). |
ToolbarCheckItem |
A two-state toggle (IsChecked / Toggle). |
ToolbarSubmenu |
A nested submenu (optional checked indicator on the root). |
ToolbarSeparator |
A visual divider. |
ToolbarRadioGroup (+ ToolbarRadioOption) |
A mutually-exclusive option group. |
ToolbarColorPicker |
A colour-swatch row that opens a picker (GetColor / SetColor). |
ToolbarNumberInput |
An inline numeric field, clamped to [Min, Max], rounded to Decimals. |
ToolbarSlider |
A draggable slider with the same data contract as ToolbarNumberInput. |
ToolbarTextInput |
An inline free-text field (you parse/validate the string). |
ToolbarDescription |
A non-interactive, greyed-out help line. |
Right-click context menu
Implement IIndicatorContextMenu to contribute items to the chart's right-click menu,
targeted at the clicked location. It reuses the same ToolbarMenuItem records, and
BuildContextMenu receives an IndicatorContextMenuLocation carrying
BarIndex, Price, Time, and the raw PixelX/PixelY
— so you can build items that act on whatever is under the cursor (e.g. the profile session the
click fell on).
context menupublic event Action? ContextMenuChanged; // most implementers never raise it (rebuilt per open)
public IReadOnlyList<ToolbarMenuItem> BuildContextMenu(IndicatorContextMenuLocation loc)
{
return new ToolbarMenuItem[]
{
new ToolbarCommand($"Anchor profile from {loc.Time:t}",
Invoke: () => { AnchorAt(loc.BarIndex); RepaintRequested?.Invoke(); }),
};
}
Threading & lifecycle
BuildMenu and BuildContextMenu always run on the UI thread (a menu open is a
user action), so read indicator state directly inside them. MenuChanged /
ContextMenuChanged may fire from any thread — the host debounces and rebuilds on the
next open. The button materialises when the indicator is added to the chart and disappears the frame
after it is removed.
You have the full picture
That completes the indicator chapter. You can build the whole spectrum — from a one-line SMA to a
footprint tool with orderflow data, custom rendering, coalesced repaints, a toolbar dropdown and a
context menu — entirely against the public contracts in TradeStrike.Pipeline.Indicators,
TradeStrike.Pipeline.Plots, TradeStrike.Pipeline.Series and friends. For a member-level
reference, see Reference › Indicators. Next up: strategies,
which extend the same indicator lifecycle with order and position-state hooks.