Karena indikator dan cBot cTrader merupakan aplikasi .NET, Anda dapat menggunakan semua teknologi .NET saat membuatnya termasuk solusi terkait UI seperti WinForms dan WPF. cTrader mendukung semua fitur yang ditawarkan oleh WinForms dan WPF termasuk dialog, form, desainer WinForms Visual Studio, kontrol UI pihak ketiga dan komponen.
Catatan
Algo yang menggunakan WinForms atau WPF hanya dapat dijalankan pada mesin Windows.
Untuk menggunakan WinForms, ubah kompilator cTrader Anda ke kompilator .NET SDK.
Kami akan menunjukkan bagaimana WinForms dapat digunakan dengan membuat dialog WinForms kustom. Mereka berguna untuk menampilkan peringatan atau meminta input pengguna.
Mengonfigurasi proyek Anda
Sebelum Anda dapat menggunakan WinForms dalam cBot dan indikator Anda, Anda harus membuat beberapa perubahan dalam file proyek cBot atau indikator Anda. Karena WinForms hanya berfungsi di Windows, Anda harus mengubah target framework indikator atau cBot proyek Anda ke varian Windows dari .NET.
Untuk melakukannya, buka file proyek Anda di Visual Studio dan ganti isinya dengan yang berikut:
Kami telah menambahkan tag UseWindowsForms dan mengubah nilai TargetFramework menjadi net6.0-windows.
Setelah itu, ubah nilai parameter kelas AccessRights menjadi FullAccess. Kecuali perubahan ini dibuat, ekstensi Anda tidak akan memiliki hak akses yang cukup untuk membuka jendela WinForms.
Bangun ulang proyek Anda setelah membuat perubahan di atas.
Menggunakan dialog
Untungnya, WinForms berisi beberapa dialog siap pakai yang berguna untuk berbagai kasus, terutama menampilkan pesan kesalahan, mendapatkan konfirmasi dari pengguna atau memilih file atau folder.
Contoh di bawah ini menggunakan kelas MessageBox dan dialog buka dan simpan file.
MessageBox
Anda dapat menggunakan kelas System.Windows.Forms.MessageBox dengan cara yang sama seperti yang digunakan dalam aplikasi .NET lainnya.
Kode di bawah ini melampirkan kotak konfirmasi standar ke indikator:
Dalam contoh, kami telah menggunakan thread terpisah untuk menjalankan kode yang terkait dengan dialog kami. Ini dilakukan untuk memperhitungkan fakta bahwa thread utama cBot atau indikator bukanlah thread STA. Kami membahas lebih detail tentang masalah ini di bagian berikutnya.
Gunakan thread khusus untuk UI
Saat menjalankan kode yang merujuk pada WinForms, Anda harus menggunakan thread yang ditandai STA, jika tidak, mungkin akan terjadi kesalahan selama eksekusi. Untuk informasi lebih lanjut, silakan lihat dokumentasi resmi.
Anda tidak dapat mengubah properti ApartmentState dari thread yang sudah berjalan. Karena thread utama cBot atau indikator tidak ditandai STA, Anda harus menggunakan thread baru yang ditandai STA untuk WinForms dan WPF.
Kode di bagian sebelumnya juga memanggil metode thread.Join(). Ini memblokir eksekusi thread utama cBot atau indikator sampai thread terkait UI dilepaskan (misalnya, oleh pengguna yang mengklik OK dalam dialog konfirmasi).
Setiap kali Anda ingin menampilkan Windows Form atau elemen WPF, Anda harus menjalankan kode Anda dalam thread terpisah yang ditandai STA.
Buat form kustom baru
Sejauh ini, kita telah menggunakan kelas WinForms bawaan. Namun, juga memungkinkan untuk membuat kelas kustom. Di bagian ini, kita akan membuat form yang menampilkan informasi tentang akun trading yang sedang aktif seperti saldo dan margin-nya.
Untuk membuat form baru, klik kanan cBot atau indikator Anda saat berada di Visual Studio, klik Add dan pilih User Control (Windows Forms) ...'
Visual Studio akan membuka jendela dialog di mana Anda dapat memilih jenis item yang ingin Anda tambahkan ke proyek Anda. Pilih Form (Windows Forms) (Visual Studio seharusnya memilihnya secara otomatis).
Setelah itu, ubah nama form dari Form1.cs menjadi AccountInfoForm.cs. Klik tombol Add.
Form baru akan dibuat di dalam proyek Anda; IDE akan menampilkan desainer WinForms Visual Studio. Hasilnya, Anda akan dapat menggunakan semua fiturnya termasuk menambahkan kontrol melalui toolbox.
Dalam contoh ini, kami telah membuat sebuah form. Salin dan tempel kode di bawah ini ke dalam 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;}}
Kode di atas dihasilkan oleh desainer WinForms. Kita sekarang akan menulis kode backend untuk form baru. Untuk melakukannya, klik kanan form dan pilih View Code.
Perlu diperhatikan, kode kita menggunakan metode BeginInvokeOnMainThread() dan BeginInvoke(). Kami menjelaskan alasan kami untuk ini di bagian berikutnya.
Untuk saat ini, kita akan menggunakan form kustom kita dengan memanggil metode ShowDialog() dari indikator kita. Salin dan tempel kode di bawah ini ke dalam kode sumber indikator Anda:
Ketika Anda menambahkan form baru, secara otomatis akan menggunakan namespace indikator atau cBot Anda (cAlgo). Untuk menghindari kesalahan, ubah namespace form yang Anda tambahkan dan kemudian tambahkan ke file kode sumber indikator atau cBot Anda. Dalam kasus kami, WinForms_Test adalah penunjukan namespace.
Setelah membangun indikator dan membuat instance, Anda seharusnya melihat form berikut muncul.
Akses anggota API dari thread khusus UI
Seperti yang telah kami jelaskan sebelumnya, Anda harus menggunakan thread terpisah yang didedikasikan untuk menjalankan WinForms Anda. Hanya kode yang dieksekusi pada thread ini yang akan dapat mengakses kontrol dan properti form.
Hal yang sama berlaku untuk semua anggota algo API. Karena API tidak thread safe, Anda tidak dapat mengakses anggota API dari thread Form. Sebaliknya, Anda harus mengirim tugas ini dengan menggunakan metode BeginInvokeOnMainThread() dalam kode indikator atau cBot Anda.
Ini mungkin menciptakan komplikasi jika Anda ingin bertukar data antara indikator atau cBot Anda dan WinForm. Untuk mengatasi masalah ini, Anda dapat membuat kelas proxy untuk mengelola interaksi antara thread cBot atau indikator Anda dan Form.