How to capture data from a view, perform a calculation, and return the result back to the view?

Viewed 115

UPDATE 1:

The HoldingsValue calculation works if I use a local HoldingsAmount value. How do I get it to take the value from the xaml HoldingsAmount Entry?

View: TickerView.xaml

Added binding to the text property of the entry control:

<Entry Placeholder="Enter holdings"
    Text="{Binding Ticker.HoldingsAmount}"
    Keyboard="Numeric" />

Models: Tickers.cs

Added HoldingsAmount to the model:

namespace MVVMDemo2.MVVM.Models
{
    public class Ticker
    {
        public decimal HoldingsAmount { get; set; }
        public string AskPrice { get; set; }
        public string BidPrice { get; set; }
        public string HoldingsValue { get; set; }
    }
}

ViewModels: TickerViewModel.cs

using Binance.Net.Clients;
using Binance.Net.Objects;
using MVVMDemo2.MVVM.Models;
using PropertyChanged;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MVVMDemo2.MVVM.ViewModels
{
    [AddINotifyPropertyChangedInterface]
    public class TickerViewModel
    {
        public Ticker Ticker { get; set; }
        //public decimal HoldingsAmount { get; set; }

        public TickerViewModel()
        {
            var socketClient = new BinanceSocketClient(new BinanceSocketClientOptions { });

            socketClient.SpotStreams.SubscribeToBookTickerUpdatesAsync("BTCUSDT", data => {

                decimal HoldingsAmount = 2;

                Ticker = new Ticker
                {
                    AskPrice = $"${data.Data.BestAskPrice.ToString("N2")}",
                    BidPrice = $"${data.Data.BestBidPrice.ToString("N2")}",
                    HoldingsValue = (HoldingsAmount * data.Data.BestBidPrice).ToString()
                };

            });

            socketClient.UnsubscribeAllAsync();
        }
    }
}

ORIGINAL:

I'm trying to learn MAUI and MVVM, but I can't seem to read a value from the XAML form to use with data from a WebSocket.

I currently have the following which works:

Views: TickerView.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MVVMDemo2.MVVM.Views.TickerView"
             Title="TickerView">
    
    <VerticalStackLayout
            VerticalOptions="Center" 
            HorizontalOptions="Center">
        
        <Label 
            Text="Holdings"
            FontSize="25" />
        <Entry Placeholder="Enter holdings"
            BackgroundColor="Gray"
            Keyboard="Numeric" />
        
        <Label 
            Text="Ask price"
            FontSize="25" />
        <Label 
            Text="{Binding Ticker.AskPrice}"
            FontSize="50" />
        
        <Label
            Text="Bid price"
            FontSize="25" />
        <Label 
            Text="{Binding Ticker.BidPrice}"
            FontSize="50" />

        <Label 
            Text="Holdings value"
            FontSize="25" />
        <Label 
            Text="{Binding Ticker.HoldingsValue}"
            FontSize="50" />

    </VerticalStackLayout>
    
</ContentPage>

Views: TickerView.xaml.cs

using MVVMDemo2.MVVM.Models;
using MVVMDemo2.MVVM.ViewModels;

namespace MVVMDemo2.MVVM.Views;

public partial class TickerView : ContentPage
{
    public TickerView()
    {
        InitializeComponent();

        BindingContext = new TickerViewModel();
    }
}

Models: Ticker.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MVVMDemo2.MVVM.Models
{
    public class Ticker
    {
        public string AskPrice { get; set; }
        public string BidPrice { get; set; }
    }
}

ViewModels: TickerViewModel.cs

using Binance.Net.Clients;
using Binance.Net.Objects;
using MVVMDemo2.MVVM.Models;
using PropertyChanged;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MVVMDemo2.MVVM.ViewModels
{
    [AddINotifyPropertyChangedInterface]
    public class TickerViewModel
    {
        public Ticker Ticker { get; set; }
        public TickerViewModel()
        {
            var socketClient = new BinanceSocketClient(new BinanceSocketClientOptions { });

            socketClient.SpotStreams.SubscribeToBookTickerUpdatesAsync("BTCUSDT", data => {

                Ticker = new Ticker
                {
                    AskPrice = $"${data.Data.BestAskPrice.ToString("N2")}",
                    BidPrice = $"${data.Data.BestBidPrice.ToString("N2")}",

                };

            });

            socketClient.UnsubscribeAllAsync();
        }
    }
}

