I have Car.xaml:
<CheckBox Name="carA" IsChecked="{Binding CarACheck, Mode=TwoWay, FallbackValue=true}" />
Then in Car.xaml.cs:
public Car()
{
//...
DataContext = this;
}
public bool CarACheck
{
get => carA.IsChecked;
set => carA.IsChecked = value;
}
When I run it and click on CheckBox
, the app crashes with the error below:
set => carA.IsChecked = value;
System.StackOverflowException
An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
CodePudding user response:
Cause of Error
The reason is that your setter is called over and over.
- The
CheckBox
is clicked. - The
IsChecked
property of theCheckBox
is set. - The binding sets value on the bound property
CarACheck
. - The setter of
CarACheck
sets theIsChecked
property of theCheckBox
. - Go to 3.
In the long run this causes the stack to overflow and the application crashes.
An MVVM Solution
It seems that you are trying to build a view that displays data about cars. What I see from your sample is that you mix your data and most likely business logic with the user interface code. You should not do that, because it harms code quality and maintainability in the long run. A better and sustainable approach is to separate your view from data and business logic. This is what the MVVM pattern is about.
In your sample code, we have a view, which we would call CarView
. This is where the CheckBox
is defined along with the rest of the user interface to represent a car. The data is exposed to the view through a separate view model type called CarViewModel
. This view model contains properties that are bound from the view.
When using simple properties, regardless of having a backing field or not, the bindings are not able to determine changes to properties from the view model side. This is why you have to implement the INotifyPropertyChanged
interface, which provides an event for this purpose. The event is raised, whenever a property is changed. Usually this is in done in the setter.
public class CarViewModel : INotifyPropertyChanged
{
private bool _carACheck;
public bool CarACheck
{
get => _carACheck;
set
{
if (_carACheck == value)
return;
_carACheck = value;
OnPropertyChanged(nameof(_carACheck));
}
}
// ...other code, maybe even setting CarACheck.
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In the view, simply assign an instance of this view model to the DataContext
in XAML or code-behind, e.g.:
public Car()
{
// ...other code.
DataContext = new CarViewModel();
}
In your view, you do not need a name for the CheckBox
, as there is no explicit reference to it. The Mode=TwoWay
declaration can be removed, too, because the IsChecked
property binds two-way by default.
<CheckBox IsChecked="{Binding CarACheck, FallbackValue=true}"/>
Remarks about your original code: I hope that you can see the benefits of the MVVM pattern. Of course, after reviewing this solution, you could simply add a backing field and INotifyPropertyChanged
to your current code to make it work, too, but the lesson to learn here is that this separation is valuable and worth investing although it might seem more verbose. See also: