Thanks for the reply. Here is an example structure, I removed almost everything except the main elements.
I tried all various kinds of dispatcher calls, I tried wrapping the tab switcher in a dispatcher to delay it, all kinds of things.
The bug appears when you have more than 1 tab open and switch between, not always but at some points one tab will get multiple duplicate icons. Once it does, reloading the chart removes them(because destroyghostitems runs)…but when you switch tabs again, they come back.
I can fix it between having destroyghostitems run everytimre you tab switch but this isn’t getting to the bottom of the issue as to why there is a leak in the first place.
//
// Copyright (C) 2020, NinjaTrader LLC <www.ninjatrader.com>.
// NinjaTrader reserves the right to modify or overwrite this NinjaScript component with each release.
//
#region Using declarations
using System;
using System.ComponentModel.DataAnnotations;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
using NinjaTrader.Gui.Chart;
using NinjaTrader.Gui.Tools;
using NinjaTrader.NinjaScript;
using Enums911501;
using System.ComponentModel;
#endregion
namespace NinjaTrader.NinjaScript.Indicators
{
public class ExampleMenuIconDuplicateBug : Indicator
{
// Chart + menu objects
private Chart myChartWindow;
private NTMenuItem MyMenu;
private Menu MyMenuContainer; // Used to attach style only
private bool menuAdded;
private TabItem tabItem;
private ChartTab chartTab;
// Styles pulled from NT skin
public bool myBool1;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Name = "ExampleMenuIconDuplicateBug";
IsOverlay = true;
UserToolbarPosition = 10;
}
else if (State == State.DataLoaded)
{
if (ChartControl != null)
{
ChartControl.Dispatcher.BeginInvoke(new Action(() =>
{
myChartWindow = Window.GetWindow(ChartControl.Parent) as Chart;
if (myChartWindow == null)
return;
RemoveGhostItems();
if (!menuAdded)
{
QM_Initialize();
}
}), DispatcherPriority.Loaded);
}
}
else if (State == State.Terminated)
{
if (myChartWindow != null)
ChartControl.Dispatcher.InvokeAsync((Action)(() =>
{
try
{
QM_Dispose();
}
catch (Exception ex)
{
Print(ex.Message);
}
}));
}
}
// Create menu structure
private void QM_Initialize()
{
QM_MenuContainer();
// Top-level NTMenuItem
if (MyMenu == null)
{
QM_MenuIcon(); // Our Menu Icon
QM_PopulateMenu();
MyMenuContainer.Items.Add(MyMenu);
// Tab Change Handler
if (myChartWindow != null)
{
myChartWindow.MainTabControl.SelectionChanged -= TabChangedHandler;
myChartWindow.MainTabControl.SelectionChanged += TabChangedHandler;
}
if (TabSelected())
QM_AddMenuIcon();
}
}
// Menu Container hack
private void QM_MenuContainer()
{
// Outer menu container (provides rendering context)
if (MyMenuContainer == null)
{
MyMenuContainer = new Menu
{
Name = "MyMenuContainer",
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Left,
Margin = new Thickness(0),
Padding = new Thickness(0)
};
}
}
// Create our Menu Item(Button)
private void QM_MenuIcon()
{
// Our Main Menu Button
Geometry menuIcon = Geometry.Parse(
// Main symbol shape
"M14,0c-1.1,0-2,.9-2,2,0,.66.32,1.24.82,1.61l-2.57,6.42c-.08-.01-.17-.03-.25-.03-.37,0-.71.11-1.01.28l-1.27-1.27c.18-.3.28-.64.28-1.01,0-1.1-.9-2-2-2s-2,.9-2,2c0,.51.2.97.51,1.33l-1.86,2.79c-.21-.07-.42-.12-.65-.12-1.1,0-2,.9-2,2s.9,2,2,2,2-.9,2-2c0-.51-.2-.97-.51-1.33l1.86-2.79c.21.07.42.12.65.12.37,0,.71-.11,1.01-.28l1.27,1.27c-.18.3-.28.64-.28,1.01,0,1.1.9,2,2,2s2-.9,2-2c0-.66-.32-1.24-.82-1.61l2.57-6.42c.08.01.17.03.25.03,1.1,0,2-.9,2-2s-.9-2-2-2ZM2,15.09c-.6,0-1.09-.49-1.09-1.09s.49-1.09,1.09-1.09,1.09.49,1.09,1.09-.49,1.09-1.09,1.09ZM4.91,8c0-.6.49-1.09,1.09-1.09s1.09.49,1.09,1.09-.49,1.09-1.09,1.09-1.09-.49-1.09-1.09ZM10,13c-.55,0-1-.45-1-1s.45-1,1-1,1,.45,1,1-.45,1-1,1ZM14,3.09c-.6,0-1.09-.49-1.09-1.09s.49-1.09,1.09-1.09,1.09.49,1.09,1.09-.49,1.09-1.09,1.09Z " +
// The “U” character shape
"M2.55,4.4c1.63,0,2.55-.83,2.55-2.15V0h-1.49v2.14c0,.63-.33,1.09-1.06,1.09s-1.07-.46-1.07-1.09V0H0v2.25c0,1.32.92,2.15,2.55,2.15Z " +
// The “E” polygon converted into a valid closed path
"M10.58,3.19L7.35,3.19L7.35,2.6L9.93,2.6L9.93,1.61L7.35,1.61L7.35,1.07L10.52,1.07L10.52,0L5.9,0L5.9,4.26L10.58,4.26Z"
);
// Illustrator SVG settings 16x16 canvas
// Object > make pixel perfect
// expost SVG > decimals = 1 or 0
MyMenu = new NTMenuItem
{
Name = "MyMenu",
Icon = menuIcon,
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Left,
Margin = new Thickness(0),
Padding = new Thickness(0),
RenderTransform = new ScaleTransform(1.067, 1.067), // For some reason icon is drawn at 15x15 so we scal up
RenderTransformOrigin = new Point(0.5, 0.5),
SnapsToDevicePixels = true,
UseLayoutRounding = true
};
}
// Add menu to toolbar
private void QM_AddMenuIcon()
{
if (myChartWindow == null || myChartWindow.MainMenu == null || menuAdded)
return;
// Add only the button to toolbar (not the whole menu - avoids spacing)
if (UserToolbarPosition >= 0 && UserToolbarPosition < myChartWindow.MainMenu.Count)
myChartWindow.MainMenu.Insert(UserToolbarPosition, MyMenuContainer);
else
myChartWindow.MainMenu.Add(MyMenuContainer);
menuAdded = true;
}
// Populate our menu with items
private void QM_PopulateMenu()
{
MyMenu.Items.Add(QM_CreateMenuItem("My Bool 1"));
}
// Create Checkable Menu Item
private NTMenuItem QM_CreateMenuItem(string _header)
{
var menuItem = new NTMenuItem
{
Header = _header,
IsCheckable = true,
IsChecked = true,
IsEnabled = true,
Tag = "",
BorderThickness = new Thickness(0),
FontSize = 10,
FontWeight = FontWeights.Normal
};
return menuItem;
}
// Check if current tab is selected
private bool TabSelected()
{
if (myChartWindow == null || myChartWindow.MainTabControl == null)
return false;
foreach (TabItem tab in myChartWindow.MainTabControl.Items)
{
if (tab.Content is ChartTab chartTabContent &&
chartTabContent.ChartControl == ChartControl &&
tab == myChartWindow.MainTabControl.SelectedItem)
{
return true;
}
}
return false;
}
// Tab change event handler
private void TabChangedHandler(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count <= 0)
return;
tabItem = e.AddedItems[0] as TabItem;
if (tabItem == null)
return;
chartTab = tabItem.Content as ChartTab;
if (chartTab == null)
return;
if (TabSelected())
QM_AddMenuIcon();
else
QM_RemoveMenuIcon();
}
// Remove menu from toolbar only (for tab switching)
private void QM_RemoveMenuIcon()
{
if (myChartWindow?.MainMenu != null && MyMenu != null &&
myChartWindow.MainMenu.Contains(MyMenuContainer))
{
myChartWindow.MainMenu.Remove(MyMenuContainer);
menuAdded = false;
}
}
// Remove ghost items from previous instances
private void RemoveGhostItems()
{
if (myChartWindow?.MainMenu == null)
return;
var itemsToRemove = new System.Collections.Generic.List<object>();
foreach (object item in myChartWindow.MainMenu)
{
if (item is NTMenuItem ntItem &&
ntItem.Name != null &&
ntItem.Name.StartsWith("MyMenu"))
{
itemsToRemove.Add(item);
}
}
foreach (object item in itemsToRemove)
{
myChartWindow.MainMenu.Remove(item);
}
}
// Remove menu from chart (full cleanup)
private void QM_Dispose()
{
try
{
// Unsub from TabChangedHandler
if (myChartWindow != null)
{
myChartWindow.MainTabControl.SelectionChanged -= TabChangedHandler;
}
// Remove from toolbar
if (myChartWindow?.MainMenu != null && MyMenuContainer != null &&
myChartWindow.MainMenu.Contains(MyMenuContainer))
{
myChartWindow.MainMenu.Remove(MyMenuContainer);
}
}
catch { }
finally
{
menuAdded = false;
}
}
protected override void OnBarUpdate() { }
#region Persisted user settings
[Display(Name = "Toolbar Position", Description = "Position in toolbar (0=first)", Order = 6, GroupName = "Menu States")]
[Range(0, 100)]
public int UserToolbarPosition { get; set; }
#endregion
}
}
namespace Enums911501
{
}