簡體   English   中英

將子數據匯總到父級進行綁定

[英]Aggregate data from child into parent for Binding

此帖子已完全編輯,以提供更加可靠的示例和簡化的問題

我希望將Child更改的結果反映在Parent中,尤其是NumberOfChildrenWithDegrees字段。 如果您選中具有HasUniversityDegreeHasHighSchoolDegree綁定的兩個框,則子視圖中的HasTwoDegrees更新。 我想要的是HasTwoDegrees的子女HasTwoDegrees會反映在父虛擬機中。

我認為這只是簡單,但事實證明它不起作用 - 檢查兩個框在父視圖中沒有任何改變( <TextBlock Text="{Binding NumberOfChildrenWithDegrees,Mode=TwoWay,UpdateSourceTrigger=Explicit}"/> 我怎樣才能讓這個數字變化實時發生?

我已經為Windows Phone創建了一個小樣本,可以直接插入名為TestPage的頁面。 只需將XAML放在內容網格中,然后將代碼放在代碼隱藏(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>

代碼隱藏:

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

由於您的子元素是引發PropertyChangeEvent並且count綁定到父屬性的元素,因此您需要在父元素中訂閱每個子元素的PropertyChangedEvent 然后,您需要在父元素中引發自己的屬性更改事件,並將PropertyName綁定到UI元素。

該行動在執行時PropertyChangedEvent升高是簡單地調用OnPropertyChanged()與方法NumberOfChildrenWithDegrees中傳遞的字符串。如果你有一個更復雜的對象,你可能要執行中的case語句ChildOnPropertyChanged()基礎上,屬性名的方法事件args。 我在代碼中留下了一個注釋示例。 下面是完整的C#代碼,我不需要更改XAML。 請注意,我還更改了列表的創建方式和添加方式,以便在將每個子項的屬性更改事件添加到列表后進行訂閱。

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

}
}

我希望這聽起來不像廣告。 使用BCL的工具,您只有機會完成@jmshapland所說的內容。 對於這個簡單的聚合,這可能不是太糟糕,但如果聚合變得更復雜,代碼會變得更加復雜。 但是,有一些工具可以為您提供支持,尤其是我自己的工具: NMF Expressions (開源)

此工具基本上允許您創建可觀察的表達式並監聽其更新。

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

支持許多聚合,包括Sum,Count,Average,Min,Max,All和Any。 此外,您幾乎可以使用所有標准查詢運算符。 然而,這種靈活性是有代價的,因為目前的實施基於反射。

如果使用直接實現INotifyEnumerable的VM類,則可以刪除“WithUpdates()”方法,如ObservableCollection頂部的薄層所做。

我一直試圖解決你的問題一段時間,我必須承認,我已經結束了與@jmshapland非常相似的解決方案 - 原理是一樣的:訂閱child的PropertyChanged 在這種情況下,我想知道是否寫這個答案 - 最后我決定這樣做,但把這個答案視為對@ jmshapland解決方案的更大評論。 這里是代碼:

Bindable類 - 更容易實現INotify:

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班 - 可能不是什么新鮮事:

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類 - 這里有一些改進 - 我使用一種方法訂閱CollectionChanged事件,該方法添加/刪除對添加/刪除項的訂閱。 訂閱方法item_PropertyChanged檢查是否允許調用它的屬性更新Parent的屬性。 如果是,則為每個已定義的bindingNames引發OnPropertyChanged事件。 我認為如果你只看代碼會更容易:

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

MainPage類:(我沒有更改xaml文件)

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

也許它會有所幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM