Search code examples
c#wpfmvvmicommand

Unable to use MVVM/binding correctly in the below code and having issues with Icommand property


Very new to WPF coding using MVVM. Tried making a simple calculator in WPF using MVVM. But unable to trigger the Icommand in the below code.If possible help me in this. Grateful if anybody can help me out.
View Code:

<Window x:Class="MVVMCalculator.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:MVVMCalculator"
        mc:Ignorable="d"
        Title="Calculator" Height="350" Width="300">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="85"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBox Text="{Binding Display, Mode=OneWay}" IsReadOnly="True" TextWrapping="Wrap" 
                 Grid.Row="0" Background="#E2E2E2" Margin="0,10,0,0" VerticalAlignment="Top" 
                 Height="75" Width="250" HorizontalAlignment="Center" FontSize="22" FontWeight="Bold" 
                 TextAlignment="Right">
            <TextBox.Effect>
                <DropShadowEffect/>
            </TextBox.Effect>
        </TextBox>
        <ItemsControl Grid.Row="1" ItemsSource="{Binding Buttns}" Margin="15,15,15,10">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid Columns="5" Rows="4" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button Content="{Binding Txt, Mode=TwoWay}" Command="{Binding Enter_number}"

                            FontSize="18" FontWeight="Bold" Height="50" Width="50" Background="#eef2f3" 
                            BorderBrush="Black" BorderThickness="1.0" Name="number">
                        <Button.Effect>
                            <DropShadowEffect/>
                        </Button.Effect>
                    </Button>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

ViewModel Code:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace MVVMCalculator
{
    class ViewModel : INotifyPropertyChanged
    {
        Buttons btn = new Buttons();
        private decimal operand1;
        private decimal operand2;
        private string operation;
        private decimal result;
        private string display;
        private bool newDisplayRequired = false;
        ObservableCollection<Buttons> buttns;
        public ObservableCollection<Buttons> Buttns
        {
            get { return buttns; }
            set { buttns = value; }
        }
        public decimal Result
        {
            get { return result; }
        }
        public decimal Operand1
        {
            get { return operand1; }
            set { operand1 = value; }
        }
        public decimal Operand2
        {
            get { return operand2; }
            set { operand2 = value; }
        }
        public string Operation
        {
            get { return operation; }
            set { operation = value; }
        }
        public string Display
        {
            get { return display; }
            set { display = value;
                OnPropertyChanged("Display");
            }
        }

        public ViewModel()
        {
            buttns = new ObservableCollection<Buttons>
            {
                new Buttons("1"), new Buttons("2"), new Buttons("3"),
                new Buttons("C"), new Buttons("Back"), new Buttons("4"),
                new Buttons("5"), new Buttons("6"), new Buttons("CE"),
                new Buttons("%"), new Buttons("7"), new Buttons("8"),
                new Buttons("9"), new Buttons("/"), new Buttons("*"),
                new Buttons("0"), new Buttons("."), new Buttons("+"),
                new Buttons("-"), new Buttons("=")
            };
            display = "0";
            operand1 = 0;
            operand2 = 0;
            operation = "";
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private ICommand enter_number;
        public ICommand Enter_number
        {
            get
            {
                if(enter_number==null)
                {
                    enter_number = new DelegateCommand<string>(MyAction, _canExecute);
                }
                return enter_number;
            }
        }
        private static bool _canExecute(string button)
        {
            return true;
        }
        public void MyAction(string btn)
        {
            switch(btn)
            {
                case "C":
                    display = "0";
                    operand1 = 0;
                    operand2 = 0;
                    //operation = "";
                    break;
                case ".":
                    if (!display.Contains("."))
                    {
                        Display = display + ".";
                    }
                    break;
                case "Back":
                    if (display.Length > 1)
                        Display = display.Substring(0, display.Length - 1);
                    else Display = "0";
                    break;
                default:
                    if (display == "0" || newDisplayRequired)
                        Display = btn;
                    else
                        Display = display + btn;
                    break;
            }
        }
    }
}

Buttons Class:

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

namespace MVVMCalculator
{
    class Buttons:INotifyPropertyChanged
    {
        private string txt;
        public string Txt
        {
            get { return txt; }
            set { txt = value; }
        }

        public Buttons(string a)
        {
            txt = a;
        }

        public Buttons()
        {
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

Xaml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace MVVMCalculator
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new ViewModel();
        }
    }
}

Solution

  • Since the Enter_number property is defined in the ViewModel class you need to use a {RelativeSource} to be able to bind to it:

        <Button Content="{Binding Txt, Mode=TwoWay}"
                Command="{Binding DataContext.Enter_number, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
                FontSize="18" FontWeight="Bold" Height="50" Width="50" Background="#eef2f3" 
                BorderBrush="Black" BorderThickness="1.0" Name="number">
            <Button.Effect>
                <DropShadowEffect/>
            </Button.Effect>
        </Button>
    

    The default DataContext of the Button is the current Buttons object in the ItemsSource collection of the ItemsControl and that's why your binding fails.