C# 如果不同的属性发生变化,则订阅属性上的不同事件

C# 如果不同的属性发生变化,则订阅属性上的不同事件,c#,wpf,inotifypropertychanged,C#,Wpf,Inotifypropertychanged,我有一个类步骤,它有一个任务集合,即列表。 步骤具有属性状态、时间。任务也具有相同的属性。当任何任务的时间或状态发生更改时,都需要更新步骤的状态和时间值。 为此,我将向Step类中的每个任务添加处理程序 private void AddHandlers() { foreach (Task tsk in Tasks) { tsk.PropertyChanged += HandleStatusChang

我有一个类步骤,它有一个任务集合,即列表。 步骤具有属性状态、时间。任务也具有相同的属性。当任何任务的时间或状态发生更改时,都需要更新步骤的状态和时间值。 为此,我将向Step类中的每个任务添加处理程序

 private void AddHandlers()
        {
            foreach (Task tsk in Tasks)
            {
                tsk.PropertyChanged += HandleStatusChanged;

                tsk.PropertyChanged += HandleTimeChanged;
            }
        }
    private void HandleStatusChanged(object sender, EventArgs e)
        {
            UpdateStepStatusFromTasks();

        }
        private void HandleTimeChanged(object sender, EventArgs e)
        {
            UpdateStepTimesFromTasks();

        }

 private void UpdateStepTimesFromTasks()
        {
        // logic for calculating Time for Step

        }

        private void UpdateStepStatusFromTasks()
        {

// logic for calculating Status for Step

        }
以下是任务中的属性已更改事件处理程序 公共事件属性更改事件处理程序属性更改

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));

    }
我的问题是,即使我只更改任务时间,它也会调用处理程序状态和时间,因为它们订阅了任务上相同的属性更改事件

如何根据从调用的属性将属性更改事件分为两部分,并确保只调用相应的处理程序,而不同时调用这两个处理程序

对不起,如果这听起来很傻,但我有点初学者WPF

问候,, P

每个事件都有添加或删除的“访问器”。类似于属性的get/set。此访问器可以向您显示事件的性质。每个事件都有一个InvocationList,它表示在引发事件时将通知的对象集合。使用此访问器,您可以更好地控制哪些内容得到通知,哪些不得到通知。当您订阅事件时,订阅的对象将被插入到调用列表中

由于您为两个事件订阅相同的对象,因此将触发它两次

您唯一能做的就是检查更新的属性的名称

public void ChangedHandler(object sender, PropertyChangedEventArgs  e)
{
    if(e.PropertyName=="Time"){//do something}
    else if (e.PropertyName == "Date") {doSomething}
}
由于您正在处理WPF,我在这里看到一个奇怪的模式。您正在从各种方法引发事件。您应该从要为其发出通知的属性引发事件,该属性绑定到控件

public class MyVM
{
    private string _status = "status1";
    public string Status
    {
        get
        {
            return _status;
        }
        set
        {
            if(_status!=value)
            {
                _status =value
                OnPropertyChanged("Status");
            }
        }
    }
}
您可以使用诸如“nameof”、基类或MethorVeawers之类的各种工具来改进这一点,例如

每个事件都有添加或删除的“访问器”。类似于属性的get/set。此访问器可以向您显示事件的性质。每个事件都有一个InvocationList,它表示在引发事件时将通知的对象集合。使用此访问器,您可以更好地控制哪些内容得到通知,哪些不得到通知。当您订阅事件时,订阅的对象将被插入到调用列表中

由于您为两个事件订阅相同的对象,因此将触发它两次

您唯一能做的就是检查更新的属性的名称

public void ChangedHandler(object sender, PropertyChangedEventArgs  e)
{
    if(e.PropertyName=="Time"){//do something}
    else if (e.PropertyName == "Date") {doSomething}
}
由于您正在处理WPF,我在这里看到一个奇怪的模式。您正在从各种方法引发事件。您应该从要为其发出通知的属性引发事件,该属性绑定到控件

public class MyVM
{
    private string _status = "status1";
    public string Status
    {
        get
        {
            return _status;
        }
        set
        {
            if(_status!=value)
            {
                _status =value
                OnPropertyChanged("Status");
            }
        }
    }
}

您可以使用诸如“nameof”、基类或MethorVeawers(如

等)之类的方法对此进行改进。您需要检查传入的参数以获取属性的名称

首先摆脱你的双重订阅

private void AddHandlers()
{
    foreach (Task tsk in Tasks)
    {
        tsk.PropertyChanged += HandlePropertyChanged;
    }
}
然后为事件使用正确的签名,以便获得正确类型的事件参数

private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
现在我们有了
PropertyChangedEventArgs
而不仅仅是
EventArgs
,我们可以检查属性并调用所需的方法

private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
    switch(e.PropertyName)
    {
        case "Status":
            UpdateStepStatusFromTasks();
            break;
        case "Time":
            UpdateStepTimesFromTasks();
            break;
     }
}
由于需要处理更多的属性,您可以将它们添加到switch语句中



注意:您可以使用a作为保存任务的集合,而不是手动订阅每个
任务,然后您可以订阅该事件,如果列表中的任何项引发属性更改,则将引发该事件(确保启用并选中等于
ListChangedType.ItemChanged
).

您需要检查传入的参数以获取属性名称

首先摆脱你的双重订阅

private void AddHandlers()
{
    foreach (Task tsk in Tasks)
    {
        tsk.PropertyChanged += HandlePropertyChanged;
    }
}
然后为事件使用正确的签名,以便获得正确类型的事件参数

private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
现在我们有了
PropertyChangedEventArgs
而不仅仅是
EventArgs
,我们可以检查属性并调用所需的方法

private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
    switch(e.PropertyName)
    {
        case "Status":
            UpdateStepStatusFromTasks();
            break;
        case "Time":
            UpdateStepTimesFromTasks();
            break;
     }
}
由于需要处理更多的属性,您可以将它们添加到switch语句中



