Comme les indicateurs et les cBots de cTrader constituent des applications .NET, vous pouvez utiliser toutes les technologies .NET lors de leur création, y compris les solutions liées à l'interface utilisateur telles que WinForms et WPF. cTrader prend en charge toutes les fonctionnalités offertes par WinForms et WPF, y compris les dialogues, les formulaires, le concepteur WinForms de Visual Studio, les contrôles et composants d'interface utilisateur tiers.
Remarque
Les algos utilisant WinForms ou WPF ne peuvent être exécutés que sur des machines Windows.
Pour utiliser WinForms, changez votre compilateur cTrader pour le compilateur SDK .NET.
Nous allons montrer comment WinForms peut être utilisé en créant un dialogue WinForms personnalisé. Ils sont utiles pour afficher des alertes ou demander une saisie de l'utilisateur.
Configurer votre projet
Avant de pouvoir utiliser WinForms dans vos cBots et indicateurs, vous devrez apporter plusieurs modifications aux fichiers de projet de votre cBot ou indicateur. Comme WinForms ne fonctionne que sur Windows, vous devrez changer la cible de votre indicateur ou du framework cBot de votre projet vers la variante Windows de .NET.
Pour ce faire, ouvrez votre fichier de projet dans Visual Studio et remplacez son contenu par ce qui suit :
Nous avons ajouté la balise UseWindowsForms et changé la valeur de TargetFramework en net6.0-windows.
Ensuite, changez la valeur du paramètre de classe AccessRights en FullAccess. À moins que cette modification ne soit effectuée, votre extension n'aura pas les droits d'accès suffisants pour ouvrir une fenêtre WinForms.
Reconstruisez votre projet après avoir effectué les modifications ci-dessus.
Utiliser les dialogues
Heureusement, WinForms contient plusieurs dialogues prêts à l'emploi qui sont utiles pour une grande variété de cas, notamment pour afficher un message d'erreur, obtenir une confirmation de l'utilisateur ou sélectionner un fichier ou un dossier.
L'exemple ci-dessous utilise la classe MessageBox et les dialogues d'ouverture et d'enregistrement de fichiers.
MessageBox
Vous pouvez utiliser la classe System.Windows.Forms.MessageBox de la même manière qu'elle est utilisée dans toute autre application .NET.
Le code ci-dessous attache une boîte de confirmation standard à un indicateur :
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){}}}
Après avoir exécuté une instance de cet indicateur, vous devriez immédiatement voir une boîte de confirmation avec le texte que vous avez spécifié ci-dessus.
Dialogues d'ouverture ou d'enregistrement de fichiers
Le dialogue d'ouverture ou d'enregistrement de fichiers peut être utilisé pour permettre aux cBots et aux indicateurs de parcourir et de sélectionner des fichiers stockés localement.
Dans les exemples, nous avons utilisé un thread séparé pour exécuter le code lié à nos dialogues. Cela a été fait pour tenir compte du fait que le thread principal du cBot ou de l'indicateur n'est pas un thread STA. Nous entrons plus en détail sur ce problème dans la section suivante.
Utiliser un thread dédié pour l'interface utilisateur
Lors de l'exécution de code faisant référence à WinForms, vous devez utiliser un thread marqué STA, sinon, il peut y avoir des erreurs lors de l'exécution. Pour plus d'informations, veuillez vous référer à la documentation officielle.
Vous ne pouvez pas modifier la propriété ApartmentState d'un thread déjà en cours d'exécution. Comme le thread principal du cBot ou de l'indicateur n'est pas marqué STA, vous devez utiliser un nouveau thread marqué STA pour WinForms et WPF.
Le code de la section précédente appelle également la méthode thread.Join(). Elle bloque l'exécution du thread principal du cBot ou de l'indicateur jusqu'à ce que le thread lié à l'interface utilisateur soit libéré (par exemple, par un utilisateur cliquant sur OK dans un dialogue de confirmation).
Chaque fois que vous souhaitez afficher un formulaire Windows ou un élément WPF, vous devez exécuter votre code dans un thread séparé marqué STA.
Créer un nouveau formulaire personnalisé
Jusqu'à présent, nous avons utilisé des classes WinForms intégrées. Cependant, il est également possible de créer des classes personnalisées. Dans cette section, nous allons créer un formulaire affichant des informations sur le compte de trading actuellement actif, telles que son solde et sa marge.
Pour créer un nouveau formulaire, faites un clic droit sur votre cBot ou indicateur dans Visual Studio, cliquez sur Ajouter et sélectionnez Contrôle utilisateur (Windows Forms) ...
Visual Studio ouvrira une fenêtre de dialogue dans laquelle vous pourrez sélectionner le type d'élément que vous souhaitez ajouter à votre projet. Sélectionnez Formulaire (Windows Forms) (Visual Studio devrait le sélectionner automatiquement).
Ensuite, changez le nom du formulaire de Form1.cs en AccountInfoForm.cs. Cliquez sur le bouton Ajouter.
Un nouveau formulaire sera créé dans votre projet ; l'IDE affichera le concepteur WinForms de Visual Studio. En conséquence, vous pourrez utiliser toutes ses fonctionnalités, y compris l'ajout de contrôles via une boîte à outils.
Dans cet exemple, nous avons déjà créé un formulaire. Copiez et collez le code ci-dessous dans le fichier 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;}}
Le code ci-dessus est généré par le concepteur WinForms. Nous allons maintenant écrire le code backend pour le nouveau formulaire. Pour ce faire, faites un clic droit sur le formulaire et sélectionnez Afficher le code.
Notamment, notre code utilise les méthodes BeginInvokeOnMainThread() et BeginInvoke(). Nous expliquons notre raisonnement à ce sujet dans la section suivante.
Pour l'instant, nous utiliserons notre formulaire personnalisé en appelant sa méthode ShowDialog() depuis notre indicateur. Copiez et collez le code ci-dessous dans le code source de votre indicateur :
Lorsque vous ajoutez un nouveau formulaire, il utilisera automatiquement votre espace de noms d'indicateur ou de cBot (cAlgo). Pour éviter les erreurs, changez l'espace de noms de votre formulaire ajouté, puis ajoutez-le au fichier de code source de votre indicateur ou cBot. Dans notre cas, WinForms_Test est la désignation de l'espace de noms.
Après avoir construit l'indicateur et créé une instance, vous devriez voir le formulaire suivant apparaître.
Accéder aux membres de l'API depuis un thread dédié à l'interface utilisateur
Comme nous l'avons expliqué précédemment, vous devez utiliser un thread dédié séparé pour exécuter vos WinForms. Seul le code s'exécutant sur ce thread pourra accéder aux contrôles et propriétés du formulaire.
Il en va de même pour tous les membres de l'API algo. Comme l'API n'est pas thread-safe, vous ne pouvez pas accéder aux membres de l'API depuis le thread Form. Au lieu de cela, vous devez déléguer cette tâche en utilisant la méthode BeginInvokeOnMainThread() dans votre code d'indicateur ou de cBot.
Cela peut créer des complications si vous souhaitez échanger des données entre votre indicateur ou cBot et un WinForm. Pour résoudre ce problème, vous pouvez créer une classe proxy pour gérer les interactions entre vos threads de cBot ou d'indicateur et de Form.