Zum Inhalt

WinForms

Da cTrader-Indikatoren und cBots .NET-Anwendungen darstellen, können Sie beim Erstellen alle .NET-Technologien verwenden, einschließlich UI-bezogener Lösungen wie WinForms und WPF. cTrader unterstützt alle von WinForms und WPF angebotenen Funktionen, einschließlich Dialoge, Formulare, den Visual Studio WinForms-Designer, UI-Steuerelemente und -Komponenten von Drittanbietern.

Hinweis

Algorithmen, die WinForms oder WPF verwenden, können nur auf Windows-Rechnern ausgeführt werden.

Um WinForms zu verwenden, ändern Sie Ihren cTrader-Compiler auf den .NET SDK-Compiler.

Wir zeigen, wie WinForms verwendet werden können, indem wir einen benutzerdefinierten WinForms-Dialog erstellen. Diese sind nützlich, um Warnungen anzuzeigen oder Benutzereingaben anzufordern.

Ihr Projekt konfigurieren

Bevor Sie WinForms in Ihren cBots und Indikatoren verwenden können, müssen Sie mehrere Änderungen in Ihrer bzw. Ihren cBot- oder Indikator-Projektdatei(en) vornehmen. Da WinForms nur unter Windows funktioniert, müssen Sie das Ziel Ihres Indikators oder cBots auf die Windows-Variante von .NET ändern.

Öffnen Sie dazu Ihre Projektdatei in Visual Studio und ersetzen Sie deren Inhalt durch Folgendes:

 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>

Wir haben das Tag UseWindowsForms hinzugefügt und den Wert von TargetFramework in net6.0-windows geändert.

Ändern Sie anschließend den Wert des Klassenparameters AccessRights in FullAccess. Sofern diese Änderung nicht vorgenommen wird, verfügt Ihre Erweiterung nicht über ausreichende Zugriffsrechte, um ein WinForms-Fenster zu öffnen.

Erstellen Sie Ihr Projekt nach den oben genannten Änderungen neu.

Dialoge verwenden

Glücklicherweise enthält WinForms mehrere gebrauchsfertige Dialoge, die für eine Vielzahl von Fällen nützlich sind, insbesondere zum Anzeigen einer Fehlermeldung, zum Einholen einer Bestätigung vom Benutzer oder zum Auswählen einer Datei oder eines Ordners.

Das folgende Beispiel verwendet die Klasse MessageBox und die Dialoge zum Öffnen und Speichern von Dateien.

MessageBox

Sie können die Klasse System.Windows.Forms.MessageBox auf die gleiche Weise verwenden, wie sie in jeder anderen .NET-Anwendung verwendet wird.

Der folgende Code fügt einem Indikator ein Standard-Bestätigungsfeld hinzu:

 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)
        {
        }
    }
}

Nachdem Sie eine Instanz dieses Indikators ausgeführt haben, sollten Sie sofort ein Bestätigungsfeld mit dem oben angegebenen Text sehen.

Dialoge zum Öffnen oder Speichern von Dateien

Der Dialog zum Öffnen oder Speichern von Dateien kann verwendet werden, um cBots und Indikatoren das Durchsuchen und Auswählen lokal gespeicherter Dateien zu ermöglichen.

 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)
        {
        }
    }
}

In den Beispielen haben wir einen separaten Thread verwendet, um den Code im Zusammenhang mit unseren Dialogen auszuführen. Dies wurde getan, um der Tatsache Rechnung zu tragen, dass der Haupt-cBot- oder Indikator-Thread kein STA-Thread ist. Wir gehen im folgenden Abschnitt näher auf dieses Problem ein.

Verwenden Sie einen dedizierten Thread für die Benutzeroberfläche

Wenn Sie Code ausführen, der auf WinForms verweist, müssen Sie einen STA-markierten Thread verwenden, da es andernfalls während der Ausführung zu Fehlern kommen kann. Weitere Informationen finden Sie in der offiziellen Dokumentation.

Sie können die ApartmentState-Eigenschaft eines bereits laufenden Threads nicht ändern. Da der Haupt-cBot- oder Indikator-Thread nicht STA-markiert ist, müssen Sie einen neuen STA-markierten Thread für WinForms und WPF verwenden.

