Blog Dla Młodszych Programistów C#/.NET

W tym artykule pokaże Ci jak zaimplementować całą logikę naszej aplikacji mobilnej, którą zaczęliśmy pisać w poprzednim materiale (xamarin cz. 1). Jak już wspominałem Ci ostatnio, ten kod będzie podobny do kodu, który pisaliśmy już wcześniej w aplikacji w WPF'ie. Tak samo tutaj w Xamarin'ie piszemy zgodnie ze wzorcem MVVM, dlatego powiążemy właściwości i zdarzenia z komendami w ViewModel'u. Tutaj będziemy mieli trochę mniej kodu do napisania, ponieważ szablon, który utworzyliśmy wcześniej z Visual Studio dużo nam ułatwia i sporo kodu już za nas wcześniej wygenerował.

Pierwsza Aplikacja Mobilna Xamarin w C# – Logika MVVM (2/2)


Główny ViewModel – MainViewModel


Uruchom proszę projekt, który utworzyliśmy w poprzednim filmie i możemy przejść do faktycznej implementacji. W naszym projekcie mamy zaimplementowany cały interfejs użytkownika (widok), ale nie mamy jeszcze żadnej logiki. Tym zajmiemy się w teraz, pokażę Ci jak zaimplementować całą logikę naszego kalkulatora w aplikacji mobilnej.

PIERWSZA APLIKACJA Mobilna XAMARIN w C# Logika Aplikacji – Start

W widoku MainPage.xaml mamy ustawiony BindingContext na MainViewModel. Ostatnio jak tworzyliśmy aplikacji w WPF'ie, pokazywałem Ci jak to zrobić w CodeBehind, jak widzisz, to samo można zrobić w widoku XAML. Możemy tutaj wskazać na ViewModel dla tego widoku.

<ContentPage.BindingContext>
    <vm:MainViewModel />
</ContentPage.BindingContext>

Czyli w tym miejscu wiążemy widok z ViewModel'em. Tak wygląda w tej chwili sam ViewModel:

public class MainViewModel : BaseViewModel
{
    public MainViewModel()
    {
        Title = "Kalkulator";
        OpenWebCommand = new Command(async () => await Browser.OpenAsync("https://aka.ms/xamarin-quickstart"));
    }

    public ICommand OpenWebCommand { get; }
}

Jest to ViewModel wygenerowany z szablonu (zmieniliśmy tylko ostatnio wartość właściwości Title), który stworzyliśmy w Visual Studio. Zauważ, że ten MainViewModel dziedziczy po BaseViewModel i właśnie BaseViewModel implementuje interfejs INotifyPropertyChanged.

public class BaseViewModel : INotifyPropertyChanged
{
    public IDataStore<Item> DataStore => DependencyService.Get<IDataStore<Item>>();

    bool isBusy = false;
    public bool IsBusy
    {
        get { return isBusy; }
        set { SetProperty(ref isBusy, value); }
    }

    string title = string.Empty;
    public string Title
    {
        get { return title; }
        set { SetProperty(ref title, value); }
    }

    protected bool SetProperty<T>(ref T backingStore, T value,
        [CallerMemberName] string propertyName = "",
        Action onChanged = null)
    {
        if (EqualityComparer<T>.Default.Equals(backingStore, value))
            return false;

        backingStore = value;
        onChanged?.Invoke();
        OnPropertyChanged(propertyName);
        return true;
    }

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        var changed = PropertyChanged;
        if (changed == null)
            return;

        changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
}

To jest implementacja tego interfejsu, czyli znowu wygląda ona bardzo podobnie do tego, co robiliśmy wcześniej w WPF'ie. Mamy tutaj tym razem metodę SetProperty, którą możemy wywołać w Set'ach naszych właściwości i od razu będzie wywoływana metoda OnPropertyChanged.


Deklaracja komend i metod w ViewModel'u


W takim razie przejdźmy od razu do klasy MainViewModel i musimy zdefiniować tutaj nowe komendy. Komendę OpenWebCommand możemy usunąć, nie będzie nam ona potrzebna.

public class MainViewModel : BaseViewModel
{
    public MainViewModel()
    {
        Title = "Kalkulator";
    }
}

