Crypto Price Widget WPF

With this app I will start developing tools for the crypto market. I plan to specialize in scalping while tracking global trends within a day and at longer intervals.

Step 1: Project Setup

Create an app like this to display the prices of major cryptocurrencies.
Create a new project in visual studio.
Let’s call it CryptoWidgetWPF
In the project, you need to create a new folder where we will throw the icons of cryptocurrencies.
Also, the following NuGet packages need to be installed:

Step 2: Creating the User Interface (UI)

  1. Open MainWindow.xaml and add the main interface:
    • We will create an interface consisting of a window without a standard title bar and control buttons.
				
					<Window x:Class="CryptoWidgetWPF.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:CryptoWidgetWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        WindowStyle="None" AllowsTransparency="True" Background="Transparent" 
        SizeToContent="WidthAndHeight" ResizeMode="NoResize">
    <WindowChrome.WindowChrome>
        <WindowChrome CaptionHeight="0" CornerRadius="15" GlassFrameThickness="0"/>
    </WindowChrome.WindowChrome>
    <Border Background="#CC2C2F33" CornerRadius="15" Padding="10">
        <Grid Background="Transparent">
            <!-- Window Header -->
            <Grid Height="45" VerticalAlignment="Top" Background="Transparent" MouseLeftButtonDown="Window_MouseLeftButtonDown">
                <TextBlock Text="Crypto Widget" VerticalAlignment="Center" Margin="15,0,20,0" Foreground="LimeGreen" FontSize="22"/>
                <TextBlock Name="dateTimeTextBlock" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="LightBlue" FontSize="30" Margin="30,0,0,0"/>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                    <!-- Minimize Button -->
                    <Button Width="45" Background="Transparent" BorderBrush="Transparent" Foreground="White" Click="Minimize_Click" Opacity="0.5">
                        <Button.Template>
                            <ControlTemplate TargetType="Button">
                                <Border x:Name="borderElement" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}">
                                    <Path x:Name="pathElement" Data="M 0 0 L 15 0" Stroke="{TemplateBinding Foreground}" StrokeThickness="3" HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0.5"/>
                                </Border>
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsMouseOver" Value="True">
                                        <Setter Property="Opacity" Value="1" TargetName="pathElement"/>
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Button.Template>
                    </Button>
                    <!-- Maximize/Restore Button -->
                    <Button Width="45" Background="Transparent" BorderBrush="Transparent" Foreground="White" Click="MaximizeRestore_Click" Opacity="0.5">
                        <Button.Template>
                            <ControlTemplate TargetType="Button">
                                <Border x:Name="borderElement" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}">
                                    <Path x:Name="pathElement" Data="M 0 0 L 15 0 L 15 15 L 0 15 Z" Stroke="{TemplateBinding Foreground}" StrokeThickness="3" HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0.5"/>
                                </Border>
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsMouseOver" Value="True">
                                        <Setter Property="Opacity" Value="1" TargetName="pathElement"/>
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Button.Template>
                    </Button>
                    <!-- Close Button -->
                    <Button Width="45" Background="Transparent" BorderBrush="Transparent" Foreground="White" Click="Close_Click" Opacity="0.5">
                        <Button.Template>
                            <ControlTemplate TargetType="Button">
                                <Border x:Name="borderElement" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}">
                                    <Path x:Name="pathElement" Data="M 0 0 L 15 15 M 0 15 L 15 0" Stroke="{TemplateBinding Foreground}" StrokeThickness="3" HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0.5"/>
                                </Border>
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsMouseOver" Value="True">
                                        <Setter Property="Opacity" Value="1" TargetName="pathElement"/>
                                        <Setter Property="Stroke" Value="Red" TargetName="pathElement"/>
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Button.Template>
                    </Button>
                </StackPanel>
            </Grid>

            <!-- Main Window Content -->
            <Grid Background="Transparent" Margin="0,45,0,10">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>

                <!-- BTC -->
                <Image Source="Resources/btc.png" Width="45" Height="45" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="15,10,15,10"/>
                <Label Content="BTC" Foreground="Orange" FontSize="36" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Margin="0,10,30,10"/>
                <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="2" VerticalAlignment="Center" Margin="0,10,0,10">
                    <TextBlock Text="$" Foreground="#1AAB1A" FontSize="38" VerticalAlignment="Center" Margin="0,0,5,0"/>
                    <TextBlock Name="btcPrice" Foreground="White" FontSize="50" VerticalAlignment="Center"/>
                </StackPanel>
                <TextBlock Name="btcChange" FontSize="36" Grid.Row="0" Grid.Column="3" VerticalAlignment="Center" Margin="30,10,30,10"/>

                <!-- ETH -->
                <Image Source="Resources/eth.png" Width="45" Height="45" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="15,10,15,10"/>
                <Label Content="ETH" Foreground="Orange" FontSize="36" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Margin="0,10,30,10"/>
                <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="2" VerticalAlignment="Center" Margin="0,10,0,10">
                    <TextBlock Text="$" Foreground="#1AAB1A" FontSize="38" VerticalAlignment="Center" Margin="0,0,5,0"/>
                    <TextBlock Name="ethPrice" Foreground="White" FontSize="50" VerticalAlignment="Center"/>
                </StackPanel>
                <TextBlock Name="ethChange" FontSize="36" Grid.Row="1" Grid.Column="3" VerticalAlignment="Center" Margin="30,10,30,10"/>

                <!-- LTC -->
                <Image Source="Resources/ltc.png" Width="45" Height="45" Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="15,10,15,10"/>
                <Label Content="LTC" Foreground="Orange" FontSize="36" Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" Margin="0,10,30,10"/>
                <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="2" VerticalAlignment="Center" Margin="0,10,0,10">
                    <TextBlock Text="$" Foreground="#1AAB1A" FontSize="38" VerticalAlignment="Center" Margin="0,0,5,0"/>
                    <TextBlock Name="ltcPrice" Foreground="White" FontSize="50" VerticalAlignment="Center"/>
                </StackPanel>
                <TextBlock Name="ltcChange" FontSize="36" Grid.Row="2" Grid.Column="3" VerticalAlignment="Center" Margin="30,10,30,10"/>
            </Grid>
        </Grid>
    </Border>
