Reputation: 1618
As I'm learning ... I have created a simple data binding project which works fine with on piece of data e.g. firstName. However, when I'm trying to use lastName compilers throws a runtime error as
** Cannot evaluate expression because the current thread is in a stack overflow state.**
this is the code. As you see 2nd field (last name) is commented out since it is causing stack overflow. any comment is appreciated.
public partial class MainWindow : Window
{
Person p;
public MainWindow()
{
InitializeComponent();
p = new Person();
p.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(p_PropertyChanged);
this.DataContext = p;
p.FirstName = p.OriginalFirstName;
p.LastName = p.OriginalLastName;
}
void p_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
stat1.Text = (p.OriginalFirstName == p.FirstName) ? "Original" : "Modified";
//stat2.Text = (p.OriginalLastName == p.LastName) ? "Original" : "Modifined";
}
}
EDIT:
class Person : INotifyPropertyChanged
{
public string OriginalFirstName = "Jim";
public string OriginalLastName = "Smith";
private string _firstName;
#region FirstName
public string FirstName
{
get { return _firstName; }
set
{
if (value != null)
{
_firstName = value;
NotifyTheOtherGuy(FirstName);
}
}
}
#endregion FirstName
private string _lastName;
#region LastName
public string LastName
{
get { return _lastName; }
set
{
if (value != null)
{
_lastName = value;
NotifyTheOtherGuy(LastName);
}
}
}
#endregion LastName
public Person()
{
}
public event PropertyChangedEventHandler PropertyChanged;
void NotifyTheOtherGuy(string msg)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(msg));
}
}
}
XAML:
<Window x:Class="FullNameDataBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="First Name:"/>
<Label Grid.Row="1" Content="Last Name:"/>
<TextBox Grid.Column="1" Grid.Row="0" Background="Yellow" Margin="5" FontWeight="Bold" Text="{Binding Path=FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock x:Name="stat1" Grid.Column="2" />
<TextBox x:Name="stat2" Grid.Column="1" Grid.Row="1" Background="Yellow" Margin="5" FontWeight="Bold" Text="{Binding Path=LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Grid.Column="2" Grid.Row="1" />
</Grid>
</Window>
Upvotes: 2
Views: 28687
Reputation: 2450
I suppose p_PropertyChanged method should be static and the following changes to your method could work without problems.
static void p_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
MainWindow w = (MainWindow) sender;
w.stat1.Text = (w.p.OriginalFirstName == w.p.FirstName) ? "Original" : "Modified";
//stat2.Text = (p.OriginalLastName == p.LastName) ? "Original" : "Modifined";
}
but it would be better if you post part of the XAML code because you probably could get the same result in a cleaner way.
When working with WPF you should nearly forget some winform programming code practices. I suppose you should write your code using mainly Bindings and DependenciesProperty.
EDIT
Here follows a working sample that doesn't stop with that problem, I also wanted to show you how the WPF platform allows you to pull all the main elaboration tasks out of the View class and allowing you to move that phase in the Model class following MVVM paradigm
In the person class there's the addition of the Check properties: it evaluates the condition of your original propertychanged method that was firing the exception. Every time in the class there is a change on FirstName or LastName property if fires changed event for the change of CheckFirstName or CheckLastName. In this way you don't need to handle change events in the View class for this purpose because the evaluation of the condition is done already by this model class and the result is available and ready for a bound object.
public class Person : INotifyPropertyChanged
{
public string OriginalFirstName = "Jim";
public string OriginalLastName = "Smith";
private string _firstName;
#region FirstName
public string FirstName
{
get { return _firstName; }
set
{
if (value != null)
{
_firstName = value;
NotifyTheOtherGuy("CheckFirstName");
}
}
}
#endregion FirstName
private string _lastName;
#region LastName
public string LastName
{
get { return _lastName; }
set
{
if (value != null)
{
_lastName = value;
NotifyTheOtherGuy("CheckLastName");
}
}
}
#endregion LastName
public string CheckFirstName
{
get
{
return (FirstName==OriginalFirstName) ? "Original": "Modified";
}
}
public string CheckLastName
{
get
{
return (LastName==OriginalLastName) ? "Original": "Modified";
}
}
public Person()
{
}
public event PropertyChangedEventHandler PropertyChanged;
void NotifyTheOtherGuy(string msg)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(msg));
}
}
}
The MainWindow class: all the elaboration tasks are removed from this class and there's only the definition of a DependecyProperty for the Person object.
public partial class MainWindow : Window
{
public static readonly DependencyProperty MyPersonProperty;
static MainWindow()
{
MyPersonProperty = DependencyProperty.Register("MyPerson", typeof(Person), typeof(MainWindow));
}
Person MyPerson
{
set
{
SetValue(MyPersonProperty,value);
}
get
{
return GetValue(MyPersonProperty) as Person;
}
}
public MainWindow()
{
MyPerson = new Person();
InitializeComponent();
}
}
The MainWindow XAML: each component is bound the the Person DependencyProperty in the right way. The TextBoxes are bound to update the Person properties values and the TextBlocks are bound to fetch the result of the Check properties that (as said before) notify its changes after the other properties of the Person class are changed.
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="TryPrj.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:prj="clr-namespace:TryPrj"
Title="TryPrj"
Height="300"
Width="300"
x:Name="myWindow">
<Grid>
<Grid.RowDefinitions>
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="100" />
<ColumnDefinition
Width="100" />
<ColumnDefinition
Width="100" />
</Grid.ColumnDefinitions>
<Label
Grid.Column="0"
Grid.Row="0"
Content="First Name:" />
<Label
Grid.Row="1"
Content="Last Name:" />
<TextBox
Grid.Column="1"
Grid.Row="0"
Background="Yellow"
Margin="5"
FontWeight="Bold"
Text="{Binding Path=MyPerson.FirstName, Mode=OneWayToSource, ElementName=myWindow, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock
Grid.Column="2"
Text="{Binding Path=MyPerson.CheckFirstName, Mode=OneWay, ElementName=myWindow}"
/>
<TextBox
Grid.Column="1"
Grid.Row="1"
Background="Yellow"
Margin="5"
FontWeight="Bold"
Text="{Binding Path=MyPerson.LastName, Mode=OneWayToSource, ElementName=myWindow, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock
Grid.Column="2"
Grid.Row="1"
Text="{Binding Path=MyPerson.CheckLastName, Mode=OneWay, ElementName=myWindow}" />
</Grid>
</Window>
Upvotes: 0
Reputation: 64989
I think the error in this chunk of your XAML:
<TextBox Grid.Column="1" Grid.Row="0" Background="Yellow" Margin="5" FontWeight="Bold" Text="{Binding Path=FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock x:Name="stat1" Grid.Column="2" />
<TextBox x:Name="stat2" Grid.Column="1" Grid.Row="1" Background="Yellow" Margin="5" FontWeight="Bold" Text="{Binding Path=LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Grid.Column="2" Grid.Row="1" />
I think you want the last TextBlock
to have x:Name="stat2"
, not the TextBox
before it.
When you change the LastName
, your PropertyChanged event handler is called, which changes the text value of stat2
. Because stat2
is the TextBox
whose value is bound to LastName
using a TwoWay
binding, this causes the binding mechanism to send the value you set back to the view-model. This causes another PropertyChanged event to fire, which changes the value of stat2
, which causes another PropertyChanged event to fire.... This endless cycle doesn't stop, which is why you get the stack-overflow error.
You don't get any such stack overflow with FirstName
because stat1
is a TextBlock
with no binding on its Text
property.
Upvotes: 4
Reputation: 203841
Every time a property is changed, you change a property (the text value) which fires off another property changed event, which changes the text property, which fires off the ....
Do you see where this is going?
You either need to disable the firing of the event when you change the text property, or not change it in the context of the property changed event handler.
Since we don't have the details of your Person
class we don't know if it already support some mechanism for disabling event firing, or for changing a value without firing off events. If one doesn't exist, you may need to create your own. How you do that will depend on the implementation of that class.
Upvotes: 1