I am new to WPF and am having a problem with setting up binding to a DataGrid. My issue is that I keep getting a StackOverFlowException and the debugger breaks on the set statement of the FirstName property. I have referred to the follow resources and was unable to solve my problem:
msdn databinding overview
stackoverflow-with-wpf-calendar-when-using-displaydatestart-binding
how-to-get-rid-of-stackoverflow-exception-in-datacontext-initializecomponent
Any help is greatly appreciated.
My code is:
namespace BindingTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<Person> persons = new ObservableCollection<Person>()
{
new Person(){FirstName="john", LastName="smith"},
new Person(){FirstName="foo", LastName="bar"}
};
dataGrid1.ItemsSource = persons;
}
class Person : INotifyPropertyChanged
{
public string FirstName
{
get
{
return FirstName;
}
set
{
FirstName = value;
NotifyPropertyChanged("FirstName");
}
}
public string LastName
{
get
{
return LastName;
}
set
{
LastName = value;
NotifyPropertyChanged("LastName");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
}
Note about answer
For information about the recursion with property settings for anyone else who has the same issue, pleasee see this:
Why would this simple code cause a stack overflow exception?
FirstName = value; causes recursive call of the property setter. Make something like this:
private string firstName;
public string FirstName
{
get { return firstName;}
set
{
this.firstName = value;
/*...*/
}
}
Related
I have two listboxes. One source listbox which is binded to ObservableCollection<Person> MyNetwork The other listbox is my target listbox which is binded to ObservableCollection<Person> Crew.Each time I drop the item in the target listbox I create a new instance of the sourceItem.
Now I would like to update the properties of new istances, but it doesn't seem to work. Is there a way to make the copies of the sourceItems to update when I change one of the sourceItems(FirstName) properties. I'm pretty new to WPF and MVVM and wonder if this is possible or is there a work around to achieve this?
Here what I have so far:
in the ViewModel
Source ListBox:
private ObservableCollection<Person> _myNetwork = new ObservableCollection<Person>();
public ObservableCollection<Person> MyNetwork
{
get { return _myNetwork; }
set { _myNetwork = value; RaisePropertyChanged(); }
}
Target ListBox:
private ObservableCollection<Person> _crew = new ObservableCollection<Person>();
public ObservableCollection<Person> Crew
{
get { return _crew; }
set { _crew = value; RaisePropertyChanged("Crew");}
}
void IDropTarget.Drop(IDropInfo dropInfo)
{
Person sourceItem = dropInfo.Data as Person;
if (dropInfo.Data is Person)
{
Person person = new Person(sourceItem.FirstName,
sourceItem.LastName,
sourceItem.Profession);
Crew.Add(person);
}
}
The Model:
public Person(string FirstName, string LastName, string Profession)
{
_firstName = FirstName;
_lastName = LastName;
_profession = Profession;
}
private string _firstName;
public string FirstName
{
get { return this._firstName; }
set { this._firstName = value; RaisePropertyChanged("FirstName"); }
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set { _lastName = value; RaisePropertyChanged("LastName"); }
}
private string _profession;
public string Profession
{
get { return _profession; }
set { _profession = value; RaisePropertyChanged("Profession"); }
}
I would recommend using a library that wraps the PropertyChanged event so it's easier to update your properties when you need to call them.
Once example is Caliburn for WPF. You can use NotifyOfPropertyChange(() => FirstName) from within your code to update the FirstName property however you need to (it doesn't have to just be used in the setter).
Here is a good article on how to use it.
Example:
using Caliburn.Micro;
namespace CaliburnMicroExample
{
public class ShellViewModel : PropertyChangedBase
{
private string _message;
public string Message
{
get { return _message; }
set
{
_message = value;
NotifyOfPropertyChange(() => Message);
}
}
public ShellViewModel()
{
Message = "Hello World";
}
}
}
I have an ObservableCollection<Person> in my viewmodel. This is bound as an ItemsSource to a DataGrid in the view. The class Person only has threeProperties:
public class Person : ViewModelBase
{
private Guid id;
public Guid Id
{
get { return this.id; }
set
{
this.id = value;
OnPropertyChanged("Id");
}
}
private string firstname;
public string Firstname
{
get { return this.firstname; }
set
{
this.firstname = value;
OnPropertyChanged("Firstname");
}
}
private string lastname;
public string Lastname
{
get { return this.lastname; }
set
{
this.lastname = value;
OnPropertyChanged("Lastname");
}
}
}
The class ViewModelBase implements INotifyPropertyChanged.
The items in the collection are updated perfect if I add or remove an entry in the dategrid. The item is then also removed in the collection.
My problem is that the content of an person-item is updated, but I don't know how I can react on this.
Do I have to add an event or something else to the person-class to get informed or is there another way to do this?
Implement INotifyPropertyChanged interface on your class Person so that any change in Person properties gets reflected back on UI.
Sample -
public class Person : INotifyPropertyChanged
{
private Guid id;
public Guid Id
{
get { return id; }
private set
{
if(id != value)
{
id = value;
NotifyPropertyChanged("Id");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Im new to WPF and Iv been breaking my head over this for past couple of days. I am trying to set a basic binding of textbox to a string property. I followed the MS tutorial but nothing seems to be working.
Here's email class, I am trying to bind its subject property to display in a textbox
public class Email : INotifyPropertyChanged
{
private string _subject;
public string Subject
{
get { return _subject; }
set
{
_subject = value;
OnPropertyChanged("Subject");
}
}
private string _contents;
public string Contents
{
get { return _contents; }
set
{
_contents = value;
OnPropertyChanged("Contents");
}
}
private Category _category;
public Category Category
{
get { return _category; }
set
{
_category = value;
OnPropertyChanged("Category");
}
}
public Email()
{
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
}
Here's the email setter inside UserControl that parents the textbox:
private Email _email;
public Email Email
{
get { return _email; }
set
{
_email = value;
if (_email != null)
{
Binding myBinding = new Binding("Subject");
myBinding.Source = _email;
tbSubject.SetBinding(TextBlock.TextProperty, myBinding);
}
}
}
tbSubject is never getting set to anything, its always empty even if email passed is not null and has a subject! If I do just this:
public Email Email
{
get { return _email; }
set
{
_email = value;
if (_email != null)
{
tbSubject.Text = _email.Subject;
}
}
}
it works fine. I dont understand what I am doing wrong.
I think I've got it. Here's the change I had to make:
public partial class EmailContentsTemplate : UserControl, INotifyPropertyChanged
{
private Email _email;
public Email Email
{
get { return _email; }
set
{
_email = value;
OnPropertyChanged("Email");
}
}
public EmailContentsTemplate()
{
InitializeComponent();
DataContext = this;
Binding myBinding = new Binding("Email.Subject");
myBinding.Source = this;
tbSubject.SetBinding(TextBlock.TextProperty, myBinding);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
}
So I changed source to "this" and made the user control implement INotifyPropertyChanged. Now it works.
Alternatively I got it working through XAML
<TextBox x:Name="tbSubject" Grid.Column="1" Grid.Row="1" Margin="3" Text="{Binding Email.Subject}"/>
I have a simple custom class (Person), which I want to bind to a label as a whole (not to separate properties of this class). The label should just present whatever the Person.ToString() returns (in this case FirstName + LastName).
How do I properly bind it using the person as a Source.
How do I make sure that any change in one of the properties of the Person will be reflected in the label?
public class Person : INotifyPropertyChanged {
private string firstName;
public string FirstName {
get {
return firstName;
}
set {
firstName = value;
OnPropertyChanged("FirstName");
}
}
private string lastName;
public string LastName {
get {
return lastName;
}
set {
lastName = value;
OnPropertyChanged("LastName");
}
}
public override string ToString() {
return FirstName + " " + LastName;
}
private void OnPropertyChanged(string name) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public Window1() {
myPerson = new Person() {
FirstName = "AAA",
LastName = "BBB"};
InitializeComponent();
}
public Person MyPerson {
get {
return myPerson;
}
set {
myPerson = value;
}
}
Label Content="{Binding Source=MyPerson}"
Create a new property FullName which returns the full name and raise PropertyChanged for FullName in the setters of FirstName and LastName as well. You should never bind to the object itself.
I have two classes:
public class Person
{
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
public ObservableCollection<AccountDetail> Details
{
get { return details; }
set { details = value; }
}
public ObservableCollection<AccountDetail> Phones
{
get
{
ObservableCollection<AccountDetail> phones;
phones = new ObservableCollection<AccountDetail>();
foreach (AccountDetail detail in Details)
{
if (detail.Type == DetailType.Phone)
{
phones.Add(detail);
}
}
return phones;
}
set
{
ObservableCollection<AccountDetail> phones;
phones = value;
foreach (AccountDetail detail in Details)
{
if (detail.Type == DetailType.Phone)
{
Details.Remove(detail);
}
}
foreach (AccountDetail detail in phones)
{
if (!string.IsNullOrEmpty(detail.Value))
{
Details.Add(detail);
}
}
}
}
private string firstName;
private string lastName;
private ObservableCollection<AccountDetail> details;
}
and
public class AccountDetail
{
public DetailType Type
{
get { return type; }
set { type = value; }
}
public string Value
{
get { return this.value; }
set { this.value = value; }
}
private DetailType type;
private string value;
}
In my XAML file I have a ListBox named PhonesListBox which is data bound to the phones list (a property of the Person object):
<Window.Resources>
<!-- Window controller -->
<contollers:PersonWindowController
x:Key="WindowController" />
</Window.Resources>
...
<ListBox
Name="PhonesListBox"
Margin="0,25,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
ItemsSource="{Binding Path=SelectedPerson.Phones,
Source={StaticResource ResourceKey=WindowController}}"
HorizontalContentAlignment="Stretch" />
...
In its code behind class, there's a handler for a button which adds a new item to that PhonesListBox:
private void AddPhoneButton_Click(object sender, RoutedEventArgs e)
{
ObservableCollection<AccountDetail> phones;
phones = (ObservableCollection<AccountDetail>)PhonesListBox.ItemsSource;
phones.Add(new AccountDetail(DetailType.Phone));
}
The problem is, the newly added list box item is not added in the person's details observable collection, i.e. the Phones property is not updated (set is never called). Why? Where am I making a mistake?
Thanks for all the help.
UPDATE: I changed the AddPhoneButton_Click method to:
private void AddPhoneButton_Click(object sender, RoutedEventArgs e)
{
PersonWindowController windowController;
ObservableCollection<AccountDetail> details;
windowController = (PersonWindowController)this.FindResource("WindowController");
details = windowController.SelectedPerson.Details;
details.Add(new AccountDetail(DetailType.Phone));
}
This updates the appropriate collection, which is details not Phones (as phones is just a view or a getter of a subset of detail items). Also, I realized I don't even need the Phones setter. The problem I am facing now is that my UI is not updated with the changes made to the details collection (and subsequently phones). I don't know how or where to call for the property changed as neither details nor phones are changing; their collection members are. Help. Please.
Why do you create a new ObservableCollection<AccountDetail> each time the Phones property is retrieved? Typically the Person class would have a member field of type ObservableCollection<AccountDetail> that would just be returned in the getter for the Phones property, instead of creating a new one each time. You could populate this collection when an instance of Person is constructed, for example.
I don't know if this would fix your problem or not, but it seems like it should help.
it sounds like you have an ObservableCollection<AccountDetail> with more than just phones in it, so it looks like you actually need a CollectionViewSource with a Filter added:
public class Person
{
public ObservableCollection<AccountDetail> Details { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
<Window.Resources>
<CollectionViewSource x:Key="phonesSource"
Source="{StaticResource ResourceKey=WindowController}"
Path="SelectedPerson.Details" />
</Window.Resources>
<Grid>
<ListBox
Name="PhonesListBox"
Margin="0,25,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
ItemsSource="{Binding Source={StaticResource phonesSource}}"
HorizontalContentAlignment="Stretch" />
</Grid>
public MainWindow()
{
InitializeComponent();
CollectionViewSource source =
(CollectionViewSource)FindResource("phonesSource");
source.Filter += (o, e) =>
{
if (((AccountDetail) e.Item).Type == DetailType.Phone)
e.Accepted = true;
};
}
Changing you Person class to something like following should work :
public class Person
{
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
public ObservableCollection<AccountDetail> Details
{
get { return details; }
set { details = value; }
}
public ObservableCollection<AccountDetail> Phones
{
get
{
if (phones == null)
{
phones = new ObservableCollection<AccountDetail>();
}
phones.Clear();
foreach (AccountDetail detail in Details)
{
if (detail.Type == DetailType.Phone)
{
phones.Add(detail);
}
}
return phones;
}
set
{
phones.Clear();
foreach (var item in value)
{
phones.Add(item);
}
foreach (AccountDetail detail in Details)
{
if (detail.Type == DetailType.Phone)
{
Details.Remove(detail);
}
}
foreach (AccountDetail detail in phones)
{
if (!string.IsNullOrEmpty(detail.Value))
{
Details.Add(detail);
}
}
}
}
private string firstName;
private string lastName;
private ObservableCollection<AccountDetail> details;
public ObservableCollection<AccountDetail> phones;
}
The code is not tested and it may require a few changes from you to actually work.
Try something like:
public class Person : INotifyPropertyChanged
{
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
public ObservableCollection<AccountDetail> Details
{
get { return details; }
set { details = value; }
}
public void AddDetail(AccountDetail detail) {
details.Add(detail);
OnPropertyChanged("Phones");
}
public IEnumerable<AccountDetail> Phones
{
get
{
return details.Where(d => d.Type == DetailType.Phone);
}
}
private string firstName;
private string lastName;
private ObservableCollection<AccountDetail> details;
/// <summary>
/// Called when a property changes.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region INotifyPropertyChanged Members
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
You could call the AddDetail method from you button event handler:
private void AddPhoneButton_Click(object sender, RoutedEventArgs e)
{
PersonWindowController windowController;
ObservableCollection<AccountDetail> details;
windowController = (PersonWindowController)this.FindResource("WindowController");
windowController.SelectedPerson.AddDetail(new AccountDetail(DetailType.Phone));
}
Raising the OnPropertyChanged event for the Phones property will simply cause the WPF binding framework to requery the propery, and the Linq query to be re-evaluated, after you added a Phone detail to the list of Details.