Como los indicadores y cBots de cTrader constituyen aplicaciones .NET, puede usar todas las tecnologías .NET al crearlos, incluidas soluciones relacionadas con la UI como WinForms y WPF. cTrader admite todas las características ofrecidas por WinForms y WPF, incluidos diálogos, formularios, el diseñador de WinForms de Visual Studio, controles y componentes de UI de terceros.
Nota
Los algoritmos que usan WinForms o WPF solo se pueden ejecutar en máquinas Windows.
Mostraremos cómo se puede usar WinForms creando un diálogo personalizado de WinForms. Son útiles para mostrar alertas o solicitar la entrada del usuario.
Configurar su proyecto
Antes de poder usar WinForms en sus cBots e indicadores, tendrá que hacer varios cambios en los archivos de proyecto de su cBot o indicador. Como WinForms solo funciona en Windows, tendrá que cambiar el objetivo del marco de su indicador o cBot de su proyecto a la variante de Windows de .NET.
Para hacerlo, abra el archivo de su proyecto en Visual Studio y reemplace su contenido con lo siguiente:
Hemos agregado la etiqueta UseWindowsForms y cambiado el valor de TargetFramework a net6.0-windows.
Después, cambie el valor del parámetro de clase AccessRights a FullAccess. A menos que se haga este cambio, su extensión no tendrá suficientes derechos de acceso para abrir una ventana de WinForms.
Reconstruya su proyecto después de hacer los cambios anteriores.
Usar diálogos
Afortunadamente, WinForms contiene varios diálogos listos para usar que son útiles para una amplia variedad de casos, principalmente para mostrar un mensaje de error, obtener una confirmación del usuario o seleccionar un archivo o carpeta.
El siguiente ejemplo utiliza la clase MessageBox y los diálogos de abrir y guardar archivos.
MessageBox
Puede utilizar la clase System.Windows.Forms.MessageBox de la misma manera que se usa en cualquier otra aplicación .NET.
El código a continuación adjunta un cuadro de confirmación estándar a un indicador:
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){}}}
Después de ejecutar una instancia de este indicador, debería ver inmediatamente un cuadro de confirmación con el texto que ha especificado arriba.
Diálogos de abrir o guardar archivos
El diálogo de abrir o guardar archivos se puede utilizar para permitir que los cBots e indicadores naveguen y seleccionen archivos almacenados localmente.
En los ejemplos, hemos utilizado un hilo separado para ejecutar el código relacionado con nuestros diálogos. Esto se hizo para tener en cuenta el hecho de que el hilo principal del cBot o indicador no es un hilo STA. Profundizamos más en este tema en la siguiente sección.
Utilizar un hilo dedicado para la interfaz de usuario
Al ejecutar código que hace referencia a WinForms, debe utilizar un hilo marcado como STA, de lo contrario, pueden producirse errores durante la ejecución. Para obtener más información, consulte la documentación oficial.
No puede cambiar la propiedad ApartmentState de un hilo que ya está en ejecución. Debido a que el hilo principal del cBot o indicador no está marcado como STA, debe utilizar un nuevo hilo marcado como STA para WinForms y WPF.
El código en la sección anterior también llama al método thread.Join(). Bloquea la ejecución del hilo principal del cBot o indicador hasta que se libera el hilo relacionado con la interfaz de usuario (por ejemplo, cuando un usuario hace clic en Aceptar en un diálogo de confirmación).
Siempre que desee mostrar un formulario de Windows o un elemento WPF, debe ejecutar su código en un hilo separado marcado como STA.
Crear un nuevo formulario personalizado
Hasta ahora, hemos utilizado clases incorporadas de WinForms. Sin embargo, también es posible crear clases personalizadas. En esta sección, crearemos un formulario que muestre información sobre la cuenta de operaciones actualmente activa, como su saldo y margen.
Para crear un nuevo formulario, haga clic con el botón derecho en su cBot o indicador mientras está en Visual Studio, haga clic en Agregar y seleccione Control de usuario (Windows Forms) ...
Visual Studio abrirá una ventana de diálogo en la que puede seleccionar el tipo de elemento que desea agregar a su proyecto. Seleccione Formulario (Windows Forms) (Visual Studio debería seleccionarlo automáticamente).
Después, cambie el nombre del formulario de Form1.cs a AccountInfoForm.cs. Haga clic en el botón Agregar.
Se creará un nuevo formulario dentro de su proyecto; el IDE mostrará el diseñador de WinForms de Visual Studio. Como resultado, podrá utilizar todas sus características, incluida la adición de controles a través de una caja de herramientas.
En este ejemplo, ya hemos creado un formulario. Copie y pegue el siguiente código en el archivo 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;}}
El código anterior es generado por el diseñador de WinForms. Ahora escribiremos el código backend para el nuevo formulario. Para hacerlo, haga clic con el botón derecho en el formulario y seleccione Ver código.
Notablemente, nuestro código utiliza los métodos BeginInvokeOnMainThread() y BeginInvoke(). Explicamos nuestra razón para esto en la siguiente sección.
Por ahora, utilizaremos nuestro formulario personalizado llamando a su método ShowDialog() desde nuestro indicador. Copie y pegue el código a continuación en el código fuente de su indicador:
Cuando agrega un nuevo formulario, automáticamente utilizará el espacio de nombres de su indicador o cBot (cAlgo). Para evitar errores, cambie el espacio de nombres de su formulario agregado y luego agréguelo al archivo de código fuente de su indicador o cBot. En nuestro caso, WinForms_Test es la designación del espacio de nombres.
Después de compilar el indicador y crear una instancia, debería ver aparecer el siguiente formulario.
Acceder a los miembros de la API desde un hilo dedicado a la interfaz de usuario
Como hemos explicado antes, debe utilizar un hilo separado dedicado para ejecutar sus WinForms. Solo el código que se ejecuta en este hilo podrá acceder a los controles y propiedades del formulario.
Lo mismo es cierto para todos los miembros de la API de algo. Como la API no es segura para hilos, no puede acceder a los miembros de la API desde el hilo Form. En su lugar, debe despachar esta tarea utilizando el método BeginInvokeOnMainThread() en el código de su indicador o cBot.
Esto puede crear complicaciones si desea intercambiar datos entre su indicador o cBot y un WinForm. Para resolver este problema, puede crear una clase proxy para gestionar las interacciones entre los hilos de su cBot o indicador y Form.