I try to grasp the concept of MVVM using WPF, but I am still struggling. To show my problem, I made a very simple example where data binding does not work:
I have a UI with one Button and one TextBlock object. The TextBlock shows 0 at the beginning. Whenever the button is clicked, the value should increase by one.
This is my UI (MainWindow.xaml):
<Window x:Class="CounterTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:counter="clr-namespace:CounterTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<counter:CounterViewModel x:Key="counterviewobj"/>
</Window.Resources>
<Grid>
<TextBlock HorizontalAlignment="Left" Margin="303,110,0,0" Text="{Binding CounterString, Mode=OneWay, Source={StaticResource counterviewobj}}" TextWrapping="Wrap" VerticalAlignment="Top"/>
<Button Command="{Binding IncCommand, Mode=OneWay, Source={StaticResource counterviewobj}}" Content="+1" HorizontalAlignment="Left" Margin="303,151,0,0" VerticalAlignment="Top" Width="37"/>
</Grid>
</Window>
I kept the code behind as clean as possible (MainWindow.xaml.cs):
using System.Windows;
namespace CounterTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
A Counter class is defined to do the business logic (Counter.cs).
namespace CounterTest
{
public class Counter
{
public int Cval { get; set; } // This is the value to increment.
public Counter() { Cval = 0; }
public void Increment() { Cval++; }
public bool CanIncrement() { return true; } // Only needed to conform to ICommand interface.
}
}
I use a lightweight ICommand implementation (RelayCommand.cs):
using System;
using System.Windows.Input;
namespace CounterTest
{
public class RelayCommand : ICommand
{
private Action WhattoExecute;
private Func<bool> WhentoExecute;
public RelayCommand(Action What, Func<bool> When)
{
WhattoExecute = What;
WhentoExecute = When;
}
public bool CanExecute(object parameter)
{
return WhentoExecute();
}
public void Execute(object parameter)
{
WhattoExecute();
}
public event EventHandler CanExecuteChanged;
}
}
And finally the ViewModel (CounterViewModel.cs):
namespace CounterTest
{
public class CounterViewModel
{
private Counter myCounter = new Counter();
private RelayCommand _IncCommand;
public RelayCommand IncCommand
{
get { return _IncCommand; }
}
public string CounterString
{
get { return myCounter.Cval.ToString(); }
}
public CounterViewModel()
{
_IncCommand = new RelayCommand(myCounter.Increment, myCounter.CanIncrement);
}
}
}
Stepping through my code, I see that the command is executed when I press the button, and Cval is increased by one each time. But obviously I don't do the data binding correctly.
What did I try? I see two possible circumventions. The first is to bind the TextBlock directly to the Cval value of the Counter class. But this would violate all principles of MVVM.
The other one is to implement INotifyPropertyChanged, bind the UpdateSourceTrigger to a PropertyChanged event from CounterString, and implement an update event for Cval which the setter from CounterString can subscribe to. But that is extremely cumbersome, and it also makes the OneWay binding in the XAML file moot.
There must be a better way. What do I miss? How can I update the TextBlock when Cval is changed and stick to the MVVM principles?
INotifyPropertyChanged
interface in your ViewModel and push the notification to your binding right after your Counter model changes – Mikantprivate Counter myCounter = new Counter();
withpublic Counter myCounter { get; set; } = new Counter();
- please notice the difference – MikantCounterString
property for the binding please take a closer look at my first comment: you should callRaisePropertyChanged("CounterString");
in your VM right afterRaisePropertyChanged("CVal");
in your M – Mikant