另外,不必手动订阅每个
任务
您可以使用a作为保存任务的集合,然后您可以订阅事件,如果列表中的任何项发生了属性更改(请确保启用并选中等于
ListChangedType.ItemChanged
),则会引发该事件。

,这里很明显的一点是,您将两个处理程序附加到``事件,因此所有内容都将被处理两次。它只需要订阅一次

但是,我宁愿使用微软的反应式扩展(Rx)——NuGet“Rx Main”——来处理事件,而不是使用大量复杂的方法来处理到处都是的代码。在学习了一些基本操作符之后,它确实使处理事件变得更加容易

用过于简单的术语来说,Rx是事件的LINQ。它允许您使用查询来处理事件,而不是枚举。它创建可观察的对象

首先,我将创建这个可观察的:

var tpns = // IObservable<{anonymous}>
    from t in Tasks.ToObservable()
    from ep in Observable.FromEventPattern<
            PropertyChangedEventHandler, PropertyChangedEventArgs>(
        h => t.PropertyChanged += h,
        h => t.PropertyChanged -= h)
    select new { Task = t, ep.EventArgs.PropertyName };
这些应该很容易理解

现在订阅每个(基本上类似于附加到事件):

您会注意到每个订阅都是一个
IDisposable
。您只需在订阅上调用
.Dispose()
,所有底层事件处理程序都将为您分离,而不是使用
-=
操作符从事件中分离

现在,我建议更改
AddHandlers
方法以返回
IDisposable
。然后,调用
AddHandlers
的代码可以处理这些处理程序(如果需要的话),以确保您可以在退出之前进行清理

因此,完整的代码如下所示:

private IDisposable AddHandlers()
{
    var tpns = // IObservable<{anonymous}>
        from t in Tasks.ToObservable()
        from ep in Observable.FromEventPattern<
                PropertyChangedEventHandler, PropertyChangedEventArgs>(
            h => t.PropertyChanged += h,
            h => t.PropertyChanged -= h)
        select new { Task = t, ep.EventArgs.PropertyName };

    IObservable<Task> statusChanges =
        from tpn in tpns
        where tpn.PropertyName == "Status"
        select tpn.Task;

    IObservable<Task> timeChanges =
        from tpn in tpns
        where tpn.PropertyName == "Time"
        select tpn.Task;

    IDisposable statusSubscription =
        statusChanges
            .Subscribe(task => UpdateStepStatusFromTasks());

    IDisposable timeSubscription =
        timeChanges
            .Subscribe(task => UpdateStepTimesFromTasks());

    return new CompositeDisposable(statusSubscription, timeSubscription);
}
private IDisposable AddHandlers()
{
var tpns=//IObservable
来自t i