Podobnie jak to robiliśmy w aplikacji WPF, dodamy komendę AddNumberCommand. W konstruktorze przypiszemy metodę, która będzie wywoływana po kliknięciu odpowiednich przycisków.

public class MainViewModel : BaseViewModel
{
    public MainViewModel()
    {
        Title = "Kalkulator";
        AddNumberCommand = new Command(AddNumber);
    }

    public ICommand AddNumberCommand { get; }

    private void AddNumber(object obj)
    {
        throw new NotImplementedException();
    }
}

Do faktycznej implementacji metody AddNumber wrócimy za chwilę. Jakie potrzebujemy jeszcze właściwości? Na pewno AddOperationCommand, ClearScreenCommand oraz GetResultCommand. Jeżeli zapoznałeś się z poprzednimi materiałami, to na pewno pamiętasz, że w WPF'ie robiliśmy to bardzo podobnie, też potrzebowaliśmy takich komend.

public class MainViewModel : BaseViewModel
{
    public MainViewModel()
    {
        Title = "Kalkulator";
        AddNumberCommand = new Command(AddNumber);
        AddOperationCommand = new Command(AddOperation);
        ClearScreenCommand = new Command(ClearScreen);
        GetResultCommand = new Command(GetResult);
    }

    public ICommand AddNumberCommand { get; }
    public ICommand AddOperationCommand { get; }
    public ICommand ClearScreenCommand { get; }
    public ICommand GetResultCommand { get; }

    private void AddNumber(object obj)
    {
        throw new NotImplementedException();
    }

    private void GetResult(object obj)
    {
        throw new NotImplementedException();
    }

    private void ClearScreen(object obj)
    {
        throw new NotImplementedException();
    }

    private void AddOperation(object obj)
    {
        throw new NotImplementedException();
    }
}

Dodałem też od razu w konstruktorze inicjalizację tych wszystkich komend, a także zdefiniowałem już same metody, które zostaną wywołane po wyzwoleniu każdej z tych komend. Jak widzisz, poniżej konstruktora definiuje najpierw właściwości, a poniżej metody. Zawsze trzymam się takiej kolejności, żeby zachować porządek w moim kodzie.


Deklaracja właściwości w ViewModel'u


Potrzebujemy jeszcze 1 właściwość, będzie to właściwość ScreenVal i będzie ona powiązana z naszym komponentem Entry i na niej będzie wyświetlany wynik naszych obliczeń na kalkulatorze oraz wszystkie operacje, które będziemy wprowadzać. Na samej górze nad konstruktorem deklaruje pole:

private string _screenVal;

A nad metodami właściwość:

public string ScreenVal
{
    get { return _screenVal; }
    set { SetProperty(ref _screenVal, value); }
}

Tak jak ci wspominałem wcześniej w naszym bazowym ViewModel'u (BaseViewModel) mamy metodę SetProperty, którą możemy tutaj wywołać. Jako parametry tej metody przekażemy pole oraz wartość setera. Zostanie ona przypisana do tej właściwości.

W konstruktorze do tej właściwości przypiszemy 0, dzięki temu po uruchomieniu to 0 będzie wyświetlane na ekranie.

public MainViewModel()
{
    ScreenVal = "0";
    Title = "Kalkulator";

    AddNumberCommand = new Command(AddNumber);
    AddOperationCommand = new Command(AddOperation);
    ClearScreenCommand = new Command(ClearScreen);
    GetResultCommand = new Command(GetResult);
}


Wiązanie danych pomiędzy Widokiem i ViewModel'em


Możemy teraz powiązać te komendy i właściwości z komponentami na naszym widoku. Przejdź proszę do MainPage.xaml. Na początek do kontrolki Entry dopisujemy właściwość Text i chcemy ją powiązać z właściwością ScreenVal z ViewModelu.

<Entry Grid.ColumnSpan="5" Text="{Binding ScreenVal}" />

Podobnie przy naszych przyciskach dodajemy wiązanie do komendy AddNumberCommand, tak samo, jak to robiliśmy w WPF'ie. Będziemy również tutaj przekazywać odpowiedni parametr, tak żebyśmy później mogli to odpowiednio obsłużyć po stronie ViewModel'u. Robimy tak dla wszystkich cyfr oraz dla przecinka.

<Button Grid.Row="1" Text="7" Command="{Binding AddNumberCommand}" CommandParameter="7" Style="{StaticResource BtnNumber}" />

<Button Grid.Row="1" Grid.Column="1" Text="8" Command="{Binding AddNumberCommand}" CommandParameter="8" Style="{StaticResource BtnNumber}" />

<Button Grid.Row="1" Text="9" Command="{Binding AddNumberCommand}" CommandParameter="9" Grid.Column="2" Style="{StaticResource BtnNumber}" />

<Button Grid.Row="2" Text="4" Command="{Binding AddNumberCommand}" CommandParameter="4" Style="{StaticResource BtnNumber}" />

<Button Grid.Row="2" Grid.Column="1" Text="5" Command="{Binding AddNumberCommand}" CommandParameter="5" Style="{StaticResource BtnNumber}" />

<Button Grid.Row="2" Text="6" Command="{Binding AddNumberCommand}" CommandParameter="6" Grid.Column="2" Style="{StaticResource BtnNumber}" />

<Button Grid.Row="3" Text="1" Command="{Binding AddNumberCommand}" CommandParameter="1" Style="{StaticResource BtnNumber}" />

<Button Grid.Row="3" Grid.Column="1" Text="2" Command="{Binding AddNumberCommand}" CommandParameter="2" Style="{StaticResource BtnNumber}" />

<Button Grid.Row="3" Grid.Column="2" Text="3" Command="{Binding AddNumberCommand}" CommandParameter="3" Style="{StaticResource BtnNumber}" />

<Button Grid.Row="4" Grid.ColumnSpan="2" Text="0" Command="{Binding AddNumberCommand}" CommandParameter="0" Style="{StaticResource BtnNumber}" />

<Button Grid.Row="4" Grid.Column="2"  Text="," Command="{Binding AddNumberCommand}" CommandParameter="," Style="{StaticResource BtnNumber}" />

Podobnie musimy zrobić z operacjami, będziemy je wiązać z komendą AddOperationCommand i przekazujemy również odpowiedni parametr.

<Button Grid.Row="1" Grid.Column="3" Text="/" Command="{Binding AddOperationCommand}" CommandParameter="/" Style="{StaticResource BtnOperation}" />

<Button Grid.Row="2" Grid.Column="3" Text="-" Command="{Binding AddOperationCommand}" CommandParameter="-" Style="{StaticResource BtnOperation}" />

<Button Grid.Row="3" Grid.Column="3" Text="*" Command="{Binding AddOperationCommand}" CommandParameter="*" Style="{StaticResource BtnOperation}" />

<Button Grid.Row="1" Grid.Column="4" Grid.RowSpan="2" Text="+" Command="{Binding AddOperationCommand}" CommandParameter="+" Style="{StaticResource BtnOperation}" />

Dla przycisku wyświetlającego wynik obliczeń będziemy wiązać z komendą GetResultCommand.

<Button Grid.Row="3" Grid.Column="4" Grid.RowSpan="2" Text="=" Command="{Binding GetResultCommand}" Style="{StaticResource BtnResult}" />

Dla przycisku Clear będzie to komenda ClearScreenCommand.

<Button Grid.Row="4" Grid.Column="3" Text="C" Command="{Binding ClearScreenCommand}" Style="{StaticResource BtnClear}" />


Implementacja metod ViewModel'u


Mamy teraz powiązane wszystkie właściwości i zdarzenia w widoku z odpowiednimi komendami i właściwościami z ViewModel'u. Pozostaje nam teraz jeszcze zaimplementować logikę naszych metod, a będzie to wyglądało prawie identycznie, jak wcześniej robiliśmy to WPF'ie.

Na początek metoda AddNumber.

private List<string> _availableOperations = new List<string> { "+", "-", "*", "/" };

private void AddNumber(object obj)
{
    var number = obj as string;

    if (ScreenVal == "0" && number != ",")
        ScreenVal = string.Empty;
    else if (number == "," && _availableOperations.Contains(ScreenVal.Substring(ScreenVal.Length - 1)))
        number = "0,";

    ScreenVal += number;
}

