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:
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.
Utilizzo di finestre di dialogo
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:
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){}}}
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.
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)...'
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).
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.
In questo esempio abbiamo già creato un modulo. Copia e incolla il codice seguente nel file 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;}}
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.
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:
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.
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.