Ana içeriğe geç

WPF

Windows Presentation Foundation (WPF), .NET'in bir parçası olan bir Windows kullanıcı arayüzü (UI) çözümüdür. İlk olarak 2006'da geliştirilen bu çözüm, WinForms için bir halef olarak tasarlanmıştır. Ek bilgi için resmi belgelerine başvurun.

WPF, zengin ve gelişmiş kullanıcı arayüzleri oluşturmaya olanak tanır. Microsoft tarafından geliştirilen XML tabanlı bir dil olan XAML'i kullanarak kodu kullanıcı arayüzünden ayırır. cTrader Windows bir WPF uygulamasıdır ve gördüğünüz tüm kullanıcı arayüzü öğeleri bu çözüm tarafından desteklenmektedir.

WPF kullanımı, neredeyse piksel mükemmelliğinde, zengin özelliklere sahip kullanıcı arayüzleri geliştirmeye olanak tanır. Ancak, WPF'nin öğrenme eğrisi diktir. Nispeten basit kullanıcı arayüzü öğeleri oluşturmak isterseniz, grafik kontrollerini veya WinForms kullanmak isteyebilirsiniz.

Not

WinForms veya WPF kullanan algoritmalar yalnızca Windows makinelerinde çalıştırılabilir.

WPF'yi cBot'lar ve göstergelerle kullanmak için, cTrader derleyicinizi değiştirin ve gömülü derleyiciden .NET SDK derleyicisine geçin.

Projenizi yapılandırma

WinForms benzer şekilde, WPF'yi cBot'larınız ve göstergelerinizle kullanmadan önce gösterge veya cBot proje dosyanızda bazı değişiklikler yapmanız gerekecektir. WPF yalnızca Windows'ta çalıştığı için, göstergenizin veya cBot projenizin hedef çerçevesini .NET'in Windows varyantına değiştirmeniz gerekecektir.

Bunu yapmak için, proje dosyanızı Visual Studio'da açın ve içeriğini aşağıdakilerle değiştirin:

 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 etiketini ekledik ve TargetFramework değerini net6.0-windows olarak değiştirdik.

Ardından, AccessRights sınıf parametresinin değerini FullAccess olarak değiştirin. Bu değişiklik yapılmadığı sürece, uzantınızın WPF ile çalışmak için yeterli erişim hakları olmayacaktır.

Yukarıdaki değişiklikleri yaptıktan sonra projenizi yeniden oluşturun.

WPF kullanarak bir pencere oluşturun ve gösterin

Özel bir pencere oluşturma süreci, özel bir WinForm oluşturmaya benzer.

Projenizi yapılandırdıktan sonra, sağ tıklayın, Ekle'yi seçin ve ardından Pencere (WPF)... seçeneğini belirleyin.

Image title

Yeni açılan pencerede, WPF penceresi seçeneğini belirleyin. Pencere adını MainWindow olarak ayarlayın ve Ekle düğmesine tıklayın. Yeni WPF penceresi proje çözüm gezgininizde görünecektir. İki dosyası olacaktır: bir .XAML dosyası ve arka uç kodunu içeren bir .cs dosyası.

.XAML dosyasını açın ve arka plan rengini "SlateGray" olarak değiştirmek için aşağıdaki kodu kopyalayıp yapıştırın.

 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>

Aşağıdaki kodu gösterge ana kaynak dosyanıza yapıştırın:

 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)
        {
        }
    }
}

Yeni bir WPF penceresi eklediğinizde, göstergenizin veya cBot'unuzun ad alanını (örneğin cAlgo) kullanacaktır. Pencere arka uç kodunu içeren .cs dosyasında, bunu WPF_Test olarak değiştirin ve gösterge kodunuzdaki using ifadesiyle eşleştiğinden emin olun.

Göstergeyi Visual Studio'dan veya cTrader Algo kullanarak yeniden oluşturun.

Derleme işlemi tamamlandıktan sonra, bir gösterge örneği oluşturun. Özel pencerenizin başlatıldıktan hemen sonra göründüğünü görmelisiniz.

Image title

Bir işlem paneli görüntüleyin

Bu örnekte, WPF kullanarak basit bir işlem paneli oluşturacağız. Yeni bir cBot oluşturun ve adını WPF Trading Panel olarak ayarlayın.

Yeni robotunuzu Visual Studio'da açın ve yukarıda belirtildiği gibi yeni bir WPF kaynağı ekleyin. Pencere adını MainWindow olarak ayarlayın.

Image title

Daha sonra, pencerenin .XAML dosyasını açın ve aşağıdaki kodu içine yapıştırın:

 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>

Ardından, arka uç .cs dosyasını açın ve aşağıdaki kodu yapıştırın:

  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());
            });
        }
    }
}

Artık cBot'umuzu kullanarak özel penceremizi başlatmaya hazırız. cBot kaynak dosyasını açın ve aşağıdaki kodu ekleyin:

 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();
        }
    }
}

Kodunuzu Visual Studio kullanarak yeniden oluşturun ve cBot'unuzun bir örneğini oluşturun. Çalıştırın ve aşağıdaki pencere görünmelidir.

Image title

Pencerede, semboller listesinden bir sembol seçin ve Yürüt'e tıklayın. İşlem panelimiz biraz basit olsa da, amaçlandığı gibi çalışır. Bunu karmaşık WPF kontrolleri oluşturmak için bir şablon olarak kullanabilirsiniz.

Penceremizin .xaml.cs dosyasındaki kod, cBot'umuza talimat göndermekten sorumludur. Pencereleriniz için arka uç kodu geliştirirken şu yönergeleri izleyin:

  • BeginInvokeOnMainThread() yöntemini kullanarak bir cBot veya gösterge ile etkileşimleri yönetmek için bir hizmet oluşturun.
  • Karmaşık WPF kontrolleri oluşturuyorsanız, MVVM, IoC kullanın ve iş mantığı bileşenlerinizi kullanıcı arayüzünden ayırın (örneğin, Prism) kullanarak).

Kullanıcı arayüzü için özel bir iş parçacığı kullanma

WinForms ve WPF pencereleri STA işaretli bir iş parçacığı içinde çalışmalıdır, aksi takdirde çalışmazlar. Daha fazla bilgi için resmi belgelere başvurun.

Zaten çalışmakta olan bir iş parçacığının ApartmentState özelliğini değiştiremezsiniz. Ana cBot veya gösterge iş parçacığı STA işaretli olmadığı için, WinForms ve WPF için yeni bir STA işaretli iş parçacığı kullanmanız gerekir.

API üyelerine kullanıcı arayüzü iş parçacığından erişin

Daha önce açıkladığımız gibi, WPF kontrollerinizi çalıştırmak için ayrı, özel bir iş parçacığı kullanmanız gerekir. Yalnızca bu iş parçacığında çalışan kod, pencere denetimlerine ve özelliklerine erişebilecektir.

Aynı durum tüm algoritmik API üyeleri için de geçerlidir. API iş parçacığı açısından güvenli değildir, bu nedenle kullanıcı arayüzü iş parçacığından API üyelerine erişemezsiniz. Bunun yerine, göstergenizin veya cBot kodunuzdaki BeginInvokeOnMainThread() yöntemini kullanarak işi göndermeniz gerekir.

Bu, göstergeniz veya cBot'unuz ile bir WPF penceresi arasında veri alışverişi yapmak isterseniz sorunlara yol açabilir. Bu sorunu çözmek için, cBot'unuz veya göstergeniz ile kullanıcı arayüzüyle ilgili iş parçacıkları arasındaki etkileşimleri yönetmek üzere bir proxy sınıfı oluşturabilirsiniz.