Vai al contenuto

WinForms

Poiché gli indicatori e i cBot di cTrader costituiscono applicazioni .NET, puoi utilizzare tutte le tecnologie .NET durante la loro creazione, incluse soluzioni relative all'interfaccia utente come WinForms e WPF. cTrader supporta tutte le funzionalità offerte da WinForms e WPF, incluse finestre di dialogo, moduli, il designer WinForms di Visual Studio, controlli e componenti UI di terze parti.

Nota

Gli algoritmi che utilizzano WinForms o WPF possono essere eseguiti solo su macchine Windows.

Per utilizzare WinForms, modifica il tuo compilatore cTrader nel compilatore .NET SDK.

Mostreremo come WinForms può essere utilizzato creando una finestra di dialogo WinForms personalizzata. Sono utili per mostrare avvisi o richiedere input dall'utente.

Configurazione del progetto

Prima di poter utilizzare WinForms nei tuoi cBot e indicatori, dovrai apportare diverse modifiche nei file di progetto del tuo cBot o indicatore. Poiché WinForms funziona solo su Windows, dovrai modificare la destinazione del framework del tuo indicatore o cBot del tuo progetto nella variante Windows di .NET.

Per farlo, apri il file di progetto in Visual Studio e sostituisci il suo contenuto con quanto segue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="cTrader.Automate" Version="1.*" />
  </ItemGroup>
</Project>

Abbiamo aggiunto il tag UseWindowsForms e modificato il valore di TargetFramework in net6.0-windows.

Successivamente, modifica il valore del parametro della classe AccessRights in FullAccess. A meno che questa modifica non venga apportata, la tua estensione non avrà diritti di accesso sufficienti per aprire una finestra WinForms.

Ricompila il tuo progetto dopo aver apportato le modifiche sopra indicate.

Fortunatamente, WinForms contiene diverse finestre di dialogo pronte all'uso che sono utili per un'ampia varietà di casi, in particolare per mostrare un messaggio di errore, ottenere una conferma dall'utente o selezionare un file o una cartella.

L'esempio seguente utilizza la classe MessageBox e le finestre di dialogo per aprire e salvare file.

MessageBox

Puoi utilizzare la classe System.Windows.Forms.MessageBox nello stesso modo in cui viene utilizzata in qualsiasi altra applicazione .NET.

Il codice seguente collega una casella di conferma standard a un indicatore:

 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
using cAlgo.API;
using System.Windows.Forms;

namespace WinFormsTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.FullAccess)]
    public class WinFormsTest : Indicator
    {
        protected override void Initialize()
        {
            var result = System.Windows.Forms.MessageBox.Show("Are you sure you want confirm?", "Confirmation", MessageBoxButtons.OKCancel, MessageBoxIcon.Information);

            if (result == DialogResult.OK)
            {
                Print("OK");
            }
            else
            {
                Print("Cancel");
            }
        }

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

Dopo aver eseguito un'istanza di questo indicatore, dovresti vedere immediatamente una casella di conferma con il testo che hai specificato sopra.

Finestre di dialogo per aprire o salvare file

La finestra di dialogo per aprire o salvare file può essere utilizzata per consentire a cBot e indicatori di sfogliare e selezionare file archiviati localmente.

 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
using cAlgo.API;
using System.Windows.Forms;
using System;
using System.Threading;

namespace WinFormsTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.FullAccess)]
    public class WinFormsTest : Indicator
    {
        protected override void Initialize()
        {
            var thread = new Thread(() =>
            {
                var dialog = new OpenFileDialog
                {
                    CheckFileExists = true,
                    CheckPathExists = true,
                    Filter = "Text Files (*.txt)|*.txt",
                    InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
                    Multiselect = true,
                    RestoreDirectory = true,
                };

                var result = dialog.ShowDialog();

                if (result == DialogResult.OK)
                {
                    foreach (var fileName in dialog.FileNames)
                    {
                        Print(fileName);
                    }
                }
                else
                {
                    Print("Dialog Canceled");
                }
            });

            thread.SetApartmentState(ApartmentState.STA);

            thread.Start();

            thread.Join();
        }

        public override void Calculate(int index)
        {
        }
    }
}
 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
