Skip to content

Controls

Introduction

Controls enable certain interactions involving cBots, indicators and plugins. Using the below guide, you can easily create basic and advanced UI controls directly on a chart.

There are several built-in classes that represent popular controls such as buttons, text blocks, text boxes, and shapes. However, you can also create custom controls.

Consider the following example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using cAlgo.API;

namespace ChartControlsTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.None)]
    public class ChartControls : Indicator
    {
        [Parameter(DefaultValue = "Click Me")]
        public string Text { get; set; }

        [Parameter(DefaultValue = HorizontalAlignment.Center)]
        public HorizontalAlignment HorizontalAlignment { get; set; }

        [Parameter(DefaultValue = VerticalAlignment.Center)]
        public VerticalAlignment VerticalAlignment { get; set; }

        protected override void Initialize()
        {
            var button = new Button
            {
                Text = Text,
                HorizontalAlignment = HorizontalAlignment,
                VerticalAlignment = VerticalAlignment
            };

            button.Click += Button_Click;

            Chart.AddControl(button);
        }

        private void Button_Click(ButtonClickEventArgs obj)
        {
            obj.Button.Text = "You clicked me, thanks";
        }

        public override void Calculate(int index)
        {
        }
    }
}

If you build the above indicator and create an instance, you should see a grey 'Click Me' button right at the centre of the chart.

The Difference Between Controls and Chart Objects

In an earlier section, we have already covered chart objects. Controls enable users to interact with cBots/indicators. Conversely, chart objects give the opportunity to draw something on the trade chart or in a detached indicator output window.

Chart controls derive from the ControlBase class while chart objects are derived from the ChartObject class.

Chart controls are positioned statically by using different alignment options. While chart objects can be positioned in exactly the same way, their position can also change dynamically depending on certain X and Y coordinates.

Similarly to chart objects, chart controls can be added both to the 'main' chart and/or any indicator output windows (if they exist). An example of such positioning is given below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using cAlgo.API;

namespace ChartControlsTest
{
    [Indicator(IsOverlay = false, AccessRights = AccessRights.None)]
    public class ChartControls : Indicator
    {
        [Parameter(DefaultValue = "Click Me")]
        public string Text { get; set; }

        [Parameter(DefaultValue = HorizontalAlignment.Center)]
        public HorizontalAlignment HorizontalAlignment { get; set; }

        [Parameter(DefaultValue = VerticalAlignment.Center)]
        public VerticalAlignment VerticalAlignment { get; set; }

        protected override void Initialize()
        {
            var button = new Button
            {
                Text = Text,
                HorizontalAlignment = HorizontalAlignment,
                VerticalAlignment = VerticalAlignment
            };

            button.Click += Button_Click;

            IndicatorArea.AddControl(button);
        }

        private void Button_Click(ButtonClickEventArgs obj)
        {
            obj.Button.Text = "You clicked me, thanks";
        }

        public override void Calculate(int index)
        {
        }
    }
}

After creating an indicator instance, you should see a 'Click Me' button in the indicator output window.

ColorPicker

The ColorPicker control empowers traders to select their preferred colour for key elements or objects in cTrader. For example, a developer may integrate the ColorPicker control into a cBot that draws trendlines so that users can select their preferred colour for each trendline. This setup makes it easier for traders to distinguish between various trendline types.

A plugin that adds indicators to charts can also implement the ColorPicker control since this feature allows users to choose a colour for different indicator lines.

When users click the colour square, the colour selector appears. Users can then choose between standard and custom colours.

Code sample:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using cAlgo.API;
using cAlgo.API.Collections;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Plugins
{
    [Plugin(AccessRights = AccessRights.None)]
    public class ColorPickerTest : Plugin
    {
        protected override void OnStart()
        {
            var aspBlock = Asp.SymbolTab.AddBlock("Color Picker");

            var colorPicker = new ColorPicker {IsStretched = true, SelectedColor = Color.Red, Height = 20, Width = 20, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center};

            aspBlock.Child = colorPicker;

            colorPicker.SelectedColorChanged += colorPicker_SelectedColorChanged;

        }

        private void colorPicker_SelectedColorChanged(ColorPickerSelectedColorChangedEventArgs obj)
        {
            Print($"colorPicker_SelectedColorChanged, NewSelectedColor: {obj.NewSelectedColor} | PreviousSelectedColor: {obj.PreviousSelectedColor}");
        }

        protected override void OnStop()
        {
            // Handle Plugin stop here
        }
    }        
}

