2014-02-05 1 views
5

Dieser Beitrag ist komplett eine solidere Beispiel bereitzustellen bearbeitet und vereinfacht fragenAggregatdaten von Kind in Mutter für Bindung

Ich bin auf der Suche, die Ergebnisse haben von Child Veränderungen in der Eltern berücksichtigt werden, insbesondere die Feld NumberOfChildrenWithDegrees. Wenn Sie beide Kontrollkästchen mit den Bindungen HasUniversityDegree und HasHighSchoolDegree aktivieren, wird HasTwoDegrees in den untergeordneten Ansichten aktualisiert. Was ich möchte ist die Anzahl der Kinder mit HasTwoDegrees in der Parent VM reflektiert werden.

Ich dachte, das nur einfach sein würde, aber es stellt sich heraus, dass es nicht funktioniert -. In der übergeordneten Ansicht (die <TextBlock Text="{Binding NumberOfChildrenWithDegrees,Mode=TwoWay,UpdateSourceTrigger=Explicit}"/> Bereich Überprüfung zwei Boxen ändert sich nichts Wie kann ich diese Nummer ändern erhalten Echtzeit geschehen ?

ich habe eine kleine Probe für Windows Phone erstellt, die direkt in eine Seite Testseite. legen Sie einfach die XAML in den Inhalt Gitter und den Code im Code-Behind (VB) genannt eingesteckt werden kann.

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 

Antwort

4

Da Ihr Kind-Element ist das eine das PropertyChangeEvent Anheben und die Zählung der Mutter Eigenschaft Sie PropertyChangedEvent innerhalb der Eltern jedes Kindes anmelden müssen gebunden ist. Dann müssen Sie Ihr eigenes geändertes Ereignis innerhalb des Parents mit dem an das UI-Element gebundenen PropertyName auslösen.

Die Aktion, die Sie durchführen, wenn die PropertyChangedEvent einfach angehoben wird, um die OnPropertyChanged() Methode mit dem NumberOfChildrenWithDegrees in gebenen String aufrufen. Wenn Sie eine komplexere Aufgabe hatte man auf der Grundlage der Property in der eine Fall-Anweisung innerhalb ChildOnPropertyChanged() Verfahren auszuführen möchten Ereignisargumente. Ich habe ein kommentiertes Beispiel im Code hinterlassen. Hier ist der vollständige C# -Code unten, ich musste das XAML nicht ändern. Beachten Sie, dass ich auch die Art und Weise geändert habe, in der die Liste erstellt und hinzugefügt wurde, um das Ereignis für das geänderte Verhalten jedes untergeordneten Objekts beim Hinzufügen zur Liste zu abonnieren.

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)); 
     } 
    } 

} 
} 
+0

Dank Joshua, Hinzufügen eines Ereignishandlers für jedes Kind scheint ein wenig unentgeltlich, aber wenn das ist sie müssen es sein, dann denke ich, das ist sie so ist es. Dies geht jedoch nur mit Situationen um, in denen ich eine 'GrandParent'-Klasse erstelle, d. H. Jedes" Kind "von jedem" Elternteil "zu" PropertyChanged "zu abonnieren. Wie bei meinen/den meisten Daten ist das 'GrandParent' serialisiert - wie abonniere ich die Kinder bei der Deserialisierung? Auch, ist diese Art, Dinge auf die übliche Weise zu tun? Ich dachte bei MVVM, es gäbe etwas weniger "Codey". –

+0

Dies ist die einzige Möglichkeit, die ich bisher erreichen konnte, aber es könnte bessere Wege geben. Was die Deserialisierung anbelangt, würde ich mich darauf verlassen, welcher Prozess bei der Deserialisierung verwendet wird, um eine Post-Construction-Logik auszuführen (dh - die Event-Handler anzubinden). Ich stimme nicht zu, dass dies komplex ist. – jmshapland

+0

hmmm ... Ich denke, das wird tun müssen ... –

1

Ich hoffe, das klingt nicht zu sehr wie eine Anzeige. Mit Tools von der BCL haben Sie nur die Möglichkeit genau das zu tun, was @jmshapland gesagt hat. Für dieses einfache Aggregat mag das nicht allzu schlecht sein, aber wenn die Aggregate komplexer werden, wird der Code schnell komplexer. Allerdings gibt es Tools, die Sie dabei unterstützen können, insbesondere mein eigenes Tool: NMF Expressions (Open Source)

Dieses Tool ermöglicht es Ihnen im Grunde, einen beobachtbaren Ausdruck zu erstellen und seine Updates zu hören.

public class Parent 
{ 
     private INotifyValue<bool> _allTwoDegrees; 
     public Parent() 
     { 
      _allTwoDegrees = Observable.Expression(() => Children.WithUpdates().All(child => child.HasTwoDegrees)); 
      _allTwoDegrees.ValueChanged += (o,e) => OnPropertyChanged("AllTwoDegrees"); 
     } 
     public bool AllTwoDegrees 
     { 
      get 
      { 
       return _allTwoDegrees.Value; 
      } 
     } 
     ... 
} 

Viele Aggregate werden unterstützt, einschließlich Summe, Anzahl, Durchschnitt, Min, Max, Alles und Jeder. Darüber hinaus können Sie fast alle Standardabfrageoperatoren verwenden. Diese Flexibilität hat jedoch ihren Preis, da die aktuelle Implementierung auf Reflection basiert.

Sie können die Methode "WithUpdates()" löschen, wenn Sie VM-Klassen verwenden, die INotifyEnumerable direkt implementieren, wie dies über eine dünne Ebene über ObservableCollection geschieht.

1

Ich habe versucht, Ihr Problem für eine kleine Weile zu lösen, und ich muss zugeben, dass ich mit sehr ähnlichen Lösung zu @jmshapland beendet habe - das Prinzip ist das gleiche: Abonnieren Sie das Kind PropertyChanged. In dieser Situation habe ich mich gefragt, ob ich diese Antwort schreiben soll - am Ende habe ich mich dazu entschlossen, diese Antwort aber als größeren Kommentar zu @ jmshaplands Lösung zu behandeln. Hier geht der Code:

Bindable Klasse - einfacher machen inotify umzusetzen:

public abstract class Bindable : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 
    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String property = null) 
    { 
     if (object.Equals(storage, value)) return false; 
     storage = value; 
     this.OnPropertyChanged(property); 
     return true; 
    } 

    protected void OnPropertyChanged([CallerMemberName] string property = null) 
    { 
     if (this.PropertyChanged != null) 
      this.PropertyChanged(this, new PropertyChangedEventArgs(property)); 
    } 
} 

Child Klasse - wahrscheinlich nichts Neues:

public class Child : Bindable 
{ 
    private string name; 
    public string Name 
    { 
     get { return name; } 
     set { SetProperty(ref name, value); } 
    } 

    public bool HasTwoDegrees { get { return HasHighSchoolDegree && HasUniversityDegree; } } 

    private bool hasUniversityDegree; 
    public bool HasUniversityDegree 
    { 
     get { return this.hasUniversityDegree; } 
     set 
     { 
      SetProperty(ref hasUniversityDegree, value); 
      OnPropertyChanged("HasTwoDegrees"); 
     } 
    } 

    private bool hasHighSchoolDegree; 
    public bool HasHighSchoolDegree 
    { 
     get { return this.hasHighSchoolDegree; } 
     set 
     { 
      SetProperty(ref hasHighSchoolDegree, value); 
      OnPropertyChanged("HasTwoDegrees"); 
     } 
    } 
} 

Parent Klasse - hier sind einige Verbesserungen - Ich abonniere CollectionChanged Ereignis mit einer Methode zum Hinzufügen/Entfernen von Abonnements zu hinzugefügten/entfernten Elementen. Die abonnierte Methode item_PropertyChanged überprüft, ob die Eigenschaft, die sie aufgerufen hat, die Eigenschaften von Parent aktualisieren darf. Wenn ja, dann wird OnPropertyChanged Ereignis für jeden der definierten bindingNames ausgelöst. Ich denke, dass es leichter sein wird, wenn man nur auf den Code aussehen:

public class Parent : Bindable 
{  
    private string[] bindingNames; 
    private string[] allowedProperties; 

    private string name; 
    public string Name 
    { 
     get { return name; } 
     set { SetProperty(ref name, value); } 
    } 

    private ObservableCollection<Child> children = new ObservableCollection<Child>(); 
    public ObservableCollection<Child> Children 
    { 
     get { return this.children; } 
     set { children = value; } 
    } 

    public Parent() 
    { 
     this.children.CollectionChanged += children_CollectionChanged; 
     bindingNames = new string[] { "NumberOfChildrenWithDegrees" }; 
     allowedProperties = new string[] { "HasUniversityDegree", "HasHighSchoolDegree" }; 
    } 

    private void children_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     if (e.NewItems != null) 
      foreach (Object item in e.NewItems) 
       if (item is INotifyPropertyChanged) 
        (item as INotifyPropertyChanged).PropertyChanged += item_PropertyChanged; 
     if (e.OldItems != null) 
      foreach (Object item in e.OldItems) 
       (item as INotifyPropertyChanged).PropertyChanged -= item_PropertyChanged; 
    } 

    private void item_PropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     if (bindingNames != null) 
      foreach (string item in bindingNames) 
       if (allowedProperties.Contains(e.PropertyName)) 
        OnPropertyChanged(item); 
    } 

    public int NumberOfChildrenWithDegrees 
    { 
     get { return Children.Where(f => f.HasTwoDegrees).Count(); } 
    } 
} 

Die Mainpage-Klasse: (ich nicht die XAML-Datei geändert haben)

public partial class MainPage : PhoneApplicationPage 
{ 
    ObservableCollection<Parent> parentGroups = new ObservableCollection<Parent>(); 

    public ObservableCollection<Parent> ParentGroups 
    { 
     get { return parentGroups; } 
     set { parentGroups = value; } 
    } 

    public MainPage() 
    { 
     InitializeComponent(); 
     this.parentLB.DataContext = this; 
     FillParents(); 
    } 

    public void FillParents() 
    { 
     Parent p = new Parent { Name = "Tom" }; 
     Child c1 = new Child { Name = "Tammy" }; 
     Child c2 = new Child { Name = "Timmy" }; 
     p.Children.Add(c1); 
     p.Children.Add(c2); 
     ParentGroups.Add(p); 
     p = new Parent { Name = "Carol" }; 
     c1 = new Child { Name = "Carl" }; 
     c1.HasHighSchoolDegree = true; 
     c1.HasUniversityDegree = true; 
     c2 = new Child { Name = "Karla" }; 
     p.Children.Add(c1); 
     p.Children.Add(c2); 
     ParentGroups.Add(p); 
    } 
} 

Vielleicht wird es ein wenig helfen.