Поскольку индикаторы и сиБоты cTrader представляют собой приложения .NET, вы можете использовать все технологии .NET при их создании, включая решения, связанные с пользовательским интерфейсом, такие как WinForms и WPF. cTrader поддерживает все функции, предлагаемые WinForms и WPF, включая диалоговые окна, формы, дизайнер WinForms в Visual Studio, сторонние элементы управления и компоненты пользовательского интерфейса.
Примечание
Алгоритмы, использующие WinForms или WPF, могут запускаться только на компьютерах с Windows.
Чтобы использовать WinForms, измените ваш компилятор cTrader на компилятор .NET SDK.
Мы продемонстрируем, как можно использовать WinForms, создав пользовательское диалоговое окно WinForms. Они полезны для отображения предупреждений или запроса пользовательского ввода.
Настройка вашего проекта
Прежде чем вы сможете использовать WinForms в ваших сиБотах и индикаторах, вам придется внести несколько изменений в файл(ы) проекта вашего сиБота или индикатора. Поскольку WinForms работает только в Windows, вам придется изменить целевую платформу вашего индикатора или сиБота на вариант .NET для Windows.
Для этого откройте файл проекта в Visual Studio и замените его содержимое следующим:
Мы добавили тег UseWindowsForms и изменили значение TargetFramework на net6.0-windows.
Затем измените значение параметра класса AccessRights на FullAccess. Если это изменение не будет внесено, ваше расширение не будет иметь достаточных прав доступа для открытия окна WinForms.
Пересоберите ваш проект после внесения вышеуказанных изменений.
Использование диалоговых окон
К счастью, WinForms содержит несколько готовых к использованию диалоговых окон, которые полезны для широкого спектра случаев, в частности для отображения сообщений об ошибках, получения подтверждения от пользователя или выбора файла или папки.
В приведенном ниже примере используется класс MessageBox и диалоговые окна открытия и сохранения файлов.
MessageBox
Вы можете использовать класс System.Windows.Forms.MessageBox так же, как он используется в любом другом .NET-приложении.
Приведенный ниже код прикрепляет стандартное окно подтверждения к индикатору:
usingcAlgo.API;usingSystem.Windows.Forms;namespaceWinFormsTest{[Indicator(IsOverlay = true, AccessRights = AccessRights.FullAccess)]publicclassWinFormsTest:Indicator{protectedoverridevoidInitialize(){varresult=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");}}publicoverridevoidCalculate(intindex){}}}
После запуска экземпляра этого индикатора вы должны сразу увидеть окно подтверждения с текстом, который вы указали выше.
Диалоговые окна открытия или сохранения файлов
Диалоговое окно открытия или сохранения файла можно использовать, чтобы позволить сиБотам и индикаторам просматривать и выбирать локально сохраненные файлы.
В примерах мы использовали отдельный поток для выполнения кода, связанного с нашими диалоговыми окнами. Это было сделано с учетом того факта, что основной поток сиБота или индикатора не является STA-потоком. Мы более подробно рассмотрим эту проблему в следующем разделе.
Использование выделенного потока для UI
При выполнении кода, ссылающегося на WinForms, вы должны использовать поток с пометкой STA, иначе во время выполнения могут возникнуть ошибки. Для получения дополнительной информации обратитесь к официальной документации.
Вы не можете изменить свойство ApartmentState уже запущенного потока. Поскольку основной поток сиБота или индикатора не имеет пометки STA, вы должны использовать новый поток с пометкой STA для WinForms и WPF.
Код в предыдущем разделе также вызывает метод thread.Join(). Он блокирует выполнение основного потока сиБота или индикатора до тех пор, пока не будет освобожден поток, связанный с UI (например, когда пользователь нажмет OK в диалоговом окне подтверждения).
Всякий раз, когда вы хотите отобразить Windows Form или элемент WPF, вы должны запускать свой код в отдельном потоке с пометкой STA.
Создание новой пользовательской формы
До сих пор мы использовали встроенные классы WinForms. Однако также возможно создавать пользовательские классы. В этом разделе мы создадим форму, отображающую информацию о текущем активном торговом счете, такую как его баланс и маржа.
Чтобы создать новую форму, щелкните правой кнопкой мыши по вашему сиБоту или индикатору в Visual Studio, нажмите Add и выберите User Control (Windows Forms) ...'
Visual Studio откроет диалоговое окно, в котором вы сможете выбрать тип элемента, который хотите добавить в свой проект. Выберите Form (Windows Forms) (Visual Studio должна выбрать его автоматически).
Затем измените имя формы с Form1.cs на AccountInfoForm.cs. Нажмите кнопку Add.
Новая форма будет создана внутри вашего проекта; IDE отобразит дизайнер WinForms Visual Studio. В результате вы сможете использовать все его функции, включая добавление элементов управления через панель инструментов.
В этом примере мы уже создали форму. Скопируйте и вставьте приведенный ниже код в файл AccountIntoForm.Designer.cs:
namespaceWinForms_Test{partialclassAccountInfoForm{/// <summary>/// Required designer variable./// </summary>privateSystem.ComponentModel.IContainercomponents=null;/// <summary>/// Clean up any resources being used./// </summary>/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>protectedoverridevoidDispose(booldisposing){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>privatevoidInitializeComponent(){this.label1=newSystem.Windows.Forms.Label();this.NumberLabel=newSystem.Windows.Forms.Label();this.BrokerLabel=newSystem.Windows.Forms.Label();this.label3=newSystem.Windows.Forms.Label();this.CurrencyLabel=newSystem.Windows.Forms.Label();this.label5=newSystem.Windows.Forms.Label();this.BalanceLabel=newSystem.Windows.Forms.Label();this.label7=newSystem.Windows.Forms.Label();this.EquityLabel=newSystem.Windows.Forms.Label();this.label9=newSystem.Windows.Forms.Label();this.PositionsNumberLabel=newSystem.Windows.Forms.Label();this.label11=newSystem.Windows.Forms.Label();this.OrdersNumberLabel=newSystem.Windows.Forms.Label();this.label13=newSystem.Windows.Forms.Label();this.NetProfitLabel=newSystem.Windows.Forms.Label();this.label15=newSystem.Windows.Forms.Label();this.UpdateButton=newSystem.Windows.Forms.Button();this.SuspendLayout();// // label1// this.label1.AutoSize=true;this.label1.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.label1.Location=newSystem.Drawing.Point(12,33);this.label1.Name="label1";this.label1.Size=newSystem.Drawing.Size(72,20);this.label1.TabIndex=0;this.label1.Text="Number:";// // NumberLabel// this.NumberLabel.AutoSize=true;this.NumberLabel.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.NumberLabel.Location=newSystem.Drawing.Point(188,33);this.NumberLabel.Name="NumberLabel";this.NumberLabel.Size=newSystem.Drawing.Size(0,20);this.NumberLabel.TabIndex=1;// // BrokerLabel// this.BrokerLabel.AutoSize=true;this.BrokerLabel.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.BrokerLabel.Location=newSystem.Drawing.Point(188,67);this.BrokerLabel.Name="BrokerLabel";this.BrokerLabel.Size=newSystem.Drawing.Size(0,20);this.BrokerLabel.TabIndex=3;// // label3// this.label3.AutoSize=true;this.label3.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.label3.Location=newSystem.Drawing.Point(12,67);this.label3.Name="label3";this.label3.Size=newSystem.Drawing.Size(56,20);this.label3.TabIndex=2;this.label3.Text="Broker";// // CurrencyLabel// this.CurrencyLabel.AutoSize=true;this.CurrencyLabel.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.CurrencyLabel.Location=newSystem.Drawing.Point(188,101);this.CurrencyLabel.Name="CurrencyLabel";this.CurrencyLabel.Size=newSystem.Drawing.Size(0,20);this.CurrencyLabel.TabIndex=5;// // label5// this.label5.AutoSize=true;this.label5.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.label5.Location=newSystem.Drawing.Point(12,101);this.label5.Name="label5";this.label5.Size=newSystem.Drawing.Size(75,20);this.label5.TabIndex=4;this.label5.Text="Currency";// // BalanceLabel// this.BalanceLabel.AutoSize=true;this.BalanceLabel.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.BalanceLabel.Location=newSystem.Drawing.Point(188,135);this.BalanceLabel.Name="BalanceLabel";this.BalanceLabel.Size=newSystem.Drawing.Size(0,20);this.BalanceLabel.TabIndex=7;// // label7// this.label7.AutoSize=true;this.label7.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.label7.Location=newSystem.Drawing.Point(12,135);this.label7.Name="label7";this.label7.Size=newSystem.Drawing.Size(63,20);this.label7.TabIndex=6;this.label7.Text="Balance";// // EquityLabel// this.EquityLabel.AutoSize=true;this.EquityLabel.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.EquityLabel.Location=newSystem.Drawing.Point(188,173);this.EquityLabel.Name="EquityLabel";this.EquityLabel.Size=newSystem.Drawing.Size(0,20);this.EquityLabel.TabIndex=9;// // label9// this.label9.AutoSize=true;this.label9.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.label9.Location=newSystem.Drawing.Point(12,173);this.label9.Name="label9";this.label9.Size=newSystem.Drawing.Size(54,20);this.label9.TabIndex=8;this.label9.Text="Equity";// // PositionsNumberLabel// this.PositionsNumberLabel.AutoSize=true;this.PositionsNumberLabel.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.PositionsNumberLabel.Location=newSystem.Drawing.Point(188,210);this.PositionsNumberLabel.Name="PositionsNumberLabel";this.PositionsNumberLabel.Size=newSystem.Drawing.Size(0,20);this.PositionsNumberLabel.TabIndex=11;// // label11// this.label11.AutoSize=true;this.label11.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.label11.Location=newSystem.Drawing.Point(12,210);this.label11.Name="label11";this.label11.Size=newSystem.Drawing.Size(85,20);this.label11.TabIndex=10;this.label11.Text="Positions #";// // OrdersNumberLabel// this.OrdersNumberLabel.AutoSize=true;this.OrdersNumberLabel.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.OrdersNumberLabel.Location=newSystem.Drawing.Point(188,250);this.OrdersNumberLabel.Name="OrdersNumberLabel";this.OrdersNumberLabel.Size=newSystem.Drawing.Size(0,20);this.OrdersNumberLabel.TabIndex=13;// // label13// this.label13.AutoSize=true;this.label13.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.label13.Location=newSystem.Drawing.Point(12,250);this.label13.Name="label13";this.label13.Size=newSystem.Drawing.Size(69,20);this.label13.TabIndex=12;this.label13.Text="Orders #";// // NetProfitLabel// this.NetProfitLabel.AutoSize=true;this.NetProfitLabel.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.NetProfitLabel.Location=newSystem.Drawing.Point(188,285);this.NetProfitLabel.Name="NetProfitLabel";this.NetProfitLabel.Size=newSystem.Drawing.Size(0,20);this.NetProfitLabel.TabIndex=15;// // label15// this.label15.AutoSize=true;this.label15.Font=newSystem.Drawing.Font("Segoe UI Variable Display",11.25F,System.Drawing.FontStyle.Bold,System.Drawing.GraphicsUnit.Point);this.label15.Location=newSystem.Drawing.Point(12,285);this.label15.Name="label15";this.label15.Size=newSystem.Drawing.Size(79,20);this.label15.TabIndex=14;this.label15.Text="Net Profit";// // UpdateButton// this.UpdateButton.Location=newSystem.Drawing.Point(273,340);this.UpdateButton.Name="UpdateButton";this.UpdateButton.Size=newSystem.Drawing.Size(75,23);this.UpdateButton.TabIndex=16;this.UpdateButton.Text="Update";this.UpdateButton.UseVisualStyleBackColor=true;this.UpdateButton.Click+=newSystem.EventHandler(this.UpdateButton_Click);// // AccountInfoForm// this.AutoScaleDimensions=newSystem.Drawing.SizeF(7F,15F);this.AutoScaleMode=System.Windows.Forms.AutoScaleMode.Font;this.ClientSize=newSystem.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+=newSystem.EventHandler(this.AccountInfoForm_Load);this.ResumeLayout(false);this.PerformLayout();}#endregionprivateSystem.Windows.Forms.Labellabel1;privateSystem.Windows.Forms.LabelNumberLabel;privateSystem.Windows.Forms.LabelBrokerLabel;privateSystem.Windows.Forms.Labellabel3;privateSystem.Windows.Forms.LabelCurrencyLabel;privateSystem.Windows.Forms.Labellabel5;privateSystem.Windows.Forms.LabelBalanceLabel;privateSystem.Windows.Forms.Labellabel7;privateSystem.Windows.Forms.LabelEquityLabel;privateSystem.Windows.Forms.Labellabel9;privateSystem.Windows.Forms.LabelPositionsNumberLabel;privateSystem.Windows.Forms.Labellabel11;privateSystem.Windows.Forms.LabelOrdersNumberLabel;privateSystem.Windows.Forms.Labellabel13;privateSystem.Windows.Forms.LabelNetProfitLabel;privateSystem.Windows.Forms.Labellabel15;privateSystem.Windows.Forms.ButtonUpdateButton;}}
Приведенный выше код генерируется дизайнером WinForms. Теперь мы напишем серверный код для новой формы. Для этого щелкните правой кнопкой мыши по форме и выберите View Code.
Примечательно, что наш код использует методы BeginInvokeOnMainThread() и BeginInvoke(). Мы объясним наше обоснование этого в следующем разделе.
Пока что мы будем использовать нашу пользовательскую форму, вызывая ее метод ShowDialog() из нашего индикатора. Скопируйте и вставьте приведенный ниже код в исходный код вашего индикатора:
Когда вы добавляете новую форму, она автоматически использует пространство имен вашего индикатора или сиБота (cAlgo). Чтобы избежать ошибок, измените пространство имен добавленной формы, а затем добавьте его в файл исходного кода вашего индикатора или сиБота. В нашем случае WinForms_Test — это обозначение пространства имен.
После сборки индикатора и создания экземпляра вы должны увидеть следующую форму.
Доступ к членам API из выделенного потока UI
Как мы объяснили ранее, вы должны использовать отдельный выделенный поток для запуска ваших WinForms. Только код, выполняющийся в этом потоке, сможет получить доступ к элементам управления и свойствам формы.
То же самое верно для всех членов API алгоритмов. Поскольку API не является потокобезопасным, вы не можете получить доступ к членам API из потока Form. Вместо этого вы должны делегировать эту задачу, используя метод BeginInvokeOnMainThread() в коде вашего индикатора или сиБота.
Это может создать сложности, если вы хотите обмениваться данными между вашим индикатором или сиБотом и WinForm. Чтобы решить эту проблему, вы можете создать прокси-класс для управления взаимодействием между потоками вашего сиБота или индикатора и Form.