Image title

DropZone

The DropZone control enables traders to perform drag-and-drop actions involving cBots, indicators or plugins. To implement this control in your algorithm, use the DropZone class. The DropZone control can be manipulated like any other control.

When a user drops a compatible file or folder into the box, the Dropped event is generated. When a user drops a folder or multiple folders into the box, only compatible files with the specified extensions in FilterExtensions are processed and copied.

Note

The dropped files are usually copied and saved in this location: ..\Documents\cAlgo\Data\{AlgoType}\{AlgoName}\Selected Files\

The location for cBot instances differs from the above: ..\Documents\cAlgo\Data\cBots\{cBotName}\{unique-instance-number}\Selected files\

This plugin code shows you how to add a DropZone control to Trade Watch:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
using System;
using System.Drawing;
using cAlgo.API;
using cAlgo.API.Collections;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Plugins
{
    [Plugin(AccessRights = AccessRights.None)]
    public class TradeWatchTabSample : Plugin
    {
        protected override void OnStart()
        {
            var tab = TradeWatch.AddTab("DropZone");

            var _border = new Border
            {
                BorderThickness = 3,
                BorderColor = Color.DarkGray,
                Opacity = 0.7,
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center,
                Height = 200,
                Width = 350,
                BackgroundColor = Color.LightGray
            };

            var _textBlock = new TextBlock
            {
                Text = "Drag your files here",
                Opacity = 1,
                ForegroundColor = Color.DarkGray,
                FontSize = 16,
                VerticalAlignment = VerticalAlignment.Center,
                HorizontalAlignment = HorizontalAlignment.Center
            };

            var _dropzone = new DropZone
            {
                FilterExtensions = new string[] { "txt", "algo", "csv" },
                IsDirectoryDropAllowed = true,
                IsVisible = true,
                IsEnabled = true
            };
            _border.Child = _textBlock;
            _dropzone.Child = _border;
            //   _dropzone.Child = _textBlock;
            tab.Child = _dropzone;

            _dropzone.Dropped += Dropped_file;
        }

        private void Dropped_file(DroppedEventArgs obj)
        {
            Print("File has been added!");
        }
    }
}

Image title

ProgressBar

The ProgressBar control displays the progression of an ongoing operation. Progress bars make algorithms more user-friendly and help manage traders' expectations regarding the wait time for certain operations.

cTrader Algo API allows developers to add two types of ProgressBar controls: Infinite Control and Determinate Control.

Infinite Control

You may want to use Infinite Control in the following situations:

  • You cannot determine the wait time for an operation.
  • You cannot detect the progression of an operation.
  • You do not want to indicate how long an operation will take.

Image title

Determinate Control

You may want to use this control when you can determine the wait time for an operation and want users to see an indicator based on that time.

To display the progress of the operation, use the Value property to set a figure. To set the progress bar percentage, use the Minimum and Maximum properties.

Image title

The plugin code below shows you how to create a progress bar using both infinite and determinate (green) controls:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System;
using cAlgo.API;
using cAlgo.API.Collections;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Plugins
{
    [Plugin(AccessRights = AccessRights.None)]
    public class ProgressBarTest : Plugin
    {
        private ProgressBar _infiniteProgressBar;
        private ProgressBar _determinateProgressBar;

        protected override void OnStart()
        {
            var aspBlock = Asp.SymbolTab.AddBlock("Progress bar");

            var panel = new StackPanel() {Height = 200, Width = 200, Orientation = Orientation.Vertical};

            _infiniteProgressBar = new ProgressBar {IsIndeterminate = true, Height = 20};

            panel.AddChild(_infiniteProgressBar);

            _determinateProgressBar = new ProgressBar {IsIndeterminate = false, Height = 20};

            panel.AddChild(_determinateProgressBar);

            var autoProgressButton = new Button {Text = "Start"};

            autoProgressButton.Click += AutoProgressButton_Click;

            panel.AddChild(autoProgressButton);

            aspBlock.Child = panel;

        }

        private void AutoProgressButton_Click(ButtonClickEventArgs obj)
        {
            Timer.Start(1);
        }

        protected override void OnTimer()
        {
            _determinateProgressBar.Value++;

            Print($"Value: {_determinateProgressBar.Value}");
        }

        protected override void OnStop()
        {
            // Handle Plugin stop here
        }
    }        
}

Dialogues

OpenFileDialog and OpenFolderDialog

