Blank grid cells

Hello!

I need to have several buttons on my chart that are placed by different indicators, but I don’t want to add them to the Chart Trader window.

Since each indicator places its own grid, the solution would seem to be to have each indicator place buttons in only certain places on its grid, but create empty grid cells that are there to be superimposed upon by the other indicators’ active buttons.

The only problem is: I can’t figure out how to get it to create an empty grid cell. If you put in anything that makes it so a button or text will be invisible or functionless, it assumes you really don’t want it and refuses to create space for it in the grid.

This is problematic, because I only want one active button per grid cell. I don’t want to click on one button to make one thing happen, and then have something else happen instead. Or, even just to have the thing I wanted to have happen not happen, while nothing else happens either.

I guess it would be possible to just create duplicate buttons, and have the button in one indicator be a dummy that does nothing, but looks exactly the same as the operating one from the other indicator. That seems like a waste of code, however, and an otherwise very inefficient way of doing things. And that assumes that they’re both going to work, which I haven’t tried, but have no reason to assume.

Any suggestions?

Thanks in advance!

You mean a WPF grid? you can have empty cells if you define dimensions

Maybe that’s the issue. I’m trying to figure this out just using sample code from other indicators and web posts.

Does each indicator have to create its own grid, or is it possible to create a master grid where all the different indicators are putting their buttons?

It’s possible yes, but each indicator would have to:

  • Check if a grid exists
  • If so; insert it’s element into the grid at the right location
  • if not; create the grid

Then you need some logic that makes sure the grid is correctly disposed which depends on how many indicators are loaded etc…

I’m only talking about two, maybe three, indicators here, so this doesn’t sound too overwhelming. And I’m sure the “if grid exists” syntax would be identical in each one, so I really only have to write that once.

I actually found this in one of my own indicators that I wrote using sample code from I-don’t-rememberwhere (probably the old forum).

    			// Grid already exists
    			if (UserControlCollection.Contains(myGrid))
      			return;

    			// Add a control grid which will host our custom buttons
    			myGrid = new System.Windows.Controls.Grid
    			{
      				Name = "MyCustomGrid",
      				// Align the control to the right-side center of the chart
      				HorizontalAlignment = HorizontalAlignment.Right,
      				VerticalAlignment 	= VerticalAlignment.Center,
    			};

    			// Define the two columns in the grid
    			System.Windows.Controls.ColumnDefinition column1 = new System.Windows.Controls.ColumnDefinition();
    			System.Windows.Controls.ColumnDefinition column2 = new System.Windows.Controls.ColumnDefinition();

    			// Add the columns to the Grid
    			myGrid.ColumnDefinitions.Add(column1);
    			myGrid.ColumnDefinitions.Add(column2);

    			// Define where the buttons should appear in the grid
    			System.Windows.Controls.Grid.SetColumn(autoOrder, 0);
    								
    			// Add the buttons as children to the custom grid
				myGrid.Children.Add(autoOrder);
				
    			// Finally, add the completed grid to the custom NinjaTrader UserControlCollection
    			UserControlCollection.Add(myGrid);

Now, I see that this is checking for a grid, and then exiting out if it’s already there. If it isn’t, it creates one and places columns and cell content, but what would happen if the grid IS already there?

You answered your own question, if the grid is there it returns out of the method.

…thereby skipping all of the instructions that tell it to place items on the grid.

Where do those instructions go when the grid already exists?

The idea is this:

If that specific grid is in the collection, it means the indicator is running a second time in that chart and therefore trying to add the grid again, so nothing needs to happen because to add it a 2nd time would be a duplicate (thus the immediate return) .

[Each grid should have a unique name, and if a grid with the same name already exists in the collection, it means it’s already been added, and there is no reason to add it again.]

I mean, the purpose of the immediate return is because that grid and all its buttons should already be there, since it’s already in the collection.

You can experiment with that code by commenting out the return, then see what happens if you add the indicator a second time.

Okay, I got it working, but it doesn’t seem to be behaving the way I would expect it to.

I rewrote this sample indicator I got from somewhere to become two separate ones:

    		ChartControl.Dispatcher.InvokeAsync((() =>
			{
    			// Grid already exists
    			if (UserControlCollection.Contains(myGrid))
				{
					myBuyButton = new System.Windows.Controls.Button
        			{
	      				Name = "MyBuyButton",
    	  				Content = "LONG",
      					Foreground = Brushes.Black,
      					Background = Brushes.Yellow
    				};
					
      				myBuyButton.Click += OnMyButtonClick;
					
					myGrid.Children.Add(myBuyButton);
					
					System.Windows.Controls.Grid.SetColumn(myBuyButton, 0);
				
				}
				//return;

    			else
				{
					// Add a control grid which will host our custom buttons
	    			myGrid				= new System.Windows.Controls.Grid();
    				myGrid.Name			= "MyCustomGrid";
      				
					// Align the control to the right-side center of the chart
      				myGrid.HorizontalAlignment	= HorizontalAlignment.Right;
      				myGrid.VerticalAlignment	= VerticalAlignment.Center;
					myGrid.Width			= 200;
					 
        			// Define the two columns in the grid, one for each button
	    			System.Windows.Controls.ColumnDefinition column1 = new System.Windows.Controls.ColumnDefinition();
					System.Windows.Controls.ColumnDefinition column2 = new System.Windows.Controls.ColumnDefinition();
											
	    			// Add the columns to the Grid
    				myGrid.ColumnDefinitions.Add(column1);
    				myGrid.ColumnDefinitions.Add(column2);

	    			// Define the custom Buy Button control object
    				myBuyButton = new System.Windows.Controls.Button
    				{
      					Name = "MyBuyButton",
      					Content = "LONG",
          				Foreground = Brushes.White,
	      				Background = Brushes.Green
    				};

        			// Define the custom Sell Button control object
	    			//mySellButton = new System.Windows.Controls.Button
    				//{
      				//	Name = "MySellButton",
      				//	Content = "SHORT",
      				//	Foreground = Brushes.White,
      				//	Background = Brushes.Red
        			//};

	    			// Subscribe to each buttons click event to execute the logic we defined in OnMyButtonClick()
    				myBuyButton.Click += OnMyButtonClick;
    				//mySellButton.Click += OnMyButtonClick;

        			// Define where the buttons should appear in the grid
	    			System.Windows.Controls.Grid.SetColumn(myBuyButton, 0);
    				//System.Windows.Controls.Grid.SetColumn(mySellButton, 1);

    				// Add the buttons as children to the custom grid
    				myGrid.Children.Add(myBuyButton);
        			//myGrid.Children.Add(mySellButton);

	    			// Finally, add the completed grid to the custom NinjaTrader UserControlCollection
    				UserControlCollection.Add(myGrid);
				} 
 			}));