Der Code im vorherigen Abschnitt ruft auch die Methode thread.Join() auf. Sie blockiert die Ausführung des Haupt-cBot- oder Indikator-Threads, bis der UI-bezogene Thread freigegeben wird (z. B. wenn ein Benutzer in einem Bestätigungsdialog auf OK klickt).

Wann immer Sie ein Windows Form oder ein WPF-Element anzeigen möchten, müssen Sie Ihren Code in einem separaten STA-markierten Thread ausführen.

Erstellen Sie ein neues benutzerdefiniertes Formular

Bisher haben wir integrierte WinForms-Klassen verwendet. Es ist jedoch auch möglich, benutzerdefinierte Klassen zu erstellen. In diesem Abschnitt erstellen wir ein Formular, das Informationen über das aktuell aktive Handelskonto anzeigt, wie z. B. dessen Saldo und Margin.

Um ein neues Formular zu erstellen, klicken Sie mit der rechten Maustaste auf Ihren cBot oder Indikator in Visual Studio, klicken Sie auf Hinzufügen und wählen Sie Benutzersteuerelement (Windows Forms) ...

Image title

Visual Studio öffnet ein Dialogfenster, in dem Sie den Typ des Elements auswählen können, das Sie Ihrem Projekt hinzufügen möchten. Wählen Sie Formular (Windows Forms) (Visual Studio sollte dies automatisch auswählen).

Image title

Ändern Sie anschließend den Formularnamen von Form1.cs in AccountInfoForm.cs. Klicken Sie auf die Schaltfläche Hinzufügen.

Ein neues Formular wird in Ihrem Projekt erstellt; die IDE zeigt den Visual Studio WinForms-Designer an. Dadurch können Sie alle seine Funktionen nutzen, einschließlich des Hinzufügens von Steuerelementen über eine Toolbox.

Image title

In diesem Beispiel haben wir bereits ein Formular erstellt. Kopieren Sie den folgenden Code und fügen Sie ihn in die Datei AccountIntoForm.Designer.cs ein:

  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;
    }
}

Der obige Code wird vom WinForms-Designer generiert. Wir werden nun den Backend-Code für das neue Formular schreiben. Klicken Sie dazu mit der rechten Maustaste auf das Formular und wählen Sie Code anzeigen.

Image title

Kopieren Sie das folgende Beispiel und fügen Sie es ein:

 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();
    }
}

Bemerkenswerterweise verwendet unser Code die Methoden BeginInvokeOnMainThread() und BeginInvoke(). Wir erklären unsere Begründung dafür im folgenden Abschnitt.

Vorerst werden wir unser benutzerdefiniertes Formular verwenden, indem wir seine Methode ShowDialog() von unserem Indikator aus aufrufen. Kopieren Sie den folgenden Code und fügen Sie ihn in Ihren Indikator-Quellcode ein:

 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)
        {
        }
    }
}

Wenn Sie ein neues Formular hinzufügen, verwendet es automatisch Ihren Indikator- oder cBot-Namespace (cAlgo). Um Fehler zu vermeiden, ändern Sie den Namespace Ihres hinzugefügten Formulars und fügen Sie es dann Ihrer Indikator- oder cBot-Quellcodedatei hinzu. In unserem Fall ist WinForms_Test die Namespace-Bezeichnung.

Nach dem Erstellen des Indikators und dem Erstellen einer Instanz sollte das folgende Formular erscheinen.

Image title

Zugriff auf API-Mitglieder von einem UI-dedizierten Thread

Wie wir bereits erklärt haben, müssen Sie einen separaten dedizierten Thread verwenden, um Ihre WinForms auszuführen. Nur der Code, der auf diesem Thread ausgeführt wird, kann auf die Formularsteuerelemente und -eigenschaften zugreifen.

Dasselbe gilt für alle Algo-API-Mitglieder. Da die API nicht threadsicher ist, können Sie nicht von dem Form-Thread aus auf API-Mitglieder zugreifen. Stattdessen müssen Sie diese Aufgabe delegieren, indem Sie die Methode BeginInvokeOnMainThread() in Ihrem Indikator- oder cBot-Code verwenden.

Dies kann zu Komplikationen führen, wenn Sie Daten zwischen Ihrem Indikator oder cBot und einem WinForm austauschen möchten. Um dieses Problem zu lösen, können Sie eine Proxy-Klasse erstellen, um die Interaktionen zwischen Ihrem cBot- oder Indikator- und Form-Thread zu verwalten.