Reputation: 10266
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.
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.
Upvotes: 0
Views: 3331
Reputation: 6361
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.
Upvotes: 0
Reputation: 20471
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;
};
}
Upvotes: 1
Reputation: 30840
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.
Upvotes: 0
Reputation: 30418
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.
Upvotes: 2