I want to be able to read the value from the first Entry, so I can perform the following calculation:

BestAskPrice x HoldingsAmount

Once that calculation has been done, it should constantly update the xaml view similar to how the websocket constantly the AskPrice and BidPrice in the view in realtime.

In other words, I have figured out how to get value from a websocket and display it in a xaml view, but I can';t seem to figure out how to get data from a xaml view, process it with data from a websocket, and then in realtime display the data in the xaml view.

Can anyone help?

2 Answers

Solution using MVVM (you need to apply so many changes):

First you need to install nuget package CommunityToolkit.MVVM

Models: Ticker.cs

namespace MVVMDemo2.MVVM.Models
{
    public class Ticker
    {
        public string AskPrice { get; set; }
        public string BidPrice { get; set; }
        public string HoldingsValue { get; set; }
    }
}

Views: TickerView.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         x:Class="MVVMDemo2.MVVM.Views.TickerView"
         Title="TickerView">

<VerticalStackLayout
    VerticalOptions="Center" 
    HorizontalOptions="Center">

    <Label 
    Text="Holdings"
    FontSize="25" />
    <Entry Placeholder="Enter holdings"
    BackgroundColor="Gray"
    Text="{Binding HoldingsAmount}"
    Keyboard="Numeric" />

        <Label 
    Text="Ask price"
    FontSize="25" />
        <Label 
    Text="{Binding Ticker.AskPrice}"
    FontSize="50" />

        <Label
    Text="Bid price"
    FontSize="25" />
        <Label 
    Text="{Binding Ticker.BidPrice}"
    FontSize="50" />

        <Label 
    Text="Holdings value"
    FontSize="25" />
        <Label 
    Text="{Binding Ticker.HoldingsValue}"
    FontSize="50" />

</VerticalStackLayout>

Views: TickerView.xaml.cs

using MVVMDemo2.MVVM.Models;
using MVVMDemo2.MVVM.ViewModels;

namespace MVVMDemo2.MVVM.Views;

public partial class TickerView : ContentPage
{
    public TickerView(TickerViewModel vm)
    {
        InitializeComponent();

        BindingContext = vm;
    }
}

ViewModels: TickerViewModel.cs

using Binance.Net.Clients;
using Binance.Net.Objects;
using MVVMDemo2.MVVM.Models;
using PropertyChanged;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MVVMDemo2.MVVM.ViewModels
{
    
    public partial class TickerViewModel : ObservableObject
    {
        [ObservableProperty]
        public Ticker ticker;

        [ObservableProperty]
        public decimal holdingsAmount; 

        public TickerViewModel()
        {
            var socketClient = new BinanceSocketClient(new BinanceSocketClientOptions { });

        socketClient.SpotStreams.SubscribeToBookTickerUpdatesAsync("BTCUSDT", data => {

            Ticker = new Ticker
            {
                AskPrice = $"${data.Data.BestAskPrice.ToString("N2")}",
                BidPrice = $"${data.Data.BestBidPrice.ToString("N2")}",
                HoldingsValue = (HoldingsAmount * data.Data.BestBidPrice).ToString()
            };

        });

        socketClient.UnsubscribeAllAsync();
        }
    }
}

Explanation: using OBSERVABLE PROPERTIES you will be able to see the changed between your ui and apply them on the viewmodel. it's tested and got it working on my emulator:

holding 0 holding 12

i recommend you to follow James Montemagno and Gerald Versluis that explains everything on their videos about bindable properties, observable objects/properties and everything about maui!

UPDATE if you get a blank page:

App.xaml.cs

public App()
    {
        InitializeComponent();
        MainPage = new AppShell();
    }

AppShell.xaml:

<Shell
x:Class="MVVMDemo2.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MVVMDemo2.MVVM.Views">

<ShellContent 
    ContentTemplate="{DataTemplate local:TickerView}"
    Route="TickerView" /> 

</Shell>

AppShell.xaml.cs

public AppShell()
    {
        InitializeComponent();

        Routing.RegisterRoute(nameof(TickerView), typeof(TickerView));


    }
Related