The opposite one has the Sell button defined in the first “if” and then the REMmed lines are uncommented and the uncommented lines are REMmed out of the “else.”

Now, I would think that whichever indicator loaded first would create the grid, and then the second one would see the grid there and run the code from that condition, but both of the buttons that are displayed are the “else” buttons. I don’t see a Yellow button, which is the one that the second indicator should create based on the grid already being present.

It’s not a huge deal because both buttons work, but why is the “if” condition not being met if the grid is already there?

The problem is in your myGrid comparison. The first indicator creates the grid as you are expecting because myGrid is not in the collection. Because myGrid is a class item, it is a reference type. This means your match criteria is looking for the exact class instance created by the first indicator. The second indicator does not have, nor know about, any of the actual reference objects in the first indicator.

if (UserControlCollection.Contains(myGrid))

To fix this, perform a value lookup instead of a reference comparison.

myGrid = UserControlCollection.FirstOrDefault(e => e.Name == "myCustomGrid")
if(myGrid == null) /* create the grid */
else /* do things with the existing grid */

Okay. When I tried that, I got this:

Cannot implicitly convert type ‘System.Windows.FrameworkElement’ to ‘System.Windows.Controls.Grid’. An explicit conversion exists (are you missing a cast?)

So, now what?

The FirstOrDefault() search function returns a framework element as the base type FrameworkElement. Since you know the element type of “MyCustomGrid”, you can just cast it to the correct type (using “as Grid”).

myGrid = UserControlCollection.FirstOrDefault(e => e.Name == "myCustomGrid") as Grid

Okay, I did that, and it compiles, but the second indicator is still not seeing the grid created by the first indicator.

I put a Print line right after that assignment…

			myGrid = UserControlCollection.FirstOrDefault(e => e.Name == "myCustomGrid") as System.Windows.Controls.Grid;
			Print("myCustomGrid = " + myGrid.Name.ToString());

but it errors out because you can’t assign null to a string.

Can I assume that the other indicator is creating its grid first, since it is higher in the list on my chart, and in the Output screen the Print line that executes after the grid is created by that indicator shows up before the one from the second indicator?

If so, then the first Indicator has already created myCustomGrid, and the second one should see it, but it clearly doesn’t. It crashes out if I ask it what it found in UserControlCollection.FirstOrDefault, and it goes ahead and creates a whole 'nother grid, evidenced by the fact that its Print line is only executed after the code that fires if the first grid isn’t found, and that Print line is still being executed. Also, the button created by the second indicator is in a completely different place when it creates its own grid, as opposed to adding a button to the existing grid, so I know that they are still both creating separate grids.

After looking at this a little more closely, UserControlCollection is a collection that’s local to the render base object. That means each indicator will have it’s own separate collection.

To find UI elements across indicator boundaries you need to use WPF directly. Try searching using the UI’s document model, not NT’s special helper container. Note the suggestion below assumes both instances of the indicator are on the same chart. If you need to go across chart boundaries you’ll have to start higher up in the document model.

Try replacing UserControlCollection.FirstOrDefault with this instead:

ChartControl.FindName("myCustomGrid");
			myGrid = ChartControl.FindName("myCustomGrid") as System.Windows.Controls.Grid;
			Print("2nd Ind. - myCustomGrid = " + myGrid.Name.ToString());

Okay, this compiles, but it still crashes out at the Print line. If I REM out the Print line, it creates its own grid again.

BTW, both Indicators are on the same chart.

Okay, so if UserControlCollection is local to the Indicator, how do we add the Grid to ChartPanel and not UserControlCollection? Because the assignment that creates the grid in every instance is

			UserControlCollection.Add(myGrid);

How should this look if it needs to be added to ChartPanel instead?

I tried

			ChartPanel.ChartObjects.Add(myGrid);

but I get this:

Argument 1: cannot convert from ‘System.Windows.Controls.Grid’ to ‘NinjaTrader.Gui.NinjaScript.IChartObject’

I feel like I’m getting close, though…

UserControlCollection is a special NT collection that is local (meaning each indicator instance will have it’s own UserControlCollection) to the script object (aka the indicator). Behind the scenes NT add those controls to the WPF UI for you. To find them again, using the WPF UI model, you can search with the FindName() function I mentioned before. If your FindName() call is returning null, the ui element is not in the document model. You can try again later if you think it should be there (ie add a delay timer and try again), or just create the new one.