Plugin Patterns and Copy-Paste Examples
1. Menu-only plugin
public sealed class MenuOnlyPlugin : IPlugin
{
private IHostContext? _context;
public string Id => "menu-only";
public string DisplayName => "Menu Only";
public Version Version => new(1,0,0);
public void OnLoad(IHostContext context)
{
_context = context;
context.WindowManager.AddMenuContribution(
10,
"Tools.100.Menu Only.Run",
_ => Execute(),
() => _context?.DocumentService.ActiveDocument is not null);
}
public void OnUnload() => _context = null;
private void Execute() { /* do work */ }
}
2. Docked pane plugin
public sealed class DockPanePlugin : IPlugin, IProvidesDockPanes
{
private IHostContext? _context;
private Label? _label;
public string Id => "dock-pane";
public string DisplayName => "Dock Pane";
public Version Version => new(1,0,0);
public void OnLoad(IHostContext context)
{
_context = context;
foreach (var pane in GetDockPanes())
{
context.WindowManager.AddDockPane(pane);
}
context.WindowManager.AddMenuContribution(90, "View.pluginsgroup.Show Dock Pane", c => c.WindowManager.SetPaneVisible("dock-pane.main", true));
context.SampleControllerBus.StateChanged += OnStateChanged;
}
public void OnUnload()
{
if (_context is not null)
{
_context.SampleControllerBus.StateChanged -= OnStateChanged;
}
_context = null;
_label = null;
}
public IEnumerable<DockPaneRegistration> GetDockPanes()
{
yield return new DockPaneRegistration(
"dock-pane.main",
"Dock Pane",
DockPlacement.Right,
CreateView,
AllowVerticalNeighbour: true,
AllowHorizontalNeighbour: true,
CanUndock: true);
}
private object CreateView()
{
_label = new Label { Dock = DockStyle.Fill, TextAlign = ContentAlignment.MiddleCenter, Text = "Ready" };
return new Panel { Dock = DockStyle.Fill, Controls = { _label } };
}
private void OnStateChanged(object? sender, SampleControllerState state)
{
if (_label is null) return;
if (_label.InvokeRequired)
{
_label.BeginInvoke(new Action(() => OnStateChanged(sender, state)));
return;
}
_label.Text = $"Cursor {state.Cursor}";
}
}
3. Effect dialogue with preview/apply/cancel
private void OpenEffectDialog()
{
var state = _context.SampleControllerBus.State;
var start = Math.Min(state.SelectionStart, state.SelectionEnd);
var endExclusive = Math.Max(state.SelectionStart, state.SelectionEnd);
if (start == endExclusive)
{
start = state.ViewStart;
endExclusive = state.ViewStart + state.ViewLength;
}
var source = _context.DocumentService.CopyEffectiveRangeToBuffer(start, endExclusive);
using var preview = _context.DocumentService.BeginPreview(start, endExclusive);
using var dlg = new MyEffectDialog();
void rebuildPreview()
{
var previewBuffer = Process(source, dlg.Strength);
preview.ApplyPreview(previewBuffer);
if (dlg.PreviewEnabled)
{
_context.AudioSystemBus.RequestPlayback(new PlaybackCommand(
PlaybackCommandType.PlayBuffer,
Buffer: previewBuffer,
StartFrame: 0,
EndFrameExclusive: previewBuffer.InterleavedSamples.Length / previewBuffer.Channels,
Loop: true,
PreviewOwnerId: "my-effect.preview"));
}
}
dlg.SettingsChanged += (_, _) => rebuildPreview();
var result = dlg.ShowDialog();
_context.AudioSystemBus.RequestPlayback(new PlaybackCommand(PlaybackCommandType.Stop, PreviewOwnerId: "my-effect.preview"));
if (result == DialogResult.OK)
{
preview.Commit();
}
else
{
preview.Cancel();
}
}
4. Context menu extension (sample editor)
context.WindowManager.AddContextMenuContribution(
surfaceId: "SampleEditor.Wave",
sort: 200,
menuPath: "effectsgroup.My Effect",
callback: _ => OpenEffectDialog(),
isEnabled: () => context.DocumentService.ActiveDocument is not null);
5. Preset-enabled pattern
private const string PresetKey = "MyEffect";
private void SavePreset(string name, string payloadJson)
{
if (_context.PresetStore.PresetExists(PresetKey, name))
{
if (MessageBox.Show($"Overwrite preset '{name}'?", "Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
{
return;
}
}
_context.PresetStore.SavePreset(PresetKey, name, payloadJson);
RefreshPresetList();
}
private void LoadLastUsedIfAny()
{
var name = _context.PresetStore.LastUsed(PresetKey);
if (string.IsNullOrWhiteSpace(name)) return;
var json = _context.PresetStore.LoadPreset(PresetKey, name);
if (string.IsNullOrWhiteSpace(json)) return;
ApplyPreset(json);
}
Persist functional settings only. Do not persist preview/bypass/crosshair UI states.
6. Codec plugin pattern
public sealed class MyCodecPlugin : IPlugin
{
public string Id => "my-codec";
public string DisplayName => "My Codec";
public Version Version => new(1,0,0);
public void OnLoad(IHostContext context)
{
context.CodecRegistry.Register(new MyCodec());
}
public void OnUnload() { }
private sealed class MyCodec : IFileTypeCodec
{
public string CodecId => "my-codec.format";
public string DisplayName => "My Codec";
public IReadOnlyList<string> Extensions => new[] { ".mywav" };
public SampleBuffer Read(string filePath)
{
// decode to interleaved float[]
throw new NotImplementedException();
}
public void Write(string filePath, SampleBuffer buffer)
{
// encode from interleaved float[]
throw new NotImplementedException();
}
}
}
7. Delegates and callback function shapes
| API | Function type |
|---|---|
AddMenuContribution(...) | Action<MenuCommandContext>, Func<bool>? |
AddContextMenuContribution(...) | Action<MenuCommandContext>, Func<bool>? |
StateChanged (sample bus) | EventHandler<SampleControllerState> |
SelectionChanged | EventHandler<SelectionChange> |
PlaybackStateChanged | EventHandler<PlaybackState> |