amit kohan
amit kohan

Reputation: 1618

Cannot evaluate expression because the current thread is in a stack overflow state

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

Answers (3)

Alessandro Rossi
Alessandro Rossi

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

  1. As someone else said you probably assigned the stat1 name to the wrong object and this started the infinite recursion that thrown the StackOverflowException.
  2. In the Person class, PropertyChanged must be called with the name of the property not with its value.

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

Luke Woodward
Luke Woodward

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

Servy
Servy

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

Related Questions