Chart toolbar menu

Indicators

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.

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 */ }
}
Two easy mistakes. First, 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.