Como os indicadores e cBots do cTrader constituem aplicações .NET, pode usar todas as tecnologias .NET ao criá-los, incluindo soluções relacionadas com UI como WinForms e WPF. O cTrader suporta todas as funcionalidades oferecidas pelo WinForms e WPF, incluindo diálogos, formulários, o designer WinForms do Visual Studio, controlos de UI de terceiros e componentes.
Nota
Os Algos que usam WinForms ou WPF só podem ser executados em máquinas Windows.
Para usar o WinForms, mude o seu compilador cTrader para o compilador .NET SDK.
Vamos demonstrar como o WinForms pode ser usado criando um diálogo WinForms personalizado. Estes são úteis para mostrar alertas ou pedir input ao utilizador.
Configurar o seu projeto
Antes de poder usar o WinForms nos seus cBots e indicadores, terá de fazer várias alterações no(s) ficheiro(s) do seu projeto de cBot ou indicador. Como o WinForms só funciona no Windows, terá de alterar o alvo da framework do seu indicador ou cBot do seu projeto para a variante Windows do .NET.
Para o fazer, abra o ficheiro do seu projeto no Visual Studio e substitua o seu conteúdo pelo seguinte:
Adicionámos a tag UseWindowsForms e alterámos o valor de TargetFramework para net6.0-windows.
Depois, altere o valor do parâmetro da classe AccessRights para FullAccess. A menos que esta alteração seja feita, a sua extensão não terá direitos de acesso suficientes para abrir uma janela WinForms.
Reconstrua o seu projeto após fazer as alterações acima.
Usar diálogos
Felizmente, o WinForms contém vários diálogos prontos a usar que são úteis para uma grande variedade de casos, nomeadamente mostrar uma mensagem de erro, obter uma confirmação do utilizador ou selecionar um ficheiro ou pasta.
O exemplo abaixo usa a classe MessageBox e os diálogos de abrir e guardar ficheiros.
MessageBox
Pode usar a classe System.Windows.Forms.MessageBox da mesma forma que é usada em qualquer outra aplicação .NET.
O código abaixo anexa uma caixa de confirmação padrão a um indicador:
Nos exemplos, usámos uma thread separada para executar o código relacionado com os nossos diálogos. Isto foi feito para ter em conta o facto de que a thread principal do cBot ou indicador não é uma thread STA. Abordamos este problema com mais detalhe na secção seguinte.
Usar uma thread dedicada para UI
Ao executar código que referencia WinForms, tem de usar uma thread marcada como STA, caso contrário, podem ocorrer erros durante a execução. Para mais informações, consulte a documentação oficial.
Não pode alterar a propriedade ApartmentState de uma thread já em execução. Como a thread principal do cBot ou indicador não está marcada como STA, tem de usar uma nova thread marcada como STA para WinForms e WPF.
O código na secção anterior também chama o método thread.Join(). Ele bloqueia a execução da thread principal do cBot ou indicador até que a thread relacionada com a UI seja libertada (por exemplo, quando um utilizador clica em OK num diálogo de confirmação).
Sempre que quiser exibir um Windows Form ou um elemento WPF, tem de executar o seu código numa thread separada marcada como STA.
Criar um novo formulário personalizado
Até agora, usámos classes WinForms incorporadas. No entanto, também é possível criar classes personalizadas. Nesta secção, vamos criar um formulário que mostra informações sobre a conta de negociação atualmente ativa, como o seu saldo e margem.
Para criar um novo formulário, clique com o botão direito no seu cBot ou indicador enquanto estiver no Visual Studio, clique em Adicionar e selecione User Control (Windows Forms) ...
O Visual Studio abrirá uma janela de diálogo na qual pode selecionar o tipo de item que gostaria de adicionar ao seu projeto. Selecione Form (Windows Forms) (o Visual Studio deve selecioná-lo automaticamente).
Depois, altere o nome do formulário de Form1.cs para AccountInfoForm.cs. Clique no botão Adicionar.
Um novo formulário será criado dentro do seu projeto; o IDE exibirá o designer WinForms do Visual Studio. Como resultado, poderá usar todas as suas funcionalidades, incluindo adicionar controlos através de uma caixa de ferramentas.
Neste exemplo, já criámos um formulário. Copie e cole o código abaixo no ficheiro 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;}}
O código acima é gerado pelo designer WinForms. Agora vamos escrever o código backend para o novo formulário. Para o fazer, clique com o botão direito no formulário e selecione Ver Código.
Notavelmente, o nosso código usa os métodos BeginInvokeOnMainThread() e BeginInvoke(). Explicamos a nossa lógica para isto na secção seguinte.
Por agora, vamos usar o nosso formulário personalizado chamando o seu método ShowDialog() a partir do nosso indicador. Copie e cole o código abaixo no código-fonte do seu indicador:
Quando adiciona um novo formulário, ele usará automaticamente o namespace do seu indicador ou cBot (cAlgo). Para evitar erros, altere o namespace do seu formulário adicionado e depois adicione-o ao ficheiro de código-fonte do seu indicador ou cBot. No nosso caso, WinForms_Test é a designação do namespace.
Depois de construir o indicador e criar uma instância, deverá ver o seguinte formulário aparecer.
Aceder a membros da API a partir de um thread dedicado à UI
Como explicámos anteriormente, tem de usar um thread dedicado separado para executar os seus WinForms. Apenas o código executado neste thread poderá aceder aos controlos e propriedades do formulário.
O mesmo se aplica a todos os membros da API de algo. Como a API não é thread safe, não pode aceder aos membros da API a partir do thread Form. Em vez disso, tem de despachar esta tarefa usando o método BeginInvokeOnMainThread() no código do seu indicador ou cBot.
Isto pode criar complicações se quiser trocar dados entre o seu indicador ou cBot e um WinForm. Para resolver este problema, pode criar uma classe proxy para gerir as interações entre os threads do seu cBot ou indicador e Form.