</Window>

				
			

Step 3: Connecting to WebSocket and Updating the UI

  1. Open MainWindow.xaml.cs
    and add logic for connecting to WebSocket and updating the interface.
				
					using System;
using System.IO;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using WebSocketSharp;
using Newtonsoft.Json.Linq;
using System.Globalization;
using System.Windows.Threading;

namespace CryptoWidgetWPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        private WebSocket _webSocket;
        private readonly string logFilePath = "WebSocketErrors.log"; // Path to the log file

        public MainWindow()
        {
            InitializeComponent();

            StartWebSocket();

            // Set the initial time value
            dateTimeTextBlock.Text = DateTime.Now.ToString("dd.MM.yyyy HH:mm");

            // Start a timer to update the time every second
            DispatcherTimer timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromSeconds(1);
            timer.Tick += (sender, args) =>
            {
                dateTimeTextBlock.Text = DateTime.Now.ToString("dd.MM.yyyy HH:mm");
            };
            timer.Start();
        }

        private void StartWebSocket()
        {
            _webSocket = new WebSocket("wss://stream.binance.com:9443/ws/!ticker@arr");
            _webSocket.OnMessage += (sender, e) =>
            {
                try
                {
                    var json = JArray.Parse(e.Data);

                    foreach (var ticker in json)
                    {
                        string symbol = ticker["s"].ToString();

                        // Only process specific symbols
                        if (symbol == "BTCUSDT" || symbol == "ETHUSDT" || symbol == "LTCUSDT")
                        {
                            if (Double.TryParse(ticker["c"].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out double price) &&
                                Double.TryParse(ticker["P"].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out double priceChangePercent))
                            {
                                // Format the price and percentage
                                string formattedPrice = price.ToString("#,##0.00", CultureInfo.InvariantCulture);
                                string formattedPercent = priceChangePercent.ToString("0.00", CultureInfo.InvariantCulture);

                                Dispatcher.Invoke(() =>
                                {
                                    switch (symbol)
                                    {
                                        case "BTCUSDT":
                                            btcPrice.Text = $"{formattedPrice}";
                                            btcChange.Text = $"{formattedPercent}%";
                                            btcChange.Foreground = priceChangePercent >= 0 ? Brushes.LimeGreen : Brushes.Red;
                                            break;

                                        case "ETHUSDT":
                                            ethPrice.Text = $"{formattedPrice}";
                                            ethChange.Text = $"{formattedPercent}%";
                                            ethChange.Foreground = priceChangePercent >= 0 ? Brushes.LimeGreen : Brushes.Red;
                                            break;

                                        case "LTCUSDT":
                                            ltcPrice.Text = $"{formattedPrice}";
                                            ltcChange.Text = $"{formattedPercent}%";
                                            ltcChange.Foreground = priceChangePercent >= 0 ? Brushes.LimeGreen : Brushes.Red;
                                            break;
                                    }
                                });
                            }
                            else
                            {
                                LogError($"Failed to parse price or priceChangePercent for symbol: {symbol}. Data received: {ticker.ToString()}");
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogError("Exception during OnMessage: " + ex.ToString());
                }
            };

            _webSocket.OnError += (sender, e) =>
            {
                LogError("WebSocket Error: " + e.Message);
            };

            _webSocket.OnClose += (sender, e) =>
            {
                LogError("WebSocket Closed: " + e.Reason);
                ReconnectWebSocket();
            };

            _webSocket.Connect();
        }

        private void ReconnectWebSocket()
        {
            if (_webSocket != null)
            {
                _webSocket.Close();
                _webSocket = null;
            }

            StartWebSocket();
        }

        private void LogError(string message)
        {
            try
            {
                using (StreamWriter writer = new StreamWriter(logFilePath, true))
                {
                    writer.WriteLine($"{DateTime.Now}: {message}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed to log error: " + ex.Message);
            }
        }

        private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount == 2)
            {
                ToggleMaximizeRestore();
            }
            else
            {
                DragMove();
            }
        }

        private void Minimize_Click(object sender, RoutedEventArgs e)
        {
            WindowState = WindowState.Minimized;
        }

        private void MaximizeRestore_Click(object sender, RoutedEventArgs e)
        {
            ToggleMaximizeRestore();
        }

        private void Close_Click(object sender, RoutedEventArgs e)
        {
            Close();
        }

        private void ToggleMaximizeRestore()
        {
            if (WindowState == WindowState.Maximized)
            {
                WindowState = WindowState.Normal;
            }
            else
            {
                WindowState = WindowState.Maximized;
            }
        }
    }
}

				
			
Run the compilation. You should see an application like this:
crypto widget
I did notice one bug, though. When the internet goes down, the values are no longer updated, even after the internet connection is restored. I think that after a connection failure the websocket stops working and needs to be restarted. This will be your homework: implement a data retrieval check and restart the websocket if necessary.

 

https://github.com/DebuggerPlus/CryptoWidgetWPF.git