0

View:

Playing with a basic calculator using WPF(MVVM). I've 1 TextBox for the first num, 1 TextBox for the second num, 1 TextBlock for the results and 1 Button to execute the AddCommand and return the result. What's the right XAML syntax to bind these controls to the right Data.

Model:

public class Operation : INotifyPropertyChanged
{
    private double _result;
    public Operation()
    {
        _result = 0;
    }

    public double Result
    {
        get { return _result; }
        set
        {
            if (value != _result)
            {
                _result = value;
                RaisePropertyChanged("Result");
            }
        }
    }

    public double DoAdd(double first, double second)
    {
        _result = first + second;
        return _result;
    }
}

ViewModel:

public class CalcViewModel
{
    private Operation _operation;
    public RelayCommand AddCommand { get; set; }

    public CalcViewModel()
    {
        _operation = new Operation();

        // This is not correct, how to define the AddCommand here so it takes two params
        // The first and second nums to work with.
        AddCommand = new RelayCommand(first, second => ExecuteAddCommand(first, second));
    }

    private void ExecuteAddCommand(double first, double second)
    {

        // How to bind this returned double to the TextBlock in View
        _oepration.DoAdd(first, second);
    }
}

EDIT new version of code on request of Vlad

Model:

public class Operation
    {
        private double _result;

        public Operation()
        {
            _result = 0;
        }

        public double Result
        {
            get { return _result; }
        }

        public void PerformAdd(double leftNum, double rightNum)
        {
            _result = leftNum + rightNum;
        }
    }

ViewModel:

 public class CalcViewModel
    {
        private Operation _operation;
        public double LeftNumber { get; set; }
        public double RightNumber { get; set; }
        public double Result { get; set; }

        public RelayCommand AddCommand { get; set; }

        public CalcViewModel()
        {
            AddCommand = new RelayCommand(a => ExecuteAddCommand());
            _operation = new Operation();
        }

        private void ExecuteAddCommand()
        {
            _operation.PerformAdd(LeftNumber, RightNumber);
            Result = _operation.Result;
        }

View XAML:

<TextBox Text="{Binding LeftNumber}" />
<TextBox Text="{Binding RightNumber}" />
<TextBox Text="{Binding Result}" />
<Button Content="Add" Command="{Binding AddCommand}" />

View Code behind:

public partial class CalcUserControl : UserControl
{
    CalcViewModel vm;

    public CalcUserControl()
    {
        InitializeComponent();
        vm = new CalcViewModel();
        this.DataContext = vm;
    }
}

I tried all modes of binding without any result. I have here an additional question, what's the default binding mode in such a situation?

I even thought that it has to do with the datatype of the calculation, so I swiched from double to int, but still not working.

Stacked
  • 6,892
  • 7
  • 57
  • 73

2 Answers2

0

Well, let's see what can be done.

1) Model. The model doesn't need anything fancy. I would keep it simple and make it just return the value and not use NotifyPropertyChanged. After all, it's a model.

public class BinaryOperation
{
    double _l, _r, _result = 0.0;
    public Operation(double l, double r)
    {
        _l = l; _r = r;
    }

    public double Result
    {
        get { return _result; }
    }

    public PerformAdd()
    {
        _result = _l + _r;
    }
}

2) ViewModel. Here, your RelayCommand doesn't really need any arguments. But you need to store the values of operands in your VM, so that your view can bind to them, instead of sending them in a command. Remember, business logic doesn't belong to view, view just blindly binds to the VM! So you need 3 DPs (left addend, right addend, result) in your VM.

3) When the command arrives, you just take the addends from VM, ask the model to perform the operation, retrieve the result and assign it to your VM's result DP. (Right now, your model operations are fast, so you don't need to do it in asynchronous way. But maybe in the future...)

4) View. You need for your Window/UserControl just to bind to the VM's properties. Its going to be something as simple as:

<TextBox Text="{Binding LeftAddend}"/>
<TextBox Text="{Binding RightAddend}"/>
<TextBox Text="{Binding Result}"/>
<Button Command="{Binding AddCommand}">Add</Button>

(Don't forget to set the DataContext right.)

Edit:
the VM class has to be a dependency object! And the properties should b defined as dependency properties. Something like this:

public class CalcViewModel : DependencyObject
{
    private Operation _operation;

    public double LeftNumber
    {
        get { return (double)GetValue(LeftNumberProperty); }
        set { SetValue(LeftNumberProperty, value); }
    }

    public static readonly DependencyProperty LeftNumberProperty = 
        DependencyProperty.Register("LeftNumber", typeof(double), typeof(CalcViewModel));

    public double RightNumber
    {
        get { return (double)GetValue(RightNumberProperty); }
        set { SetValue(RightNumberProperty, value); }
    }

    public static readonly DependencyProperty RightNumberProperty = 
        DependencyProperty.Register("RightNumber", typeof(double), typeof(CalcViewModel));

    public double Result
    {
        get { return (double)GetValue(ResultProperty); }
        set { SetValue(ResultProperty, value); }
    }

    public static readonly DependencyProperty ResultProperty = 
        DependencyProperty.Register("Result", typeof(double), typeof(CalcViewModel));

    public RelayCommand AddCommand { get; set; }

    public CalcViewModel()
    {
        AddCommand = new RelayCommand(a => ExecuteAddCommand());
        _operation = new Operation();
    }

    private void ExecuteAddCommand()
    {
        _operation.PerformAdd(LeftNumber, RightNumber);
        Result = _operation.Result;
    }
}

Or, if you want to do it with INotifyPropertyChanged, and you are working with .NET 4.5

public class CalcViewModel : INotifyPropertyChanged
{
    private Operation _operation;

    public event PropertyChangedEventHandler PropertyChanged;

    void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    double _leftNumber;
    public double LeftNumber
    {
        get { return _leftNumber; }
        set
        {
            if (value == _leftNumber) return;
            _leftNumber = value;
            NotifyPropertyChanged();
        }
    }

    double _rightNumber;
    public double RightNumber
    {
        get { return _rightNumber; }
        set
        {
            if (value == _rightNumber) return;
            _rightNumber = value;
            NotifyPropertyChanged();
        }
    }

    double _result;
    public double Result
    {
        get { return _result; }
        set
        {
            if (value == _result) return;
            _result = value;
            NotifyPropertyChanged();
        }
    }

    public RelayCommand AddCommand { get; set; }

    public CalcViewModel()
    {
        AddCommand = new RelayCommand(a => ExecuteAddCommand());
        _operation = new Operation();
    }

    private void ExecuteAddCommand()
    {
        _operation.PerformAdd(LeftNumber, RightNumber);
        Result = _operation.Result;
    }
}

The same with older .NET versions:

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

    double _leftNumber;
    public double LeftNumber
    {
        get { return _leftNumber; }
        set
        {
            if (value == _leftNumber) return;
            _leftNumber = value;
            NotifyPropertyChanged("LeftNumber");
        }
    }

etc.

Vlad
  • 35,022
  • 6
  • 77
  • 199
  • Your code is not 100% correct but I fixed it, I've another problem now, I can't see the Result in my TextBox, it's bounded correctly as far as I know and I can get its value on a MessageBox, but I can't see it on my TextBox. Has this to do with de datatype of Result DP in my VM? I tried also to use a ValueConverter but still not working :( – Stacked Aug 15 '12 at 17:12
  • @Stacked: I wonder what's the problem with the code? If you tell me, I'll correct the answer so that the other readers won't need to correct it themselves. – Vlad Aug 15 '12 at 18:33
  • @Stacked: Strange, the binding should work. Are you sure the data context is right? BTW, what is the type of your Result DP? Maybe you can post the VM class. – Vlad Aug 15 '12 at 18:34
  • @Vlad Shouldn't you implement INotifyPropertyChanged so the binding will know that Result is updated? – Bob. Aug 15 '12 at 21:20
  • @Bob: The VM is a dependency object (otherwise you won't have DPs), so no need. – Vlad Aug 15 '12 at 21:28
  • @Vlad sorry, your code was correct, I was going the wrong way :) but my code is a little bit different, no need to use the 2 vars _l and _r I think, correct me if I'm wrong. – Stacked Aug 15 '12 at 22:27
  • Even if I implenment the INotifyPropertyChanged on VM the problem is still not fixed!! – Stacked Aug 15 '12 at 22:38
  • @Stacked: oh, your `CalcViewModel` should really be a `DependencyObject`, and `LeftNumber` etc dependency properties, not just properties! Alternately, you can indeed make `CalcViewModel` implement `INotifyPropertyChanged`, that must do as well. – Vlad Aug 16 '12 at 12:02
0

Thank you all and especially @Vlad. Just one tiny fault, y've declared the property Result twice on class CalcViewModel : DependencyObject.

It works now fine :)

Stacked
  • 6,892
  • 7
  • 57
  • 73