繁体   English   中英

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

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

我有一个 class,其属性为“DesignParameters”,更改后会影响另一个名为“AxialMomentDataLists”的属性。 但是,“DesignParameters”由一堆其他“子”属性组成,这些属性可以通过 UI 上的数据网格访问,并且还实现了属性更改。 如果其中一个子属性发生变化,我还希望“DesignParameters”自动更新,这反过来又需要设置新的“AxialMomentDataLists”。 有人对实现这一目标的最佳方法有建议吗?

    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 { }
        }
}

摘自子 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));
                
            }
        }
}

编辑

我创建了一段更简单的代码,试图完成与我在这里大致相同的事情( https://github.com/dtnaughton/SampleApp

基本上,我有一个绑定到FamilyMember属性的 UI,并且属性AgeName可以更改。 Family (父母)class 上,我有一个属性CumulativeFamilyAge ,它返回一个基本上总结家庭成员年龄的方法(在这个例子中,只有 1 个家庭成员,因此它应该与FamilyMember.Age读取相同.

当用户在 UI 上更改FamilyMember.Age时,我希望Family检测到其中一个子属性已更改,因此相应地更新CumulativeFamilyAge

我不确定我是否理解正确。 DesignParameters实现INotifyPropertyChanged并且每当其属性之一发生更改时,您想在您的 class Column中为AxialMomentDataLists属性调用PropertyChanged

如果是这样,这很容易。 每当您为DesParameters属性设置新值时,只需订阅此事件即可。 不要忘记取消订阅旧值的事件。 可能需要进行 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 { }
    }
}

例如,如果“NumberOfXBars”属性发生更改(它显示在 UI 上),那么我需要 class 列上的父属性“DesignParameters”来识别其中一个子属性已更改,因此必须更新。

我会按照我的理解写。

在 class 列中,您有一个包含DesignParameters DesParameters
如果此实例的某个属性 (NumberOfXBars) 的值已更改,则您希望更新与此实例关联的所有视图。

在这里,您有一个明显的问题,即违反 INotifyPropertyChanged 接口的合同。 根据此合同,PropertyChanged 事件不会在将值分配给属性时触发,但仅当该值与前一个值不同时才会触发。
出于这个原因,如果您只是为相同的值引发此事件,那么绑定将不会对此做出反应。

正如我认为的那样,您根本没有正确思考。
您无需更新与实例的绑定。
您需要更新对其任何属性的任何绑定。
为此,接口合约提供了创建带有空参数的事件。

如果以上是对你的问题的正确理解,那么试试这个实现:

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

附加问题后的示例实现:

我创建了一段更简单的代码,试图完成与我在这里大致相同的事情( https://github.com/dtnaughton/SampleApp

使用通用 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 派生自 ViewModelBase 以便能够观察事件属性的变化。
这样做是为了演示如何观察属性中实体的变化。
还添加了第二个 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;
        }
    }
}

Window 的数据上下文最好在 XAML 中定义 - 这使得 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;
        }
    }
}

在 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