انتقل إلى المحتوى

WPF

Windows Presentation Foundation (WPF) هو حل واجهة مستخدم Windows وهو جزء من .NET. تم تطويره في الأصل في عام 2006، وكان يُقصد به أن يكون خلفًا لـ WinForms. للحصول على معلومات إضافية، راجع وثائقه الرسمية.

يسمح WPF بإنشاء واجهة مستخدم غنية ومتطورة. إنه يفصل الكود عن واجهة المستخدم باستخدام XAML، وهي لغة تستند إلى XML طورتها Microsoft. cTrader Windows هو تطبيق WPF وجميع عناصر واجهة المستخدم التي تراها مدعومة بهذا الحل.

استخدام WPF يسمح بتطوير واجهة مستخدم غنية بالميزات تقترب من الكمال على مستوى البكسل. ومع ذلك، فإن WPF له منحنى تعلم حاد. إذا كنت ترغب في إنشاء عناصر واجهة مستخدم بسيطة نسبيًا، فقد ترغب في استخدام عناصر التحكم في الرسم البياني أو WinForms.

ملاحظة

يمكن تشغيل الخوارزميات التي تستخدم WinForms أو WPF فقط على أجهزة Windows.

لاستخدام WPF مع cBots والمؤشرات، قم بتغيير مترجم cTrader الخاص بك من المترجم المضمن إلى مترجم .NET SDK.

تكوين مشروعك

بشكل مماثل لـ WinForms، قبل أن تتمكن من استخدام WPF مع cBots والمؤشرات الخاصة بك، سيتعين عليك إجراء بعض التغييرات على ملف مشروع المؤشر أو cBot الخاص بك. نظرًا لأن WPF يعمل فقط على Windows، سيتعين عليك تغيير إطار العمل المستهدف لمشروع المؤشر أو cBot الخاص بك إلى نسخة Windows من .NET.

للقيام بذلك، افتح ملف المشروع الخاص بك في Visual Studio واستبدل محتوياته بما يلي:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0-windows</TargetFramework>
    <UseWpf>true</UseWpf>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="cTrader.Automate" Version="1.*" />
  </ItemGroup>
</Project>

لقد أضفنا علامة UseWpf وغيرنا قيمة TargetFramework إلى net6.0-windows.

بعد ذلك، قم بتغيير قيمة معلمة فئة AccessRights إلى FullAccess. ما لم يتم إجراء هذا التغيير، لن يكون لملحقك حقوق وصول كافية للعمل مع WPF.

أعد بناء مشروعك بعد إجراء التغييرات المذكورة أعلاه.

إنشاء وعرض نافذة باستخدام WPF

عملية إنشاء نافذة مخصصة مشابهة لـ إنشاء نموذج WinForm مخصص.

بعد تكوين مشروعك، انقر بزر الماوس الأيمن عليه، حدد إضافة ثم اختر نافذة (WPF)....

Image title

في النافذة المفتوحة حديثًا، حدد خيار نافذة WPF. قم بتعيين اسم النافذة إلى MainWindow وانقر على زر إضافة. ستظهر نافذة WPF الجديدة في مستكشف حلول المشروع الخاص بك. سيكون لديها ملفان، ملف .XAML وملف .cs يحتوي على الكود الخلفي.

افتح ملف .XAML وانسخ والصق الكود التالي فيه لتغيير لون خلفيته إلى "SlateGray".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<Window x:Class="WPF_Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Test"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" Background="SlateGray">
    <Grid />
</Window>

الصق الكود التالي في ملف المصدر الرئيسي للمؤشر الخاص بك:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using cAlgo.API;
using System.Threading;
using WPF_Test;

namespace WPFTest
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.FullAccess)]
    public class WPFTest : Indicator
    {
        protected override void Initialize()
        {
            var thread = new Thread(() =>
            {
                var window = new MainWindow();

                _ = window.ShowDialog();
            });

            thread.SetApartmentState(ApartmentState.STA);

            thread.Start();
        }

        public override void Calculate(int index)
        {
        }
    }
}

عند إضافة نافذة WPF جديدة، ستستخدم مساحة الاسم الخاصة بالمؤشر أو cBot الخاص بك (مثل cAlgo). في ملف .cs الذي يحتوي على الكود الخلفي للنافذة، قم بتغييره إلى WPF_Test وتأكد من أنه يتطابق مع بيان using في كود المؤشر.

