Aggregate data from child into parent for Binding
binding, c#, mvvm, vb.net, windows-phone-8
Solution
Since your child element is the one raising the `PropertyChangeEvent` and the count is bound to the parent property you need to subscribe to each child's `PropertyChangedEvent` within the parent. Then you need to raise your own property changed event within the parent with the PropertyName bound to the UI element.
The action you perform when the`PropertyChangedEvent` is raised is simply calling the `OnPropertyChanged()` method with the `NumberOfChildrenWithDegrees` string passed in. If you had a more complex object you may want to perform a case statement within `ChildOnPropertyChanged()` method based on the PropertyName in the event args. I left a commented example in the code. Here is the full C# code below, I didn't need to change the XAML. Note that I also changed how the list was created and added to in order to subscribe to each child's property changed event upon adding them to the list.
using System.Linq;
using Microsoft.Phone.Controls;
using System.Collections.Generic;
using System.ComponentModel;
namespace PhoneApp1
{
public partial class TestPage : PhoneApplicationPage
{
public TestPage()
{
InitializeComponent();
this.parentLB.ItemsSource = Grandparent1().ParentGroups;
}
public GrandParent Grandparent1()
{
List<Parent> ParentGroups = new List<Parent>();
ParentGroups.Add(Parent1());
ParentGroups.Add(Parent2());
GrandParent gp = new GrandParent();
gp.ParentGroups = ParentGroups;
return gp;
}
public Parent Parent2()
{
Parent p = new Parent { Name = "Tom" };
Child c1 = new Child { Name = "Tammy" };
Child c2 = new Child { Name = "Timmy" };
p.AddChild(c1);
p.AddChild(c2);
return p;
}
public Parent Parent1()
{
Parent p = new Parent { Name = "Carol" };
Child c1 = new Child { Name = "Carl" };
c1.HasHighSchoolDegree = true;
c1.HasUniversityDegree = true;
Child c2 = new Child { Name = "Karla" };
p.AddChild(c1);
p.AddChild(c2);
return p;
}
}
public class GrandParent : BindableBase
{
public List<Parent> ParentGroups { get; set; }
}
public class Parent : BindableBase
{
public string Name { get; set; }
private List<Child> _children;
public List<Child> Children
{
get { return this._children; }
set { _children = value; }
}
public Parent()
{
_children = new List<Child>();
}
public void AddChild(Child child)
{
child.PropertyChanged += ChildOnPropertyChanged;
_children.Add(child);
}
private void ChildOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
//if(propertyChangedEventArgs.PropertyName == "HasUniversityDegree");
OnPropertyChanged("NumberOfChildrenWithDegrees");
}
private int _numberOfChildrenWithDegrees;
public int NumberOfChildrenWithDegrees
{
get { return Children.Where(f => f.HasTwoDegrees).Count(); }
set { _numberOfChildrenWithDegrees = value; }
}
}
public class Child : BindableBase
{
public string Name { get; set; }
public bool HasTwoDegrees
{
get { return HasHighSchoolDegree && HasUniversityDegree; }
}
private bool _hasUniversityDegree;
public bool HasUniversityDegree
{
get { return this._hasUniversityDegree; }
set
{
_hasUniversityDegree = value;
OnPropertyChanged("HasTwoDegrees");
}
}
private bool _hasHighSchoolDegree;
public bool HasHighSchoolDegree
{
get { return this._hasHighSchoolDegree; }
set
{
_hasHighSchoolDegree = value;
OnPropertyChanged("HasTwoDegrees");
}
}
}
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, string propertyName = null)
{
if (object.Equals(storage, value))
return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged(string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Problem
This post is completely edited to provide a more solid example and simplified ask I'm looking to have the results from Child changes be reflected in the Parent, particularly the field `NumberOfChildrenWithDegrees`. If you check both boxes with bindings of `HasUniversityDegree` and `HasHighSchoolDegree` then `HasTwoDegrees` in the Child View updates. What I would like is the number of Children with `HasTwoDegrees` be reflected in the Parent VM. I thought this would just be simple, but it turns out that it doesn't work - checking two boxes changes nothing in the Parent View (the `<TextBlock Text="{Binding NumberOfChildrenWithDegrees,Mode=TwoWay,UpdateSourceTrigger=Explicit}"/>` area. How can I get this number change to happen real time? I've created a small sample for Windows Phone that can be plugged directly into a page called TestPage. Just place the XAML in the content grid and the code in the code-behind (VB). XAML: ``` <Grid x:Name="ContentPanel" Background="{StaticResource PhoneChromeBrush}" Grid.Row="1"> <ListBox x:Name="parentLB" Margin="12,12,12,12" HorizontalContentAlignment="Stretch"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Vertical" Margin="0,12,0,0"> <StackPanel Orientation="Horizontal"> <TextBlock Text="Parent Name: "/> <TextBlock Text="{Binding Name}"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="Number of Children with Degrees: "/> <TextBlock Text="{Binding NumberOfChildrenWithDegrees,Mode=TwoWay,UpdateSourceTrigger=Explicit}"/> </StackPanel> <ListBox Margin="12,0,0,0" x:Name="group2" ItemsSource="{Binding Children,Mode=TwoWay}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="Child Name: "/> <TextBlock Text="{Binding Name}"/> </StackPanel> <CheckBox Content="Has High School Degree:" IsChecked="{Binding HasHighSchoolDegree,Mode=TwoWay}"/> <CheckBox Content="Has University Degree: " IsChecked="{Binding HasUniversityDegree,Mode=TwoWay}"/> <CheckBox Content="Has Two Degrees? " IsEnabled="False" IsChecked="{Binding HasTwoDegrees}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> ``` Code-behind: ``` Imports System.Collections.ObjectModel Imports System.ComponentModel Partial Public Class TestPage Inherits PhoneApplicationPage Public Sub New() InitializeComponent() parentLB.ItemsSource = Grandparent1.ParentGroups End Sub Public Function Grandparent1() As GrandParent Dim ParentGroups As New List(Of Parent) ParentGroups.Add(Parent1) ParentGroups.Add(Parent2) Dim gp As New GrandParent gp.ParentGroups = ParentGroups Return gp End Function Public Function Parent2() As Parent Dim p As New Parent With {.Name = "Tom"} Dim c1 As New Child With {.Name = "Tammy"} Dim c2 As New Child With {.Name = "Timmy"} Dim children As New List(Of Child) children.Add(c1) children.Add(c2) p.Children = children Return p End Function Public Function Parent1() As Parent Dim p As New Parent With {.Name = "Carol"} Dim c1 As New Child With {.Name = "Carl"} c1.HasHighSchoolDegree = True c1.HasUniversityDegree = True Dim c2 As New Child With {.Name = "Karla"} Dim children As New List(Of Child) children.Add(c1) children.Add(c2) p.Children = children Return p End Function End Class Public Class GrandParent Inherits BindableBase Public Property ParentGroups As List(Of Parent) End Class Public Class Parent Inherits BindableBase Public Property Name As String Private _children As List(Of Child) Public Property Children As List(Of Child) Get Return Me._children End Get Set(value As List(Of Child)) Me.SetProperty(Me._children, value) End Set End Property Private _numberOfChildrenWithDegrees As Integer Public Property NumberOfChildrenWithDegrees As Integer Get Return Children.Where(Function(f) f.HasTwoDegrees).Count End Get Set(value As Integer) Me.SetProperty(Me._numberOfChildrenWithDegrees, value) End Set End Property End Class Public Class Child Inherits BindableBase Public Property Name As String Public ReadOnly Property HasTwoDegrees As Boolean Get Return HasHighSchoolDegree AndAlso HasUniversityDegree End Get End Property Private _hasUniversityDegree As Boolean Public Property HasUniversityDegree As Boolean Get Return Me._hasUniversityDegree End Get Set(value As Boolean) Me.SetProperty(Me._hasUniversityDegree, value) OnPropertyChanged("HasTwoDegrees") End Set End Property Private _hasHighSchoolDegree As Boolean Public Property HasHighSchoolDegree As Boolean Get Return Me._hasHighSchoolDegree End Get Set(value As Boolean) Me.SetProperty(Me._hasHighSchoolDegree, value) OnPropertyChanged("HasTwoDegrees") End Set End Property End Class Public MustInherit Class BindableBase Implements INotifyPropertyChanged Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged Protected Function SetProperty(Of T)(ByRef storage As T, value As T, Optional propertyName As String = Nothing) As Boolean If Object.Equals(storage, value) Then Return False storage = value Me.OnPropertyChanged(propertyName) Return True End Function Protected Sub OnPropertyChanged(Optional propertyName As String = Nothing) RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName)) End Sub End Class ```