C# - 当 child 属性之一发生变化时通知 parent 属性
C# - Notifying the parent property when one of the child properties changes
我有一个 class 和一个 属性 'DesignParameters',更改后会影响另一个 属性,称为 'AxialMomentDataLists'。但是,'DesignParameters' 由一堆其他 'child' 属性组成,这些属性可通过 UI 上的数据网格访问,并且还实现了 属性 更改。如果 child 属性之一发生变化,我还希望 'DesignParameters' 自动更新,这将 in-turn 要求设置新的 '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 { }
}
}
摘自 child 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)
基本上,我有一个 UI 绑定到 FamilyMember
上的属性,并且可以更改属性 Age
和 Name
。在Family
(parent)class上,我有一个属性CumulativeFamilyAge
其中returns一个方法,基本上总结了一家人的年龄成员(在此示例中,只有 1 个家庭成员,因此它应该与 FamilyMember.Age
.
相同
当用户在 UI 上更改 FamilyMember.Age
时,我希望 Family
检测到其中一个 child 属性已更改,因此更新 CumulativeFamilyAge
相应地。
我不确定我是否理解正确。 DesignParameters
实现 INotifyPropertyChanged
并且每当它的一个属性发生变化时,您想在 Column
class 中为 AxialMomentDataLists
[=22] 调用 PropertyChanged
=]?
如果是这样,这很容易。每当您为 DesParameters
属性 设置新值时,只需订阅此事件。不要忘记取消订阅旧值的事件。空检查可能是必要的(或者您是否将 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.
我会按照我的理解来写的。
在列 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);
}
}
附加问题后的示例实现:
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)
使用通用 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;
}
}
}
族派生自 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 中有一个带有 link 的字段到 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 并为两个实体设置了输出。
<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>
我有一个 class 和一个 属性 'DesignParameters',更改后会影响另一个 属性,称为 'AxialMomentDataLists'。但是,'DesignParameters' 由一堆其他 'child' 属性组成,这些属性可通过 UI 上的数据网格访问,并且还实现了 属性 更改。如果 child 属性之一发生变化,我还希望 'DesignParameters' 自动更新,这将 in-turn 要求设置新的 '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 { }
}
}
摘自 child 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)
基本上,我有一个 UI 绑定到 FamilyMember
上的属性,并且可以更改属性 Age
和 Name
。在Family
(parent)class上,我有一个属性CumulativeFamilyAge
其中returns一个方法,基本上总结了一家人的年龄成员(在此示例中,只有 1 个家庭成员,因此它应该与 FamilyMember.Age
.
当用户在 UI 上更改 FamilyMember.Age
时,我希望 Family
检测到其中一个 child 属性已更改,因此更新 CumulativeFamilyAge
相应地。
我不确定我是否理解正确。 DesignParameters
实现 INotifyPropertyChanged
并且每当它的一个属性发生变化时,您想在 Column
class 中为 AxialMomentDataLists
[=22] 调用 PropertyChanged
=]?
如果是这样,这很容易。每当您为 DesParameters
属性 设置新值时,只需订阅此事件。不要忘记取消订阅旧值的事件。空检查可能是必要的(或者您是否将 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.
我会按照我的理解来写的。
在列 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);
}
}
附加问题后的示例实现:
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)
使用通用 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;
}
}
}
族派生自 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 中有一个带有 link 的字段到 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 并为两个实体设置了输出。
<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>