Do zmiennej number przypisujemy sobie przekazany parametr. Następnie sprawdzamy, czy ScreenVal jest równy 0 (czyli czy obecnie w naszej kontrolce Entry jest wyświetlane 0). Jeżeli tam jest 0, a parametr jest inny niż przecinek, to wtedy do ScreenVal przypisujemy pustego stringa (string.Empty). W przeciwnym przypadku, jeżeli parametr jest przecinkiem i ostatni wprowadzony znak jest operacją, to wpisany number poprzedzamy "0,". Czyli jeżeli na ekranie ostatnim znakiem będzie operacja, to po kliknięciu w przecinek zostanie on poprzedzony 0. W celu sprawdzenia, czy ostatni znak jest operacją, dodałem nowe pole, listę stringów z dostępnymi operacjami kalkulatora. Następnie sprawdzamy, czy ostatni znak na ekranie należy do listy operacji. Na koniec wyświetlamy na ekranie ten parametr, a właściwie doklejamy do tej wartości, która już tam wcześniej była. Podobnie jak to robiliśmy w WPF'ie, będziemy się tutaj chcieli też zabezpieczyć przed takim zabiegiem, żeby nie wprowadzać kilku operacji jedna za drugą, tylko żeby była operacja później jakaś tam liczba i znowu operacja, ale żeby nie było kilka operacji z rzędu. W tym celu dodamy sobie właściwość IsLastSignAnOperation i ustawimy ją na false wewnątrz tej metody.

public bool IsLastSignAnOperation
{
    get { return _isLastSignAnOperation; }
    set 
    { 
        SetProperty(ref _isLastSignAnOperation, value);
    }
}

private void AddNumber(object obj)
{
    var number = obj as string;

    if (ScreenVal == "0" && number != ",")
        ScreenVal = string.Empty;
    else if (number == "," && _availableOperations.Contains(ScreenVal.Substring(ScreenVal.Length - 1)))
        number = "0,";

    ScreenVal += number;

    IsLastSignAnOperation = false;
}

Następnie dodamy 2 metody, które sprawdzają, czy przycisk dodawania i wyświetlania wyniku powinien być dostępny.

private bool CanGetResult(object arg) => !IsLastSignAnOperation;

private bool CanAddOperation(object arg) => !IsLastSignAnOperation;

W obu tych metodach na podstawie wartości właściwości IsLastSignAnOperation będzie zwracany odpowiedni wynik. Potrzebujemy również odświeżać tej metody wewnątrz set'a właściwości IsLastSignAnOperation.

public bool IsLastSignAnOperation
{
    get { return _isLastSignAnOperation; }
    set
    {
        SetProperty(ref _isLastSignAnOperation, value);
        GetResultCommand.ChangeCanExecute();
        AddOperationCommand.ChangeCanExecute();
    }
}

Możemy to zrobić, po prostu wywołując metodę ChangeCanExecute na tych komendach. Żeby to zadziałało, to nie możemy operować na interfejsie ICommand, ale już na faktycznych klasach Command.

public Command AddNumberCommand { get; }
public Command AddOperationCommand { get; }
public Command ClearScreenCommand { get; }
public Command GetResultCommand { get; }

Dodatkowo musimy w konstruktorze przekazać wcześniej przygotowane metody.

public MainViewModel()
{
    ScreenVal = "0";
    Title = "Kalkulator";

    AddNumberCommand = new Command(AddNumber);
    AddOperationCommand = new Command(AddOperation, CanAddOperation);
    ClearScreenCommand = new Command(ClearScreen);
    GetResultCommand = new Command(GetResult, CanGetResult);
}

Dzięki temu, jeżeli właściwość IsLastSignAnOperation zmieni swoją wartość, to w zależności od tego, czy będzie true, czy false, to dostępność przycisku zostanie odpowiednio dostosowana. To znaczy, jeżeli IsLastSignAnOperation będzie miało wartość false, to przycisk będzie dostępny, a jeżeli tutaj będzie true, to wtedy te przyciski będą niedostępne (wynika to z tego, że w metodach mamy negację tej właściwości). Tak samo GetResultCommand, jak i AddOperationCommand.

Możemy teraz wrócić tutaj do implementacji pozostałych naszych metod. Metoda GetResult:

private DataTable _dataTable = new DataTable();

private void GetResult(object obj)
{
    var result = Math.Round(Convert.ToDouble(_dataTable.Compute(ScreenVal.Replace(",", "."), "")), 2);

    ScreenVal = result.ToString();
}