أعد بناء المؤشر إما من Visual Studio أو باستخدام cTrader Algo.

بعد انتهاء عملية البناء، قم بإنشاء نسخة من المؤشر. يجب أن ترى النافذة المخصصة الخاصة بك تظهر تقريبًا فور التشغيل.

Image title

عرض لوحة تداول

في هذا المثال، سننشئ لوحة تداول بسيطة باستخدام WPF. قم بإنشاء cBot جديد وقم بتعيين اسمه إلى WPF Trading Panel.

افتح الروبوت الجديد في Visual Studio، وأضف مورد WPF جديد كما هو موضح أعلاه. قم بتعيين اسم النافذة إلى MainWindow.

Image title

بعد ذلك، افتح ملف النافذة .XAML والصق الكود التالي فيه:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<Window x:Class="WPF_Trading_Panel.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Trading_Panel"
        mc:Ignorable="d"
        Title="WPF Trading Panel" Height="200" Width="500" Background="SlateGray" WindowStartupLocation="CenterScreen" ResizeMode="NoResize" Closed="Window_Closed" Loaded="Window_Loaded" DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <TextBlock Grid.Column="0" Grid.Row="0" Text="Symbol" Margin="5" />
        <ComboBox Grid.Column="1" Grid.Row="0" ItemsSource="{Binding Symbols, UpdateSourceTrigger=PropertyChanged}" SelectedValue="{Binding SelectedSymbol, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="5" />

        <TextBlock Grid.Column="0" Grid.Row="1" Text="Direction" Margin="5" />
        <ComboBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding Directions, UpdateSourceTrigger=PropertyChanged}" SelectedValue="{Binding SelectedDirection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="5" />

        <TextBlock Grid.Column="0" Grid.Row="2" Text="Volume" Margin="5" />
        <TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Volume, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="5" />

        <StackPanel Orientation="Vertical" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="3" VerticalAlignment="Bottom">
            <Button x:Name="ExecuteButton" Content="Execute" Click="ExecuteButton_Click" Margin="5" />
            <Button x:Name="CloseAllButton" Content="Close All Symbol Positions" Click="CloseAllButton_Click"  Margin="5" />
        </StackPanel>
    </Grid>
</Window>

بعد ذلك، افتح ملف .cs الخلفي والصق الكود التالي:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
using System;
using System.Collections.Generic;
using System.Windows;
using System.Collections.ObjectModel;
using cAlgo.API;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Linq;

namespace WPF_Trading_Panel
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private readonly Robot _robot;
        private string _volume, _selectedSymbol, _selectedDirection;

        public MainWindow(Robot robot)
        {
            _robot = robot;

            InitializeComponent();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public ObservableCollection<string> Symbols { get; } = new ObservableCollection<string>();

        public ObservableCollection<string> Directions { get; } = new ObservableCollection<string>(new string[] { "Buy", "Sell" });

        public string SelectedSymbol
        {
            get => _selectedSymbol;
            set
            {
                if (SetValue(ref _selectedSymbol, value))
                {
                    ChangeVolume();
                }
            }
        }

        public string SelectedDirection
        {
            get => _selectedDirection;
            set => SetValue(ref _selectedDirection, value);
        }

        public string Volume
        {
            get => _volume;
            set => SetValue(ref _volume, value);
        }

        private void ExecuteButton_Click(object sender, RoutedEventArgs e)
        {
            _robot.BeginInvokeOnMainThread(() =>
            {
                var direction = "Buy".Equals(SelectedDirection, StringComparison.Ordinal) ? TradeType.Buy : TradeType.Sell;

                var volume = double.Parse(Volume);

                _ = _robot.ExecuteMarketOrder(direction, SelectedSymbol, volume);
            });
        }

        private void Window_Closed(object sender, EventArgs _) => _robot.BeginInvokeOnMainThread(_robot.Stop);

        private bool SetValue<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            if (Equals(storage, value))
            {
                return false;
            }

            storage = value;

            OnPropertyChanged(propertyName);

            return true;
        }

        private void Window_Loaded(object sender, RoutedEventArgs _)
        {
            PopulateSymbols();

            SelectedDirection = Directions[0];
        }

        private void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        private void CloseAllButton_Click(object sender, RoutedEventArgs e)
        {
            _robot.BeginInvokeOnMainThread(() =>
            {
                foreach (var position in _robot.Positions)
                {
                    if (position.SymbolName.Equals(SelectedSymbol, StringComparison.Ordinal) is false) continue;

                    _ = _robot.ClosePositionAsync(position);
                }
            });
        }

        private void PopulateSymbols()
        {
            _robot.BeginInvokeOnMainThread(() =>
            {
                var symbolsList = new List<string>();

                symbolsList.AddRange(_robot.Symbols);

                _ = Dispatcher.BeginInvoke(() =>
                {
                    foreach (var symbol in symbolsList)
                    {
                        Symbols.Add(symbol);
                    }

                    SelectedSymbol = Symbols.FirstOrDefault();
                });
            });
        }

        private void ChangeVolume()
        {
            if (string.IsNullOrWhiteSpace(SelectedSymbol)) return;

            _robot.BeginInvokeOnMainThread(() =>
            {
                var minVolume = _robot.Symbols.GetSymbol(SelectedSymbol).VolumeInUnitsMin;

                _ = Dispatcher.BeginInvoke(() => Volume = minVolume.ToString());
            });
        }
    }
}

