简体   繁体   English

C# - 当子属性之一发生更改时通知父属性

[英]C# - Notifying the parent property when one of the child properties changes

I have a class with a property 'DesignParameters' that upon changing, would affect another property called 'AxialMomentDataLists'.我有一个 class,其属性为“DesignParameters”,更改后会影响另一个名为“AxialMomentDataLists”的属性。 However, the 'DesignParameters' is comprised of a bunch of other 'child' properties that are accessible through a datagrid on the UI and also implement property changed.但是,“DesignParameters”由一堆其他“子”属性组成,这些属性可以通过 UI 上的数据网格访问,并且还实现了属性更改。 If one of the child properties changes, I also want 'DesignParameters' to automatically update, which would in-turn call for a new 'AxialMomentDataLists' to be set.如果其中一个子属性发生变化,我还希望“DesignParameters”自动更新,这反过来又需要设置新的“AxialMomentDataLists”。 Does anyone have advice on the best method to achieve this?有人对实现这一目标的最佳方法有建议吗?

    public class Column : ObservableObject
    {
        private double length;
        private DesignParameters desParameters;

        public DesignParameters DesParameters
        {
            get { return desParameters; }
            set
            {
                desParameters = value;
                RaisePropertyChanged(nameof(DesParameters));
                RaisePropertyChanged(nameof(AxialMomentDataLists));
            }
        }
        public List<AxialMomentDataSet> AxialMomentDataLists
        {
            get { return CalculateAxialMomentLists(ColumnForce, DesParameters); }
            set { }
        }
}

Excerpt from child class:摘自子 class:

public class DesignParameters : ObservableObject
    {
        #region Private variables

        private double phiC;
        private double phiS;
        private int cover;
        private int reinforcementYieldStrength;
        private int concreteStrength;
        private double b;
        private double h;
        private LiveLoadReductionType liveLoadReduction;
        private StirrupType stirrupBar;
        private int numberOfXBars;
        private int numberOfYBars;
        private BarDiameterType longitudinalBarDiameter;
        private double longitudinalReinforcementPercentage;
        List<Bar> rebar;

        #endregion



        public int NumberOfXBars
        {
            get { return numberOfXBars; }
            set
            {
                numberOfXBars = PropertyMethods.SetNumberOfBars("x", value, B, H, Cover, LongitudinalBarDiameter);
                RaisePropertyChanged(nameof(NumberOfXBars));
                RaisePropertyChanged(nameof(Rebar));
                RaisePropertyChanged(nameof(LongitudinalReinforcementPercentage));
                RaisePropertyChanged(nameof(AxialResistance));
                
            }
        }
}

EDIT编辑