cTrader Algo API provides the OpenFileDialog interface to enable users to select a file for an algorithm. When a user selects a file in the resulting window, the file is copied to the algorithm Selected files folder. An algorithm can work files inside its Selected files folder without restrictions.

Tip

Use the OpenFileDialog functionality when you need to load important data files, backup or configuration files, custom indicators or scripts.

The code below shows you how to use the OpenFileDialog dialogue in an indicator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System;
using cAlgo.API;
using System.Text;

namespace cAlgo
{
    [Indicator(AccessRights = AccessRights.None, IsOverlay = true)]
    public class OpenFileDialogExample : Indicator
    {
        private OpenFileDialog _openFileDialog;

        protected override void Initialize()
        {
            _openFileDialog = new OpenFileDialog()
            {
                InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
                Multiselect = true,
                Title = "My Open File Dialog Title"
            };

            var showOpenFileDialog = new Button { Text = "Show Open File Dialog" };
            showOpenFileDialog.Click += showOpenFileDialog_Click;

            var panel = new StackPanel
            {
                Orientation = Orientation.Vertical,
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center
            };

            panel.AddChild(showOpenFileDialog);
            Chart.AddControl(panel);
        }

        private void showOpenFileDialog_Click(ButtonClickEventArgs obj)
        {
            var result = _openFileDialog.ShowDialog();
            Print($"Result: {result} | FileName: {_openFileDialog.FileName} | FileNames: {string.Join(',', _openFileDialog.FileNames)}");
        }

        public override void Calculate(int index)
        {
        }
    }
}

Image title

cTrader Algo API provides the OpenFolderDialog interface to enable users to specify a folder for an algorithm. When a user selects a folder in the resulting window, all files and folders with files inside the selected folder are copied to the algorithm Selected files folder.

Tip

Use the OpenFolderDialog functionality when you need to load a folder containing critical data files, backup or configuration files, custom indicators or scripts.

Selected files and folders are usually copied to the Selected files folder because an algorithm can work with items inside that folder even when its AccessRights is set to None.

Note

An algorithm Selected files folder is usually along this path: ..\Documents\cAlgo\Data\{AlgoType}\{AlgoName}\Selected files\

The Selected files folder for a cBot instance differs from the above: ..\Documents\cAlgo\Data\cBots\{cBotName}\{Instance-Id}\Selected files\

The code below shows you how to use the OpenFolderDialog dialogue in an indicator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System;
using cAlgo.API;

namespace cAlgo
{
    [Indicator(AccessRights = AccessRights.None, IsOverlay = true)]
    public class OpenFolderDialogExample : Indicator
    {
        private OpenFolderDialog _openFolderDialog;

        protected override void Initialize()
        {
            _openFolderDialog = new OpenFolderDialog()
            {
                InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
                Title = "My Open Folder Dialog Title"
            };

            var showOpenFolderDialog = new Button { Text = "Show Open Folder Dialog" };
            showOpenFolderDialog.Click += showOpenFolderDialog_Click;

            var panel = new StackPanel
            {
                Orientation = Orientation.Vertical,
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center
            };

            panel.AddChild(showOpenFolderDialog);
            Chart.AddControl(panel);
        }

        private void showOpenFolderDialog_Click(ButtonClickEventArgs obj)
        {
            var result = _openFolderDialog.ShowDialog();
            Print($"Result: {result} | FolderName: {_openFolderDialog.FolderName}");
        }

        public override void Calculate(int index)
        {
        }
    }
}

Image title

SaveFileDialog

cTrader Algo API provides the SaveFileDialog interface to enable users to save files (locally) to their computers. When the relevant window appears, a user selects the folder to which they want to save the file and enters a name for the file. The file is then saved in the specified location.

Tip

Use theSaveFileDialog functionality when you need to do any of the following:

  • Save performance reports, backtesting results, configuration files or optimisation data.
  • Export trading logs, operation journals or general user activity data.
  • Store custom indicators, data for charts and visualisations or parameters for a strategy.

The code below shows you how to use the SaveFileDialog dialogue in an indicator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using System;
using cAlgo.API;
using System.Text;

namespace cAlgo
{
    [Indicator(AccessRights = AccessRights.None, IsOverlay = true)]
    public class SaveFileDialogExample : Indicator
    {
        private SaveFileDialog _saveFileDialog;

        protected override void Initialize()
        {
            _saveFileDialog = new SaveFileDialog()
            {
                InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
                Title = "My Save File Dialog Title"
            };

            var showSaveFileDialogText = new Button { Text = "Show Save File Dialog (Set Content as text)" };
            var showSaveFileDialogBytes = new Button { Text = "Show Save File Dialog (Set Content as bytes)" };

            showSaveFileDialogText.Click += showSaveFileDialogText_Click;
            showSaveFileDialogBytes.Click += showSaveFileDialogBytes_Click;

            var panel = new StackPanel
            {
                Orientation = Orientation.Vertical,
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center
            };
            panel.AddChild(showSaveFileDialogText);
            panel.AddChild(showSaveFileDialogBytes);
            Chart.AddControl(panel);
        }

        private void showSaveFileDialogText_Click(ButtonClickEventArgs obj)
        {
            var result = _saveFileDialog.ShowDialog();
            Print($"Result: {result}");
            if (result != FileDialogResult.Cancel)
            {
                _saveFileDialog.WriteToFile("Test in text");
            }
        }

        private void showSaveFileDialogBytes_Click(ButtonClickEventArgs obj)
        {
            var result = _saveFileDialog.ShowDialog();
            Print($"Result: {result}");
            if (result != FileDialogResult.Cancel)
            {
                _saveFileDialog.WriteToFile(Encoding.UTF8.GetBytes("Test in bytes"));
            }
        }

        public override void Calculate(int index)
        {
        }
    }
}

Image title

Custom Controls

A custom control is a control that is, in essence, 'made up' of several pre-defined controls. In other words, it is a control for which other controls constitute its content.

Custom controls act as reusable classes similarly to built-in controls.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
using cAlgo.API;

namespace ChartControlsTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.None)]
    public class ChartControls : Indicator
    {
        [Parameter("# Of Text Areas", DefaultValue = 4)]
        public int NumberOfTextAreas { get; set; }

        protected override void Initialize()
        {
            var panel = new WrapPanel
            {
                Orientation = Orientation.Horizontal,
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center
            };

            for (int i = 0; i < NumberOfTextAreas; i++)
            {
                var textArea = new TextArea
                {
                    HorizontalAlignment = HorizontalAlignment.Right,
                    VerticalAlignment = VerticalAlignment.Stretch,
                    Margin = 5,
                };

                panel.AddChild(textArea);
            }

            Chart.AddControl(panel);
        }

        public override void Calculate(int index)
        {
        }
    }

    public class TextArea : CustomControl
    {
        private readonly TextBox _textBox;

        public TextArea()
        {
            _textBox = new TextBox
            {
                TextAlignment = TextAlignment.Left,
                TextWrapping = TextWrapping.Wrap,
                AcceptsReturn = true,
                AcceptsTab = true,
                Width = 300,
                Height = 200,
            };

            AddChild(_textBox);
        }
    }
}

An instance of this indicator should display four text boxes directly in the centre of the main chart.

Organising Controls with Panels

To make working with controls more convenient, you may want to place several controls into a distinct 'group' with its own position in the UI. To do this, you can use Panels class and its derivatives.

Think of a panel as a control with other controls as its contents. cTrader supports five different classes inheriting from the base Panels class (which itself inherits from the Control class).

  • Canvas
  • DockPanel
  • Grid
  • StackPanel
  • WrapPanel

Each of the above classes uses different panel layouts and positioning strategies as discussed below.

Canvas

The canvas is a panel that allows for positioning controls based on certain X and Y coordinates.

Notably, the X and Y axes are different from the ones used by chart objects/drawings. The X and Y coordinates used by the Canvas class represent numeric values starting from (0, 0) from the top left corner of the chart.

Consider the following example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using cAlgo.API;

namespace ChartControlsTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.None)]
    public class ChartControls : Indicator
    {
        [Parameter(DefaultValue = "Click Me")]
        public string Text { get; set; }

        [Parameter(DefaultValue = 0)]
        public double Left { get; set; }

        [Parameter(DefaultValue = 0)]
        public double Top { get; set; }

        [Parameter(DefaultValue = 0)]
        public double Margin { get; set; }

        [Parameter(DefaultValue = 10)]
        public double Padding { get; set; }

        protected override void Initialize()
        {
            var button = new Button
            {
                Text = Text,
                Left = Left,
                Top = Top,
                Margin = Margin,
                Padding = Padding
            };

            button.Click += Button_Click;

            var canvas = new Canvas();

            /* We add our button control to the canvas
            panel. */
            canvas.AddChild(button);

            // We add our canvas panel to the chart.
            Chart.AddControl(canvas);
        }

        private void Button_Click(ButtonClickEventArgs obj)
        {
            obj.Button.Text = "You clicked me, thanks";
        }

        public override void Calculate(int index)
        {
        }
    }
}

Upon creating an instance of the above indicator, you should see the 'Click Me' button in the top left corner of the chart.

The Top property of a control determines its positioning on the Y-axis. In turn, the Left property defines its positioning on the X-axis.

The above code also uses the Padding and Margin properties. Padding refers to the distance between the control contents and its outer borders. Margin is the distance between the control and the borders of its 'parent'. The Padding and Margin properties are applicable to all panels, not just the canvas class. They are useful for managing spacing between your controls.

In the majority of cases, the Canvas class is used to group only a small number of controls.

Dock Panel

The DockPanel class is used to 'dock' a control in a static location on the chart. There are four possible dock positions.

  • Top
  • Bottom
  • Left
  • Right

Each control has a Dock property. When using the DockPanel class, you can use this property to position a control inside a DockPanel. This is illustrated in the following example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using cAlgo.API;

namespace ChartControlsTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.None)]
    public class ChartControls : Indicator
    {
        [Parameter(DefaultValue = Dock.Top)]
        public Dock TextBoxDock { get; set; }

        [Parameter(DefaultValue = Dock.Bottom)]
        public Dock ButtonDock { get; set; }

        private TextBox _textBox;

        protected override void Initialize()
        {
            _textBox = new TextBox
            {
                Margin = 5,
                Text = "Write here...",
                ForegroundColor = Color.Yellow,
                Dock = TextBoxDock,
                Width = 200
            };

            var button = new Button
            {
                Text = "Tell what I wrote?",
                Dock = ButtonDock,
                Width = 200
            };

            button.Click += Button_Click;

            var dockPanel = new DockPanel
            {
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center,
            };

            dockPanel.AddChild(_textBox);
            dockPanel.AddChild(button);

            Chart.AddControl(dockPanel);
        }

        private void Button_Click(ButtonClickEventArgs obj)
        {
            obj.Button.Text = $"You wrote: {_textBox.Text}";
        }

        public override void Calculate(int index)
        {
        }
    }
}

When creating an instance of this indicator, you should see a dock panel in the centre of the chart containing a text field and a clickable button.

Note that dock panels can be populated either horizontally or vertically. You cannot use both of these alignments at the same time. The dock panel orientation is set automatically when setting up the first control in a DockPanel. If the Dock property of the first control is set to either Top or Bottom, the entire DockPanel will be oriented vertically, and vice versa.

Stack Panel

Stack panels are one of the most frequently used controls due to their simplicity and usability. Stack panels align 'child' controls either horizontally or vertically one by one. As shown below, you only have to set up its orientation and the class will manage the remainder.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
using cAlgo.API;

namespace ChartControlsTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.None)]
    public class ChartControls : Indicator
    {
        [Parameter(DefaultValue = Orientation.Vertical)]
        public Orientation Orientation { get; set; }

        protected override void Initialize()
        {
            var firstNameTextBox = new TextBox
            {
                Margin = 5,
                Text = "First Name...",
                Width = 200
            };

            var lastNameTextBox = new TextBox
            {
                Margin = 5,
                Text = "Last Name...",
                Width = 200
            };

            var isMarriedCheckBox = new CheckBox
            {
                Text = "Is Married?",
                Margin = 5,
            };

            var submitButton = new Button
            {
                Text = "Submit",
                Margin = 5
            };

            var stackPanel = new StackPanel
            {
                Orientation = Orientation,
                HorizontalAlignment = HorizontalAlignment.Right,
                VerticalAlignment = VerticalAlignment.Bottom,
                BackgroundColor = Color.FromArgb(80, Color.Gold),
                Margin = 20
            };

            stackPanel.AddChild(firstNameTextBox);
            stackPanel.AddChild(lastNameTextBox);
            stackPanel.AddChild(isMarriedCheckBox);
            stackPanel.AddChild(submitButton);

            Chart.AddControl(stackPanel);
        }

        public override void Calculate(int index)
        {
        }
    }
}

After creating an indicator instance, you should see a horizontal stack panel with two text fields and a 'Submit' button in the bottom-right corner of the main chart.

Wrap Panel

Wrap panels are mostly identical to stack panels. However, when there is not enough space to fit all elements of a wrap panel, it will automatically 'wrap' the remaining controls to the next line on the Y axis.

Grid

Think of grids as spreadsheets with a set number of columns and rows. When using grids, you can add controls to each separate cell.

As demonstrated below, when you create an instance of the Grid class, you pass the number of its rows and columns as integer arguments. When adding new 'children' or controls, you similarly have to specify the number of 'child' rows and columns.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using cAlgo.API;

namespace ChartControlsTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.None)]
    public class ChartControls : Indicator
    {
        [Parameter("Grid Rows #", DefaultValue = 10)]
        public int GridRowsNumber { get; set; }

        [Parameter("Grid Columns #", DefaultValue = 2)]
        public int GridColumnsNumber { get; set; }

        protected override void Initialize()
        {
            var grid = new Grid(GridRowsNumber, GridColumnsNumber)
            {
                BackgroundColor = Color.Gold,
                Opacity = 0.6,
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center,
                ShowGridLines = true
            };

            for (int iRow = 0; iRow < GridRowsNumber; iRow++)
            {
                for (int iColumn = 0; iColumn < GridColumnsNumber; iColumn++)
                {
                    grid.AddChild(new TextBlock
                    {
                        Text = string.Format("Row {0} and Column {1}", iRow, iColumn),
                        Margin = 5,
                        ForegroundColor = Color.Black,
                        FontWeight = FontWeight.ExtraBold
                    }, iRow, iColumn);
                }
            }

            Chart.AddControl(grid);
        }

        public override void Calculate(int index)
        {
        }
    }
}

When creating an instance of the above indicator, you should see a 10x2 grid right in the centre of the chart.

Positioning Controls Within Price and Time Coordinates

In addition to panel controls, cTrader allows for specifying price and time coordinates for controls directly in the chart area. The AddControl() and MoveControl() methods provide algo developers with this functionality.

Use the following method overloads to manage the coordinates of your chart controls depending on your preferences.

To add a chart control to the chart/indicator area on absolute bar index and price (x, y) coordinates.

1
void AddControl(ControlBase control, int barIndex, double y)

To move a chart control to the chart/indicator area on absolute bar index and price (x, y) coordinates.

1
void MoveControl(ControlBase control, int barIndex, double y)

To add a chart control to the chart/indicator area on absolute time and price (x, y) coordinates.

1
void AddControl(ControlBase control, DateTime time, double y)

To move a chart control to the chart/indicator area on absolute time and price (x, y) coordinates.

1
void MoveControl(ControlBase control, DateTime time, double y)

To add a chart control to the chart/indicator area on absolute time (x) coordinate.

1
void AddControl(ControlBase control, DateTime time)

To move a chart control to the chart/indicator area on absolute time (x) coordinate.

1
void MoveControl(ControlBase control, DateTime time)

To add a chart control to the chart/indicator area on absolute bar index (x) coordinate.

1
void AddControl(ControlBase control, int barIndex)

To move a chart control to the chart/indicator area on absolute bar index (x) coordinate.

1
void MoveControl(ControlBase control, int barIndex)

To add a chart control to the chart/indicator area on absolute price (y) coordinate.

1
void AddControl(ControlBase control, double y)

To move a chart control to the chart/indicator area on absolute price (y) coordinate.

1
void MoveControl(ControlBase control, double y)

The ControlBase parameter class can include any other subordinate class (e.g., Control, Button, etc.) for the method to be called in accordance with the above signatures.

The following cBot example adds the 'Click me!' button above the last bar on the chart. The button is moved ahead together with every new bar added. After clicking on the button, a message box appears on the screen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using cAlgo.API;
using cAlgo.API.Collections;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(AccessRights = AccessRights.None)]
    public class ButtonTest : Robot
    {
        [Parameter(DefaultValue = "Hello world!")]
        public string Message { get; set; }

        Button button;

        protected override void OnStart() 
        {
            button = new Button
            {
                Text = "Click me!"
            };

            button.Click += Button_Click;

            Chart.AddControl(button, Bars.Count - 1);
        }

        protected override void OnBar() 
        {
            Chart.MoveControl(button, Bars.Count - 1);
        }

        private void Button_Click(ButtonClickEventArgs obj)
        {
            MessageBox.Show(Message, "Title/Caption");
        }
    }
}

Warning

The number of root controls to be added on a chart is limited to 100 because of possible performance issues. This limitation applies only to one algorithm instance and controls attached to price and/or bars.

The 'Padding' and 'Margin' Properties

The Margin property defines the space between the borders of a Control object and its parent (e.g., a chart, a panel, a border, etc.).

In turn, the Padding property determines the space between the control contents and its borders. You can vary the property values so that they are all different for different sides.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using cAlgo.API;

namespace ChartControlsTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.None)]
    public class ChartControls : Indicator
    {
        [Parameter(DefaultValue = "Click Me")]
        public string Text { get; set; }

        [Parameter(DefaultValue = 0)]
        public double Left { get; set; }

        [Parameter(DefaultValue = 0)]
        public double Top { get; set; }

        [Parameter(DefaultValue = 0, Group = "Margin")]
        public double LeftMargin { get; set; }

        [Parameter(DefaultValue = 0, Group = "Margin")]
        public double TopMargin { get; set; }

        [Parameter(DefaultValue = 0, Group = "Margin")]
        public double RightMargin { get; set; }

        [Parameter(DefaultValue = 0, Group = "Margin")]
        public double BottomMargin { get; set; }

        [Parameter(DefaultValue = 5, Group = "Padding")]
        public double LeftPadding { get; set; }

        [Parameter(DefaultValue = 5, Group = "Padding")]
        public double TopPadding { get; set; }

        [Parameter(DefaultValue = 5, Group = "Padding")]
        public double RightPadding { get; set; }

        [Parameter(DefaultValue = 5, Group = "Padding")]
        public double BottomPadding { get; set; }

        protected override void Initialize()
        {
            var button = new Button
            {
                Text = Text,
                Left = Left,
                Top = Top,
                Margin = new Thickness(LeftMargin, TopMargin, RightMargin, BottomMargin),
                Padding = new Thickness(LeftPadding, TopPadding, RightPadding, BottomPadding)
            };

            button.Click += Button_Click;

            var canvas = new Canvas();

            canvas.AddChild(button);

            Chart.AddControl(canvas);
        }

        private void Button_Click(ButtonClickEventArgs obj)
        {
            obj.Button.Text = "You clicked me, thanks";
        }

        public override void Calculate(int index)
        {
        }
    }
}

An instance of this indicator will create a grey 'Click Me' button in the top-left corner of the chart. Modify the parameters in the 'Add instance'/'Modify parameters' windows to see how exactly different margin and padding values change how the control is displayed.

Styles

When using styles you can give a similar look to several different types of controls. This is particularly useful when dealing with a large (five or more) number of controls.

The Style class allows for setting values for different properties of controls such as Margin or BackgroundColor once. Afterward, you can reuse these values as a template for multiple other controls.

We can also create a consistent look for multiple controls without using the Style class at all.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using cAlgo.API;

namespace ChartControlsTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.None)]
    public class ChartControls : Indicator
    {
        protected override void Initialize()
        {
            var firstTextBox = new TextBox
            {
                ForegroundColor = Color.Red,
                Margin = 5,
                FontFamily = "Cambria",
                FontSize = 12,
                Text = "Type...",
                Width = 150
            };

            var secondTextBox = new TextBox
            {
                ForegroundColor = Color.Red,
                Margin = 5,
                FontFamily = "Cambria",
                FontSize = 12,
                Text = "Type...",
                Width = 150
            };

            var thirdTextBox = new TextBox
            {
                ForegroundColor = Color.Red,
                Margin = 5,
                FontFamily = "Cambria",
                FontSize = 12,
                Text = "Type...",
                Width = 150
            };

            var panel = new StackPanel
            {
                Orientation = Orientation.Vertical,
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center
            };

            panel.AddChild(firstTextBox);
            panel.AddChild(secondTextBox);
            panel.AddChild(thirdTextBox);

            Chart.AddControl(panel);
        }

        public override void Calculate(int index)
        {
        }
    }
}

Here is how using the Style class can simplify working with multiple controls.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using cAlgo.API;

namespace ChartControlsTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.None)]
    public class ChartControls : Indicator
    {
        protected override void Initialize()
        {
            var textBoxStyle = new Style();

            textBoxStyle.Set(ControlProperty.ForegroundColor, Color.Red);
            textBoxStyle.Set(ControlProperty.Margin, 5);
            textBoxStyle.Set(ControlProperty.FontFamily, "Cambria");
            textBoxStyle.Set(ControlProperty.FontSize, 12);
            textBoxStyle.Set(ControlProperty.Width, 150);

            var firstTextBox = new TextBox
            {
                Text = "Type...",
                Style = textBoxStyle
            };

            var secondTextBox = new TextBox
            {
                Text = "Type...",
                Style = textBoxStyle
            };

            var thirdTextBox = new TextBox
            {
                Text = "Type...",
                Style = textBoxStyle
            };

            var panel = new StackPanel
            {
                Orientation = Orientation.Vertical,
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center
            };

            panel.AddChild(firstTextBox);
            panel.AddChild(secondTextBox);
            panel.AddChild(thirdTextBox);

            Chart.AddControl(panel);
        }

        public override void Calculate(int index)
        {
        }
    }
}

Both code snippets above should display the same controls if you launch their respective indicator instances.

You can also using the Style class to change the appearance of a control based on its state.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using cAlgo.API;

namespace ChartControlsTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.None)]
    public class ChartControls : Indicator
    {
        protected override void Initialize()
        {
            var textBoxStyle = new Style();

            textBoxStyle.Set(ControlProperty.ForegroundColor, Color.Red);
            textBoxStyle.Set(ControlProperty.Margin, 5);
            textBoxStyle.Set(ControlProperty.FontFamily, "Cambria");
            textBoxStyle.Set(ControlProperty.FontSize, 12);
            textBoxStyle.Set(ControlProperty.Width, 150);
            // Here we change the foreground color to Yellow if mouse hover over the textbox
            textBoxStyle.Set(ControlProperty.ForegroundColor, Color.Yellow, ControlState.Hover);

            var firstTextBox = new TextBox
            {
                Text = "Type...",
                Style = textBoxStyle
            };

            var secondTextBox = new TextBox
            {
                Text = "Type...",
                Style = textBoxStyle
            };

            var thirdTextBox = new TextBox
            {
                Text = "Type...",
                Style = textBoxStyle
            };

            var panel = new StackPanel
            {
                Orientation = Orientation.Vertical,
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center
            };

            panel.AddChild(firstTextBox);
            panel.AddChild(secondTextBox);
            panel.AddChild(thirdTextBox);

            Chart.AddControl(panel);
        }

        public override void Calculate(int index)
        {
        }
    }
}

Image

The Image control can be used to display a locally stored image. The Image control uses the .NET Bitmap class, which means that it supports the majority of popular image formats such as the following.

  • .PNG
  • .JPG
  • .BMP
  • .GIF
  • .TIFF

Consult the .NET Bitmap class documentation to learn more about it.

To use an Image control, set its Source property to an image file data in a byte array (byte[]).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using cAlgo.API;
using System.IO;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
    public class ImageSample : Indicator
    {
        [Parameter("Image File Path")]
        public string ImageFilePath { get; set; }

        protected override void Initialize()
        {
            if (File.Exists(ImageFilePath) is false)
            {
                Print($"Image not found: {ImageFilePath}");

                return;
            }

            var imageBytes = File.ReadAllBytes(ImageFilePath);

            var image = new Image
            {
                Source = imageBytes,
                Width = 200,
                Height = 200,
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center
            };

            Chart.AddControl(image);
        }

        public override void Calculate(int index)
        {
        }
    }
}

After launching an instance of the above indicator, you should see nothing new on the main chart. However, input a valid file path as a value of the 'Image File Path' parameter, and you should see the chosen image displayed in the centre of the screen.

You can also use your project resources for storing images and displaying them via the Image control.

To do so, open the project resources in Visual Studio and switch to the 'Resources' tab. In it, create a new resource and add an existing image.

Afterward, you will be able to this image as a source in any Image control via the Project_Name_Space.Properties.Resources._Image_Name property.

For an illustration, save the below image as image.png on your system.

As an example copy the below image and save it as "image.png" on your system:

Image title

Create a new indicator in cTrader, set its name to 'Image Sample', and open it via Visual Studio. Afterward, add the logo indicator project as a resource. To do so, right-click on your project, select 'Properties' and click on 'Resources', and, subsequently, 'Create and manage assembly resources'. Copy the logo.png file into the newly opened tab.

Copy the below code to your indicator main source code file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using cAlgo.API;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class ImageSample : Indicator
    {
        protected override void Initialize()
        {
            var image = new Image
            {
                Source = Image_Sample.Properties.Resources.logo,
                Width = 200,
                Height = 200,
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center
            };

            Chart.AddControl(image);
        }

        public override void Calculate(int index)
        {
        }
    }
}

After creating an instance of this indicator, you should see the following output.

Image title

Note

The maximum size for images is 10 MB.