نحن الآن جاهزون لإطلاق النافذة المخصصة الخاصة بنا باستخدام cBot الخاص بنا. افتح ملف المصدر cBot وأدخل الكود أدناه:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using cAlgo.API;
using System.Threading;
using WPF_Trading_Panel;

namespace WPFTradingPanel
{
    [Robot(AccessRights = AccessRights.FullAccess)]
    public class WPFTradingPanel : Robot
    {
        protected override void OnStart()
        {
            var thread = new Thread(() =>
            {
                var window = new MainWindow(this);

                _ = window.ShowDialog();
            });

            thread.SetApartmentState(ApartmentState.STA);

            thread.Start();
        }
    }
}

أعد بناء الكود الخاص بك باستخدام Visual Studio وقم بإنشاء نسخة من cBot الخاص بك. قم بتشغيله ويجب أن تظهر النافذة التالية.

Image title

في النافذة، حدد رمزًا من قائمة الرموز وانقر على تنفيذ. في حين أن لوحة التداول الخاصة بنا بسيطة بعض الشيء، إلا أنها تعمل كما هو مقصود. يمكنك استخدامها كقالب لإنشاء عناصر تحكم WPF معقدة.

الكود في ملف .xaml.cs لنافذتنا مسؤول عن إرسال التعليمات إلى cBot الخاص بنا. عند تطوير الكود الخلفي للنوافذ الخاصة بك، اتبع هذه الإرشادات:

    • قم بإنشاء خدمة لإدارة التفاعلات مع cBot أو مؤشر باستخدام طريقة BeginInvokeOnMainThread().
    • إذا كنت تبني عناصر تحكم WPF معقدة، استخدم MVVM، IoC وافصل مكونات منطق الأعمال الخاصة بك عن واجهة المستخدم (على سبيل المثال، باستخدام Prism)

استخدام خيط مخصص لواجهة المستخدم

لا يمكنك تغيير خاصية ApartmentState لخيط قيد التشغيل بالفعل. نظرًا لأن الخيط الرئيسي لـ cBot أو المؤشر غير مميز بـ STA، يجب عليك استخدام خيط جديد مميز بـ STA لـ WinForms وWPF.

الوصول إلى أعضاء API من سلسلة واجهة المستخدم

كما شرحنا سابقًا، يجب عليك استخدام سلسلة منفصلة مخصصة لتشغيل عناصر تحكم WPF الخاصة بك. فقط الكود الذي يتم تنفيذه على هذه السلسلة سيكون قادرًا على الوصول إلى عناصر التحكم وخصائص النافذة.

ينطبق الشيء نفسه على جميع أعضاء API للخوارزميات. نظرًا لأن API غير آمن للسلاسل، لا يمكنك الوصول إلى أعضاء API من سلسلة واجهة المستخدم. بدلاً من ذلك، يجب عليك إرسال العمل باستخدام طريقة BeginInvokeOnMainThread() في كود المؤشر أو cBot الخاص بك.

قد يؤدي هذا إلى تعقيدات إذا كنت ترغب في تبادل البيانات بين المؤشر أو cBot الخاص بك ونافذة WPF. لحل هذه المشكلة، يمكنك إنشاء فئة وسيطة لإدارة التفاعلات بين cBot أو المؤشر الخاص بك والسلاسل المتعلقة بواجهة المستخدم.