using cAlgo.API;
using System.Windows.Forms;
using System;
using System.Threading;

namespace WinFormsTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.FullAccess)]
    public class WinFormsTest : Indicator
    {
        protected override void Initialize()
        {
            var thread = new Thread(() =>
            {
                var dialog = new SaveFileDialog
                {
                    CheckFileExists = true,
                    CheckPathExists = true,
                    Filter = "Text Files (*.txt)|*.txt",
                    InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
                    RestoreDirectory = true,
                };

                var result = dialog.ShowDialog();

                if (result == DialogResult.OK)
                {
                    Print(dialog.FileName);
                }
                else
                {
                    Print("Dialog Canceled");
                }
            });

            thread.SetApartmentState(ApartmentState.STA);

            thread.Start();

            thread.Join();
        }

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

Negli esempi, abbiamo utilizzato un thread separato per eseguire il codice relativo alle nostre finestre di dialogo. Questo è stato fatto per tenere conto del fatto che il thread principale del cBot o dell'indicatore non è un thread STA. Approfondiamo questo problema nella sezione seguente.

Utilizzo di un thread dedicato per l'interfaccia utente

Quando esegui codice che fa riferimento a WinForms, devi utilizzare un thread contrassegnato STA, altrimenti potrebbero verificarsi errori durante l'esecuzione. Per ulteriori informazioni, consulta la documentazione ufficiale.

Non puoi modificare la proprietà ApartmentState di un thread già in esecuzione. Poiché il thread principale del cBot o dell'indicatore non è contrassegnato come STA, devi utilizzare un nuovo thread contrassegnato come STA per WinForms e WPF.

Il codice nella sezione precedente chiama anche il metodo thread.Join(). Questo blocca l'esecuzione del thread principale del cBot o dell'indicatore fino al rilascio del thread relativo all'interfaccia utente (ad esempio, quando un utente fa clic su OK in una finestra di dialogo di conferma).

Ogni volta che desideri visualizzare un Windows Form o un elemento WPF, devi eseguire il tuo codice in un thread separato contrassegnato come STA.

Crea un nuovo modulo personalizzato

Finora abbiamo utilizzato classi WinForms integrate. Tuttavia, è anche possibile creare classi personalizzate. In questa sezione creeremo un modulo che mostra informazioni sul conto di trading attualmente attivo, come il saldo e il margine.

Per creare un nuovo modulo, fai clic con il pulsante destro del mouse sul tuo cBot o indicatore in Visual Studio, fai clic su Aggiungi e seleziona Controllo utente (Windows Forms)...'

Image title

Visual Studio aprirà una finestra di dialogo in cui puoi selezionare il tipo di elemento che desideri aggiungere al tuo progetto. Seleziona Modulo (Windows Forms) (Visual Studio dovrebbe selezionarlo automaticamente).

Image title

Successivamente, modifica il nome del modulo da Form1.cs a AccountInfoForm.cs. Fai clic sul pulsante Aggiungi.

Un nuovo modulo verrà creato all'interno del tuo progetto; l'IDE visualizzerà il designer WinForms di Visual Studio. Di conseguenza, potrai utilizzare tutte le sue funzionalità, inclusa l'aggiunta di controlli tramite una casella degli strumenti.

Image title

In questo esempio abbiamo già creato un modulo. Copia e incolla il codice seguente nel file AccountIntoForm.Designer.cs:

  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
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
namespace WinForms_Test
{
    partial class AccountInfoForm
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.label1 = new System.Windows.Forms.Label();
            this.NumberLabel = new System.Windows.Forms.Label();
            this.BrokerLabel = new System.Windows.Forms.Label();
            this.label3 = new System.Windows.Forms.Label();
            this.CurrencyLabel = new System.Windows.Forms.Label();
            this.label5 = new System.Windows.Forms.Label();
            this.BalanceLabel = new System.Windows.Forms.Label();
            this.label7 = new System.Windows.Forms.Label();
            this.EquityLabel = new System.Windows.Forms.Label();
            this.label9 = new System.Windows.Forms.Label();
            this.PositionsNumberLabel = new System.Windows.Forms.Label();
            this.label11 = new System.Windows.Forms.Label();
            this.OrdersNumberLabel = new System.Windows.Forms.Label();
            this.label13 = new System.Windows.Forms.Label();
            this.NetProfitLabel = new System.Windows.Forms.Label();
            this.label15 = new System.Windows.Forms.Label();
            this.UpdateButton = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.label1.Location = new System.Drawing.Point(12, 33);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(72, 20);
            this.label1.TabIndex = 0;
            this.label1.Text = "Number:";
            // 
            // NumberLabel
            // 
            this.NumberLabel.AutoSize = true;
            this.NumberLabel.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.NumberLabel.Location = new System.Drawing.Point(188, 33);
            this.NumberLabel.Name = "NumberLabel";
            this.NumberLabel.Size = new System.Drawing.Size(0, 20);
            this.NumberLabel.TabIndex = 1;
            // 
            // BrokerLabel
            // 
            this.BrokerLabel.AutoSize = true;
            this.BrokerLabel.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.BrokerLabel.Location = new System.Drawing.Point(188, 67);
            this.BrokerLabel.Name = "BrokerLabel";
            this.BrokerLabel.Size = new System.Drawing.Size(0, 20);
            this.BrokerLabel.TabIndex = 3;
            // 
            // label3
            // 
            this.label3.AutoSize = true;
            this.label3.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.label3.Location = new System.Drawing.Point(12, 67);
            this.label3.Name = "label3";
            this.label3.Size = new System.Drawing.Size(56, 20);
            this.label3.TabIndex = 2;
            this.label3.Text = "Broker";
            // 
            // CurrencyLabel
            // 
            this.CurrencyLabel.AutoSize = true;
            this.CurrencyLabel.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.CurrencyLabel.Location = new System.Drawing.Point(188, 101);
            this.CurrencyLabel.Name = "CurrencyLabel";
            this.CurrencyLabel.Size = new System.Drawing.Size(0, 20);
            this.CurrencyLabel.TabIndex = 5;
            // 
            // label5
            // 
            this.label5.AutoSize = true;
            this.label5.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.label5.Location = new System.Drawing.Point(12, 101);
            this.label5.Name = "label5";
            this.label5.Size = new System.Drawing.Size(75, 20);
            this.label5.TabIndex = 4;
            this.label5.Text = "Currency";
            // 
            // BalanceLabel
            // 
            this.BalanceLabel.AutoSize = true;
            this.BalanceLabel.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.BalanceLabel.Location = new System.Drawing.Point(188, 135);
            this.BalanceLabel.Name = "BalanceLabel";
            this.BalanceLabel.Size = new System.Drawing.Size(0, 20);
            this.BalanceLabel.TabIndex = 7;
            // 
            // label7
            // 
            this.label7.AutoSize = true;
            this.label7.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.label7.Location = new System.Drawing.Point(12, 135);
            this.label7.Name = "label7";
            this.label7.Size = new System.Drawing.Size(63, 20);
            this.label7.TabIndex = 6;
            this.label7.Text = "Balance";
            // 
            // EquityLabel
            // 
            this.EquityLabel.AutoSize = true;
            this.EquityLabel.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.EquityLabel.Location = new System.Drawing.Point(188, 173);
            this.EquityLabel.Name = "EquityLabel";
            this.EquityLabel.Size = new System.Drawing.Size(0, 20);
            this.EquityLabel.TabIndex = 9;
            // 
            // label9
            // 
            this.label9.AutoSize = true;
            this.label9.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.label9.Location = new System.Drawing.Point(12, 173);
            this.label9.Name = "label9";
            this.label9.Size = new System.Drawing.Size(54, 20);
            this.label9.TabIndex = 8;
            this.label9.Text = "Equity";
            // 
            // PositionsNumberLabel
            // 
            this.PositionsNumberLabel.AutoSize = true;
            this.PositionsNumberLabel.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.PositionsNumberLabel.Location = new System.Drawing.Point(188, 210);
            this.PositionsNumberLabel.Name = "PositionsNumberLabel";
            this.PositionsNumberLabel.Size = new System.Drawing.Size(0, 20);
            this.PositionsNumberLabel.TabIndex = 11;
            // 
            // label11
            // 
            this.label11.AutoSize = true;
            this.label11.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.label11.Location = new System.Drawing.Point(12, 210);
            this.label11.Name = "label11";
            this.label11.Size = new System.Drawing.Size(85, 20);
            this.label11.TabIndex = 10;
            this.label11.Text = "Positions #";
            // 
            // OrdersNumberLabel
            // 
            this.OrdersNumberLabel.AutoSize = true;
            this.OrdersNumberLabel.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.OrdersNumberLabel.Location = new System.Drawing.Point(188, 250);
            this.OrdersNumberLabel.Name = "OrdersNumberLabel";
            this.OrdersNumberLabel.Size = new System.Drawing.Size(0, 20);
            this.OrdersNumberLabel.TabIndex = 13;
            // 
            // label13
            // 
            this.label13.AutoSize = true;
            this.label13.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.label13.Location = new System.Drawing.Point(12, 250);
            this.label13.Name = "label13";
            this.label13.Size = new System.Drawing.Size(69, 20);
            this.label13.TabIndex = 12;
            this.label13.Text = "Orders #";
            // 
            // NetProfitLabel
            // 
            this.NetProfitLabel.AutoSize = true;
            this.NetProfitLabel.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.NetProfitLabel.Location = new System.Drawing.Point(188, 285);
            this.NetProfitLabel.Name = "NetProfitLabel";
            this.NetProfitLabel.Size = new System.Drawing.Size(0, 20);
            this.NetProfitLabel.TabIndex = 15;
            // 
            // label15
            // 
            this.label15.AutoSize = true;
            this.label15.Font = new System.Drawing.Font("Segoe UI Variable Display", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.label15.Location = new System.Drawing.Point(12, 285);
            this.label15.Name = "label15";
            this.label15.Size = new System.Drawing.Size(79, 20);
            this.label15.TabIndex = 14;
            this.label15.Text = "Net Profit";
            // 
            // UpdateButton
            // 
            this.UpdateButton.Location = new System.Drawing.Point(273, 340);
            this.UpdateButton.Name = "UpdateButton";
            this.UpdateButton.Size = new System.Drawing.Size(75, 23);
            this.UpdateButton.TabIndex = 16;
            this.UpdateButton.Text = "Update";
            this.UpdateButton.UseVisualStyleBackColor = true;
            this.UpdateButton.Click += new System.EventHandler(this.UpdateButton_Click);
            // 
            // AccountInfoForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(360, 375);
            this.Controls.Add(this.UpdateButton);
            this.Controls.Add(this.NetProfitLabel);
            this.Controls.Add(this.label15);
            this.Controls.Add(this.OrdersNumberLabel);
            this.Controls.Add(this.label13);
            this.Controls.Add(this.PositionsNumberLabel);
            this.Controls.Add(this.label11);
            this.Controls.Add(this.EquityLabel);
            this.Controls.Add(this.label9);
            this.Controls.Add(this.BalanceLabel);
            this.Controls.Add(this.label7);
            this.Controls.Add(this.CurrencyLabel);
            this.Controls.Add(this.label5);
            this.Controls.Add(this.BrokerLabel);
            this.Controls.Add(this.label3);
            this.Controls.Add(this.NumberLabel);
            this.Controls.Add(this.label1);
            this.Name = "AccountInfoForm";
            this.Text = "Account Info";
            this.Load += new System.EventHandler(this.AccountInfoForm_Load);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label NumberLabel;
        private System.Windows.Forms.Label BrokerLabel;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.Label CurrencyLabel;
        private System.Windows.Forms.Label label5;
        private System.Windows.Forms.Label BalanceLabel;
        private System.Windows.Forms.Label label7;
        private System.Windows.Forms.Label EquityLabel;
        private System.Windows.Forms.Label label9;
        private System.Windows.Forms.Label PositionsNumberLabel;
        private System.Windows.Forms.Label label11;
        private System.Windows.Forms.Label OrdersNumberLabel;
        private System.Windows.Forms.Label label13;
        private System.Windows.Forms.Label NetProfitLabel;
        private System.Windows.Forms.Label label15;
        private System.Windows.Forms.Button UpdateButton;
    }
}

Il codice sopra è generato dal designer WinForms. Ora scriveremo il codice backend per il nuovo modulo. Per farlo, fai clic con il pulsante destro del mouse sul modulo e seleziona Visualizza codice.

Image title

Copia e incolla il seguente esempio:

 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
using System;
using System.Linq;
using System.Windows.Forms;
using cAlgo.API;

namespace WinForms_Test
{
    public partial class AccountInfoForm : Form
    {
        private int _accountNumber;
        private string _brokerName;
        private string _currency;
        private double _balance;
        private double _equity;
        private int _positionsCount;
        private int _ordersCount;
        private double _netProfit;
        private readonly Indicator _indicator;

        public AccountInfoForm(Indicator indicator)
        {
            _indicator = indicator;

            InitializeComponent();
        }

        private void GetDataFromIndicator()
        {
            _indicator.BeginInvokeOnMainThread(() =>
            {
                _accountNumber = _indicator.Account.Number;
                _brokerName = _indicator.Account.BrokerName;
                _currency = _indicator.Account.Asset.Name;
                _balance = _indicator.Account.Balance;
                _equity = _indicator.Account.Equity;
                _positionsCount = _indicator.Positions.Count;
                _ordersCount = _indicator.PendingOrders.Count;
                _netProfit = _indicator.History.Sum(trade => trade.NetProfit);

                UpdateData();
            });
        }

        private void UpdateData()
        {
            _ = BeginInvoke(() =>
            {
                NumberLabel.Text = _accountNumber.ToString();
                BrokerLabel.Text = _brokerName;
                CurrencyLabel.Text = _currency;
                BalanceLabel.Text = Math.Round(_balance, 2).ToString();
                EquityLabel.Text = Math.Round(_equity, 2).ToString();
                PositionsNumberLabel.Text = _positionsCount.ToString();
                OrdersNumberLabel.Text = _ordersCount.ToString();
                NetProfitLabel.Text = Math.Round(_netProfit, 2).ToString();
            });
        }

        private void UpdateButton_Click(object sender, EventArgs e) => GetDataFromIndicator();

        private void AccountInfoForm_Load(object sender, EventArgs e) => GetDataFromIndicator();
    }
}

In particolare, il nostro codice utilizza i metodi BeginInvokeOnMainThread() e BeginInvoke(). Spieghiamo la nostra logica nella sezione seguente.

Per ora, utilizzeremo il nostro modulo personalizzato chiamando il suo metodo ShowDialog() dal nostro indicatore. Copia e incolla il codice seguente nel codice sorgente del tuo indicatore:

 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
using cAlgo.API;
using System.Threading;
using WinForms_Test;

namespace WinFormsTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.FullAccess)]
    public class WinFormsTest : Indicator
    {
        protected override void Initialize()
        {
            var thread = new Thread(() =>
            {
                var form = new AccountInfoForm(this);

                _ = form.ShowDialog();
            });

            thread.SetApartmentState(ApartmentState.STA);

            thread.Start();
        }

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

Quando aggiungi un nuovo modulo, utilizzerà automaticamente il namespace del tuo indicatore o cBot (cAlgo). Per evitare errori, modifica il namespace del modulo aggiunto e poi aggiungilo al file del codice sorgente del tuo indicatore o cBot. Nel nostro caso, WinForms_Test è la designazione del namespace.

Dopo aver compilato l'indicatore e creato un'istanza, dovresti vedere apparire il seguente modulo.

Image title

Accedi ai membri API da un thread dedicato all'interfaccia utente

Come abbiamo spiegato in precedenza, devi utilizzare un thread dedicato separato per eseguire i tuoi WinForms. Solo il codice in esecuzione su questo thread sarà in grado di accedere ai controlli e alle proprietà del modulo.

Lo stesso vale per tutti i membri API degli algoritmi. Poiché l'API non è thread-safe, non puoi accedere ai membri API dal thread Form. Invece, devi delegare questa attività utilizzando il metodo BeginInvokeOnMainThread() nel codice del tuo indicatore o cBot.

Questo può creare complicazioni se desideri scambiare dati tra il tuo indicatore o cBot e un WinForm. Per risolvere questo problema, puoi creare una classe proxy per gestire le interazioni tra i thread del tuo cBot o indicatore e Form.