Do obliczenia wyniku skorzystamy z metody Compute obiektu DataTable. Dodamy deklarację pola _dataTable wraz z inicjalizacją. Do metody Compute przekazujemy wartość z ekranu (ScreenVal). Oprócz tego jeszcze zastąpimy przecinek kropką, tak żeby metoda Compute dobrze zinterpretowała nasze liczby. Na koniec przekonwertujemy wynik na double i zaokrąglimy do 2 miejsc po przecinku. Wynik wyświetlimy na ekranie, wystarczy do właściwości ScreenVal przypisać wartość zmiennej result.

Aby nie było problemu z interpretowaniem znaku "," oraz "." warto jeszcze ustawić dla całej aplikacji tzw. CultureInfo. Możesz to zrobić w klasie App.xaml.cs w metodzie OnStart.

protected override void OnStart()
{
    var cultureVal = "pl-PL";
    Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureVal);
    Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(cultureVal);
}

Dzięki tym ustawieniom, w naszej aplikacji między innymi zawsze będzie wyświetlany przecinek zamiast kropki w liczbach zmiennoprzecinkowych. Przejdźmy do implementacji kolejnej metody ClearScreen. Zadaniem tej metody będzie przede wszystkim wyczyszczenie ekranu oraz ustawienie właściwości IsLastSignAnOperation na false:

private void ClearScreen(object obj)
{
    ScreenVal = "0";
    IsLastSignAnOperation = false;
}

Na koniec metoda AddOperation:

private void AddOperation(object obj)
{
    var operation = obj as string;

    ScreenVal += operation;

    IsLastSignAnOperation = true;
}

Sprawdzamy na początek, jaki parametr został przekazany. Przypisujemy go do zmiennej operation. Tę operację doklejamy do wartości wyświetlanej na ekranie i ustawiamy IsLastSignAnOperation na true.


Gotowa aplikacja


To tyle. Wygląda na to, że cała logika została już zaimplementowana. Uruchom teraz proszę aplikację poprzez Ctrl+F5 i zweryfikujemy czy wszystko działa zgodnie z założeniami.

PIERWSZA APLIKACJA Mobilna XAMARIN w C# Logika Aplikacji – Uruchomienie Aplikacji

PIERWSZA APLIKACJA Mobilna XAMARIN w C# Logika Aplikacji – Obliczenia

PIERWSZA APLIKACJA Mobilna XAMARIN w C# Logika Aplikacji – Wynik


Kod całej aplikacji


MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Calculator.XamarinFormsApp.Views.MainPage"
             xmlns:vm="clr-namespace:Calculator.XamarinFormsApp.ViewModels"
             Title="{Binding Title}">

    <ContentPage.BindingContext>
        <vm:MainViewModel />
    </ContentPage.BindingContext>

    <ContentPage.Resources>
        <ResourceDictionary>

            <Style TargetType="Entry">
                <Setter Property="FontSize" Value="46" />
                <Setter Property="Margin" Value="5" />
                <Setter Property="HorizontalTextAlignment" Value="End" />
                <Setter Property="VerticalTextAlignment" Value="Center" />
                <Setter Property="IsReadOnly" Value="True" />
            </Style>

            <Style x:Key="BaseButton" TargetType="Button">
                <Setter Property="FontSize" Value="46" />
                <Setter Property="Margin" Value="5" />
            </Style>

            <Style TargetType="Button" BasedOn="{StaticResource BaseButton}">
            </Style>

            <Style x:Key="BtnNumber" TargetType="Button" BasedOn="{StaticResource BaseButton}">
                <Setter Property="Background" Value="#29AF29" />
            </Style>

            <Style x:Key="BtnOperation" TargetType="Button" BasedOn="{StaticResource BaseButton}">
                <Setter Property="Background" Value="#FF787878" />
            </Style>

            <Style x:Key="BtnClear" TargetType="Button" BasedOn="{StaticResource BaseButton}">
                <Setter Property="Background" Value="#FFE64848" />
            </Style>

            <Style x:Key="BtnResult" TargetType="Button" BasedOn="{StaticResource BaseButton}">
                <Setter Property="Background" Value="#FF0D37E3" />
            </Style>

        </ResourceDictionary>
    </ContentPage.Resources>

    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <Entry Grid.ColumnSpan="5" Text="{Binding ScreenVal}" />

        <Button Grid.Row="1" Text="7" Command="{Binding AddNumberCommand}" CommandParameter="7" Style="{StaticResource BtnNumber}" />

        <Button Grid.Row="1" Grid.Column="1" Text="8" Command="{Binding AddNumberCommand}" CommandParameter="8" Style="{StaticResource BtnNumber}" />

        <Button Grid.Row="1" Text="9" Command="{Binding AddNumberCommand}" CommandParameter="9" Grid.Column="2" Style="{StaticResource BtnNumber}" />

        <Button Grid.Row="2" Text="4" Command="{Binding AddNumberCommand}" CommandParameter="4" Style="{StaticResource BtnNumber}" />

        <Button Grid.Row="2" Grid.Column="1" Text="5" Command="{Binding AddNumberCommand}" CommandParameter="5" Style="{StaticResource BtnNumber}" />

        <Button Grid.Row="2" Text="6" Command="{Binding AddNumberCommand}" CommandParameter="6" Grid.Column="2" Style="{StaticResource BtnNumber}" />

        <Button Grid.Row="3" Text="1" Command="{Binding AddNumberCommand}" CommandParameter="1" Style="{StaticResource BtnNumber}" />

        <Button Grid.Row="3" Grid.Column="1" Text="2" Command="{Binding AddNumberCommand}" CommandParameter="2" Style="{StaticResource BtnNumber}" />

        <Button Grid.Row="3" Grid.Column="2" Text="3" Command="{Binding AddNumberCommand}" CommandParameter="3" Style="{StaticResource BtnNumber}" />

        <Button Grid.Row="4" Grid.ColumnSpan="2" Text="0" Command="{Binding AddNumberCommand}" CommandParameter="0" Style="{StaticResource BtnNumber}" />

        <Button Grid.Row="4" Grid.Column="2"  Text="," Command="{Binding AddNumberCommand}" CommandParameter="," Style="{StaticResource BtnNumber}" />

        <Button Grid.Row="1" Grid.Column="3" Text="/" Command="{Binding AddOperationCommand}" CommandParameter="/" Style="{StaticResource BtnOperation}" />

        <Button Grid.Row="2" Grid.Column="3" Text="-" Command="{Binding AddOperationCommand}" CommandParameter="-" Style="{StaticResource BtnOperation}" />

        <Button Grid.Row="3" Grid.Column="3" Text="*" Command="{Binding AddOperationCommand}" CommandParameter="*" Style="{StaticResource BtnOperation}" />

        <Button Grid.Row="4" Grid.Column="3" Text="C" Command="{Binding ClearScreenCommand}" Style="{StaticResource BtnClear}" />

        <Button Grid.Row="1" Grid.Column="4" Grid.RowSpan="2" Text="+" Command="{Binding AddOperationCommand}" CommandParameter="+" Style="{StaticResource BtnOperation}" />

        <Button Grid.Row="3" Grid.Column="4" Grid.RowSpan="2" Text="=" Command="{Binding GetResultCommand}" Style="{StaticResource BtnResult}" />

    </Grid>

</ContentPage>

MainViewModel.cs:

using System;
using System.Collections.Generic;
using System.Data;
using Xamarin.Forms;

namespace Calculator.XamarinFormsApp.ViewModels
{
    public class MainViewModel : BaseViewModel
    {
        private string _screenVal;
        private List<string> _availableOperations = new List<string> { "+", "-", "*", "/" };
        private bool _isLastSignAnOperation;
        private DataTable _dataTable = new DataTable();

        public MainViewModel()
        {
            ScreenVal = "0";
            Title = "Kalkulator";

            AddNumberCommand = new Command(AddNumber);
            AddOperationCommand = new Command(AddOperation, CanAddOperation);
            ClearScreenCommand = new Command(ClearScreen);
            GetResultCommand = new Command(GetResult, CanGetResult);
        }

        public Command AddNumberCommand { get; }
        public Command AddOperationCommand { get; }
        public Command ClearScreenCommand { get; }
        public Command GetResultCommand { get; }

        public string ScreenVal
        {
            get { return _screenVal; }
            set { SetProperty(ref _screenVal, value); }
        }

        public bool IsLastSignAnOperation
        {
            get { return _isLastSignAnOperation; }
            set
            {
                SetProperty(ref _isLastSignAnOperation, value);
                GetResultCommand.ChangeCanExecute();
                AddOperationCommand.ChangeCanExecute();
            }
        }

        private void AddNumber(object obj)
        {
            var number = obj as string;

            if (ScreenVal == "0" && number != ",")
                ScreenVal = string.Empty;
            else if (number == "," && _availableOperations.Contains(ScreenVal.Substring(ScreenVal.Length - 1)))
                number = "0,";

            ScreenVal += number;

            IsLastSignAnOperation = false;
        }

        private void GetResult(object obj)
        {
            var result = Math.Round(Convert.ToDouble(_dataTable.Compute(ScreenVal.Replace(",", "."), "")), 2);

            ScreenVal = result.ToString();
        }

        private void ClearScreen(object obj)
        {
            ScreenVal = "0";
            IsLastSignAnOperation = false;
        }

        private void AddOperation(object obj)
        {
            var operation = obj as string;

            ScreenVal += operation;

            IsLastSignAnOperation = true;
        }

        private bool CanGetResult(object arg) => !IsLastSignAnOperation;

        private bool CanAddOperation(object arg) => !IsLastSignAnOperation;
    }
}

BaseViewModel.cs:

using Calculator.XamarinFormsApp.Models;
using Calculator.XamarinFormsApp.Services;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;

namespace Calculator.XamarinFormsApp2.ViewModels
{
    public class BaseViewModel : INotifyPropertyChanged
    {
        public IDataStore<Item> DataStore => DependencyService.Get<IDataStore<Item>>();

        bool isBusy = false;
        public bool IsBusy
        {
            get { return isBusy; }
            set { SetProperty(ref isBusy, value); }
        }

        string title = string.Empty;
        public string Title
        {
            get { return title; }
            set { SetProperty(ref title, value); }
        }

        protected bool SetProperty<T>(ref T backingStore, T value,
            [CallerMemberName] string propertyName = "",
            Action onChanged = null)
        {
            if (EqualityComparer<T>.Default.Equals(backingStore, value))
                return false;

            backingStore = value;
            onChanged?.Invoke();
            OnPropertyChanged(propertyName);
            return true;
        }

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var changed = PropertyChanged;
            if (changed == null)
                return;

            changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
}

App.xaml.cs

using Calculator.XamarinFormsApp.Services;
using System.Threading;
using Xamarin.Forms;

namespace Calculator.XamarinFormsApp
{
    public partial class App : Application
    {

        public App()
        {
            InitializeComponent();

            DependencyService.Register<MockDataStore>();
            MainPage = new AppShell();
        }

        protected override void OnStart()
        {
            var cultureVal = "pl-PL";
            Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureVal);
            Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(cultureVal);
        }
    }
}


PODSUMOWANIE


Wygląda na to, że wszystko działa prawidłowo. Zauważ, że nie możesz kilka razy podrząd kliknąć operacji. Jeżeli zostanie kliknięta dowolna operacja, to wszystkie przyciski operacji są już zablokowane. Ta aplikacja jest już kompletna, możemy ją uruchamiać na symulatorze poprzez Visual Studio, ale oczywiście nic nie stoi na przeszkodzie, aby uruchomić aplikację już na swoim telefonie. Można ją opublikować na różne sposoby, ale jest to już temat na kolejne materiały.

Jeżeli taki artykuł Ci się spodobał, to koniecznie dołącz do mojej społeczności. Zapisz się na darmowy newsletter, gdzie co tydzień dzielę się wartościowymi materiałami w szczególności dotyczącymi C# i platformy .NET (darmowy zapis – newsletter).

Poprzedni artykuł - Pierwsza Aplikacja Mobilna Xamarin w C# – UI w XAML (1/2).
Następny artykuł - Programowanie a Studia. Czy Programista .NET Potrzebuje Studiów?.
Autor artykułu:
Kazimierz Szpin
Kazimierz Szpin
Programista C#/.NET. Specjalizuje się w ASP.NET Core, ASP.NET MVC, ASP.NET Web API, WPF oraz Windows Forms.
Autor bloga ModestProgrammer.pl
Dodaj komentarz
© Copyright 2021 modestprogrammer.pl. Wszelkie prawa zastrzeżone. Polityka prywatności. Design by Kazimierz Szpin