When is myTextBox.Text no longer null?

The null is timing, not the TextBox. Dispatcher.InvokeAsync only queues the build on the chart’s thread. OnBarUpdate runs ahead on its own thread, so myTextBox doesn’t exist yet for the first ~2500 bars. OnBarUpdate also shouldn’t touch a WPF control directly, since it’s on the wrong thread.

The cleaner way is to bind the TextBox to a property. OnBarUpdate only reads or sets the property and never touches the control, so it doesn’t matter when the box gets built. It works both ways: typing changes the property, and setting the property in code changes the box.

Here’s a small working indicator that shows how this could be accomplished.

#region Using declarations
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Xml.Serialization;
using NinjaTrader.Gui.Chart;
#endregion

namespace NinjaTrader.NinjaScript.Indicators
{
	public class HTTextBoxBindDemo : Indicator, INotifyPropertyChanged
	{
		private volatile string boxText = "44";
		private TextBox myTextBox;

		public event PropertyChangedEventHandler PropertyChanged;

		// The TextBox is bound to this. Typing changes it, and setting it in code changes the box.
		[Browsable(false)]
		[XmlIgnore]
		public string BoxText
		{
			get { return boxText; }
			set
			{
				boxText = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new PropertyChangedEventArgs("BoxText"));
			}
		}

		protected override void OnStateChange()
		{
			if (State == State.SetDefaults)
			{
				Name		= "HTTextBoxBindDemo";
				Description	= "One TextBox bound both ways to a property.";
				Calculate	= Calculate.OnBarClose;
				IsOverlay	= true;
			}
			else if (State == State.DataLoaded)
			{
				if (ChartControl != null)
					ChartControl.Dispatcher.InvokeAsync(BuildTextBox);
			}
			else if (State == State.Terminated)
			{
				if (ChartControl != null)
					ChartControl.Dispatcher.InvokeAsync(RemoveTextBox);
			}
		}

		protected override void OnBarUpdate()
		{
			if (State != State.Realtime)
				return;

			// Code to box: the bar's range in ticks. Calculate decides how often:
			// On bar close = once per bar, On price change = every price change.
			BoxText = ((int)Math.Round((High[0] - Low[0]) / TickSize)).ToString();
		}

		private void BuildTextBox()
		{
			myTextBox = new TextBox
			{
				Width				= 60,
				HorizontalAlignment	= HorizontalAlignment.Right,
				VerticalAlignment	= VerticalAlignment.Top,
				Margin				= new Thickness(0, 10, 100, 0),
				HorizontalContentAlignment	= HorizontalAlignment.Center
			};

			myTextBox.SetBinding(TextBox.TextProperty, new Binding("BoxText")
			{
				Source				= this,
				Mode				= BindingMode.TwoWay,
				UpdateSourceTrigger	= UpdateSourceTrigger.PropertyChanged
			});

			myTextBox.PreviewKeyDown += OnBoxPreviewKeyDown;
			UserControlCollection.Add(myTextBox);
		}

		private void RemoveTextBox()
		{
			if (myTextBox == null)
				return;

			myTextBox.PreviewKeyDown -= OnBoxPreviewKeyDown;
			BindingOperations.ClearBinding(myTextBox, TextBox.TextProperty);
			UserControlCollection.Remove(myTextBox);
			myTextBox = null;
		}

		// The chart grabs typed keys to open its instrument selector.
		// Insert the character ourselves and mark the key handled so the chart never sees it.
		private void OnBoxPreviewKeyDown(object sender, KeyEventArgs e)
		{
			if (Keyboard.Modifiers != ModifierKeys.None)
				return;

			string ch = null;
			if (e.Key >= Key.D0 && e.Key <= Key.D9)
				ch = ((int)(e.Key - Key.D0)).ToString();
			else if (e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9)
				ch = ((int)(e.Key - Key.NumPad0)).ToString();
			else if (e.Key == Key.OemPeriod || e.Key == Key.Decimal)
				ch = ".";
			else if (e.Key == Key.OemMinus || e.Key == Key.Subtract)
				ch = "-";
			else if ((e.Key >= Key.A && e.Key <= Key.Z) || e.Key == Key.Space)
			{
				e.Handled = true;	// numbers only: swallow letters so they never reach the chart
				return;
			}

			if (ch == null)
				return;

			int pos = myTextBox.SelectionStart;
			myTextBox.SelectedText = ch;
			myTextBox.Select(pos + 1, 0);
			e.Handled = true;
		}
	}
}

What it does:

  • The box starts at 44.
  • OnBarUpdate writes the bar’s range in ticks into the box. With Calculate set to On bar close it changes once per bar. With On price change it changes every tick.
  • You can type in the box anytime. Whatever is in it, typed or set by code, is in BoxText for OnBarUpdate to read, for example double.TryParse(BoxText, out value).
  • The PreviewKeyDown handler is what stops the instrument selector from opening while you type. The chart grabs typed keys, so the handler inserts digits, period and minus itself and swallows letters. That’s why this particular textbox example is numbers only.

Hope this might help, and give you some additional inspiration for your project.

While I’m here, a quick plug for my newest tool. If you build chart tools like this, it lets an AI read your live charts and help you test them. The video has chapters, so you can jump straight to what interests you:

NinjaTrader 8’s Ultimate AI Connection | HTAIBridge

Please Be Safe in this Crazy World!
:smiling_face_with_sunglasses:

1 Like