I have created a more simple piece of code that tries to accomplish approximately the same thing that I am here ( https://github.com/dtnaughton/SampleApp )我创建了一段更简单的代码,试图完成与我在这里大致相同的事情( https://github.com/dtnaughton/SampleApp

Basically, I had a UI that binds to properties on the FamilyMember and the properties Age and Name can be changed.基本上,我有一个绑定到FamilyMember属性的 UI,并且属性AgeName可以更改。 On the Family (parent) class, I have a property CumulativeFamilyAge which returns a method that basically sums up the age of the family member (in this example, there is only 1 family member and therefore it should read the same as the FamilyMember.Age .Family (父母)class 上,我有一个属性CumulativeFamilyAge ,它返回一个基本上总结家庭成员年龄的方法(在这个例子中,只有 1 个家庭成员,因此它应该与FamilyMember.Age读取相同.

When the user changes FamilyMember.Age on the UI, I want Family to detect that one of it's child properties has changed, and therefore update CumulativeFamilyAge accordingly.当用户在 UI 上更改FamilyMember.Age时,我希望Family检测到其中一个子属性已更改,因此相应地更新CumulativeFamilyAge

I am not sure if I unterstood you correctly.我不确定我是否理解正确。 DesignParameters implements INotifyPropertyChanged and whenever one of its properties changes, you want to invoke PropertyChanged in your Column class for the AxialMomentDataLists property? DesignParameters实现INotifyPropertyChanged并且每当其属性之一发生更改时,您想在您的 class Column中为AxialMomentDataLists属性调用PropertyChanged

If so, this is quite easy.如果是这样,这很容易。 Just subscribe to this event whenever you set a new value to your DesParameters property.每当您为DesParameters属性设置新值时,只需订阅此事件即可。 Don't forget to unsubscribe the event from the old value.不要忘记取消订阅旧值的事件。 A null check might be necessary (or do you use C# 8 with nullable reference types?)可能需要进行 null 检查(或者您是否将 C# 8 与可为空的引用类型一起使用?)

public class Column : ObservableObject
{
    private double length;
    private DesignParameters desParameters;

    public DesignParameters DesParameters
    {
        get { return desParameters; }
        set
        {
            if(desParameters != null)
            {
                desParameters.PropertyChanged -= DesParametersChildPropertyChanged;
            }
            desParameters = value;
            desParameters.PropertyChanged += DesParametersChildPropertyChanged;
            RaisePropertyChanged(nameof(DesParameters));
            RaisePropertyChanged(nameof(AxialMomentDataLists));
        }
    }

    private void DesParametersChildPropertyChanged(object sender, PropertyChangeEventArgs args)
    {
         RaisePropertyChanged(nameof(DesParameters));
         RaisePropertyChanged(nameof(AxialMomentDataLists));
    }

    public List<AxialMomentDataSet> AxialMomentDataLists
   {
        get { return CalculateAxialMomentLists(ColumnForce, DesParameters); }
        set { }
    }
}

For example, if the 'NumberOfXBars' property gets changed (it is exposed on the UI) then I need the parent property 'DesignParameters' on the Column class to recognize one of it's child properties has changed and therefore this must update.例如,如果“NumberOfXBars”属性发生更改(它显示在 UI 上),那么我需要 class 列上的父属性“DesignParameters”来识别其中一个子属性已更改,因此必须更新。

I'll write as I understand it.我会按照我的理解写。

In the Column class, you have a DesParameters property that contains an instance of DesignParameters .在 class 列中,您有一个包含DesignParameters DesParameters
If the value of one of the properties (NumberOfXBars) of this instance has changed, then you want to update all views associated with this instance.如果此实例的某个属性 (NumberOfXBars) 的值已更改,则您希望更新与此实例关联的所有视图。

Here you have an obvious problem with breaking the contract of the INotifyPropertyChanged interface.在这里,您有一个明显的问题,即违反 INotifyPropertyChanged 接口的合同。 According to this contract, the PropertyChanged event does not fire when a value is assigned to a property, but only if this value is different from the previous one.根据此合同,PropertyChanged 事件不会在将值分配给属性时触发,但仅当该值与前一个值不同时才会触发。
For this reason, if you just raise this event for the same value, then the bindings will not react to this.出于这个原因,如果您只是为相同的值引发此事件,那么绑定将不会对此做出反应。

As I think, you are simply not thinking correctly.正如我认为的那样,您根本没有正确思考。
You don't need to update the bindings to the instance.您无需更新与实例的绑定。
You need to update any bindings to any of its properties.您需要更新对其任何属性的任何绑定。
For this, the interface contract provides for the creation of an event with an empty argument.为此,接口合约提供了创建带有空参数的事件。

If the above is a correct understanding of your problem, then try this implementation:如果以上是对你的问题的正确理解,那么试试这个实现:

    public int NumberOfXBars
    {
        get { return numberOfXBars; }
        set
        {
            numberOfXBars = PropertyMethods.SetNumberOfBars("x", value, B, H, Cover, LongitudinalBarDiameter);
            RaisePropertyChanged(string.Empty);
        }
    }

Example implementation after additional question:附加问题后的示例实现:

I have created a more simple piece of code that tries to accomplish approximately the same thing that I am here ( https://github.com/dtnaughton/SampleApp )我创建了一段更简单的代码,试图完成与我在这里大致相同的事情( https://github.com/dtnaughton/SampleApp

Improved implementation of Person entity using generic MVVMLight methods.使用通用 MVVMLight 方法改进了 Person 实体的实现。

using GalaSoft.MvvmLight;

namespace SampleApp.Models
{
    public class Person : ObservableObject
    {
        private string _name;
        private int _age;
        public string Name { get => _name; set => Set(ref _name, value); }
        public int Age { get => _age; set => Set(ref _age, value); }

        public Person()
        {
            Name = "TestName";
            Age = 30;
        }
    }
}

Family is derived from ViewModelBase to be able to watch event properties change. Family 派生自 ViewModelBase 以便能够观察事件属性的变化。
This is done to demonstrate how to observe changes in entities in properties.这样做是为了演示如何观察属性中实体的变化。
Also added a second Person entity to improve the demo example.还添加了第二个 Person 实体以改进演示示例。

using GalaSoft.MvvmLight;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace SampleApp.Models
{
    public class Family : ViewModelBase
    {
        private Person _familyMember;
        private int _cumulativeFamilyAge;
        private Person _childMember;

        public Person FamilyMember { get => _familyMember; set => Set(ref _familyMember, value); }
        public Person ChildMember { get => _childMember; set => Set(ref _childMember, value); }
        public int CumulativeFamilyAge { get => _cumulativeFamilyAge; private set => Set(ref _cumulativeFamilyAge, value); }

        public Family()
        {
            FamilyMember = new Person() { Name = "Father" };
            ChildMember = new Person() { Name = "Son", Age = 7 };
        }


        public override void RaisePropertyChanged<T>([CallerMemberName] string propertyName = null, T oldValue = default, T newValue = default, bool broadcast = false)
        {
            base.RaisePropertyChanged(propertyName, oldValue, newValue, broadcast);

            if (propertyName == nameof(FamilyMember) || propertyName == nameof(ChildMember))
            {
                if (oldValue is Person oldPerson)
                    oldPerson.PropertyChanged -= OnPersonPropertyChanged;
                if (newValue is Person newPerson)
                    newPerson.PropertyChanged += OnPersonPropertyChanged;

                CalculateCumulativeFamilyAge();
            }

        }

        private void OnPersonPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            CalculateCumulativeFamilyAge();
        }

        public void CalculateCumulativeFamilyAge()
        {
            CumulativeFamilyAge = (FamilyMember?.Age ?? 0)
                                + (ChildMember?.Age ?? 0);
            return;
        }
    }
}

The Data Context of the Window is best defined in XAML - this makes XAML design much easier. Window 的数据上下文最好在 XAML 中定义 - 这使得 XAML 设计更加容易。
If you need to have a field in Code Behind with a link to the ViewModel, you should get it from XAML.如果您需要在 Code Behind 中有一个带有 ViewModel 链接的字段,您应该从 XAML 获取它。

using SampleApp.ViewModels;
using System.Windows;

namespace SampleApp
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly MainViewModel viewModel /*= new MainViewModel()*/;

        public MainWindow()
        {
            InitializeComponent();
            //DataContext = viewModel;
            viewModel = (MainViewModel)DataContext;
        }
    }
}

Added a DataTemplate for the Person entity in the Window and set the output for both entities.在 Window 中为 Person 实体添加了 DataTemplate,并为两个实体设置了 output。

<Window x:Class="SampleApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:viewmodels="clr-namespace:SampleApp.ViewModels"
        xmlns:local="clr-namespace:SampleApp.Models"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <viewmodels:MainViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:Person}">
            <Border Margin="5" Background="LightSkyBlue" Padding="5">
                <StackPanel>
                    <TextBox Text="{Binding Name}" Height="30"/>
                    <TextBox Text="{Binding Age}" Height="30"/>
                </StackPanel>
            </Border>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <StackPanel Grid.Column="0">
            <ContentPresenter Content="{Binding FamilyModel.FamilyMember}"/>
            <ContentPresenter Content="{Binding FamilyModel.ChildMember}"/>
            <TextBlock Text="{Binding FamilyModel.CumulativeFamilyAge}" Height="30"/>
        </StackPanel>

    </Grid>
</Window>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM