【问题标题】:Aggregate data from child into parent for Binding将数据从子级聚合到父级以进行绑定
【发布时间】:2014-03-02 03:51:21
【问题描述】:

这篇文章经过完全编辑,以提供更可靠的示例和简化的问题

我希望将 Child 更改的结果反映在 Parent 中,尤其是字段 NumberOfChildrenWithDegrees。如果您选中绑定HasUniversityDegreeHasHighSchoolDegree 的两个框,则子视图中的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

【问题讨论】:

    标签: c# vb.net mvvm windows-phone-8 binding


    【解决方案1】:

    我已经尝试解决您的问题一段时间了,我必须承认,我以与@jmshapland 非常相似的解决方案结束 - 原理是相同的:订阅孩子的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 检查调用它的属性是否允许更新父属性。如果是这样,则为每个定义的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);
        }
    }
    

    也许会有所帮助。

    【讨论】:

      【解决方案2】:

      我希望这听起来不太像广告。使用 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 顶部的薄层一样。

      【讨论】:

        【解决方案3】:

        由于您的子元素是提高PropertyChangeEvent 的元素,并且计数绑定到父属性,因此您需要订阅父元素中每个子元素的PropertyChangedEvent。然后,您需要在父级中引发您自己的属性更改事件,并将 PropertyName 绑定到 UI 元素。

        在引发PropertyChangedEvent 时执行的操作只是调用OnPropertyChanged() 方法并传入NumberOfChildrenWithDegrees 字符串。如果您有更复杂的对象,您可能希望在ChildOnPropertyChanged() 中执行case 语句基于事件参数中的 PropertyName 的方法。我在代码中留下了一个注释示例。下面是完整的 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));
                }
            }
        
        }
        }
        

        【讨论】:

        • 感谢 Joshua,为每个孩子添加一个事件处理程序似乎有点无缘无故,但如果这是他们必须的方式,那么我想这就是他们的方式。但是,这只处理我创建GrandParent 类的情况,即订阅每个Parent 的每个ChildPropertyChanged。与我/大多数数据的情况一样,GrandParent 是序列化的 - 我如何在反序列化时为孩子订阅?另外,这种做事方式是标准方式吗?我认为使用 MVVM 会少一些“codey”。
        • 这是迄今为止我能够完成的唯一方法,但可能还有更好的方法。至于反序列化,我将依赖任何对其进行反序列化的过程来运行一些后期构造逻辑(即 - 连接事件处理程序)。我不同意这很复杂。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-26
        • 1970-01-01
        • 1970-01-01
        • 2017-08-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多