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

subscribing to different events on Property changed if different properties

我有 class Step,其中包含 Task 的集合,即 List 。 步骤具有属性 Status 和 Time 。任务也具有相同的属性。每当任何任务的时间或状态发生更改时,都需要更新步骤的状态和时间的值。 为此,我在步骤 class.

中为每个任务添加处理程序
 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

        }

这是任务中 属性 更改的事件处理程序 public 事件 属性ChangedEventHandler 属性 已更改;

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

    }

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

我如何根据从 属性 调用的 属性 更改的事件进行分叉,并确保只调用各自的处理程序而不是同时调用两者?

对不起,如果这听起来很傻,但我是 WPF 的初学者。

此致, P

每个事件都有 "accessors" 添加或删除。类似 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"、baseClasses 或 FODY

等 MethorVeawers 等各种方法对此进行改进

您需要检查传入的 args 参数以获取 属性.

的名称

首先取消你的双重订阅。

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

然后为您的事件使用正确的签名,以便获得正确类型的事件参数。

private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{

现在我们有了 PropertyChangedEventArgs,而不仅仅是 EventArgs,我们可以检查 PropertyName 属性 并调用所需的方法。

private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
    switch(e.PropertyName)
    {
        case "Status":
            UpdateStepStatusFromTasks();
            break;
        case "Time":
            UpdateStepTimesFromTasks();
            break;
     }
}

如果您需要处理更多属性,您可以将它们添加到 switch 语句中。


P.S。您可以使用 BindingList<Task> as the collection that holds the tasks, you can then subscribe to the ListChanged event, that event will be raised if any of the items in the list raise PropertyChanged (be sure to enable RaiseListChangedEvents and check ListChangedEventArgs.ListChangedType 等于 ListChangedType.ItemChanged).

,而不是手动订阅每个 Task

所以,这里显而易见的是,您将两个处理程序附加到 `` 事件,因此所有内容都被处理了两次。只需订阅一次。

但是,与其编写大量复杂的代码到处乱跳的方法,我更喜欢使用 Microsoft 的 Reactive Extensions (Rx) - NuGet "Rx-Main" - 来处理事件。在学习了一些基本运算符之后,它确实使处理事件变得容易得多。

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

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

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

此查询基本上采用 Tasks 的列表,并将每个任务的所有 PropertyChanged 事件转换为单个可观察到的 return 每个 Task 的事件任务有一个 属性 更改,更改的任务 PropertyName

现在可以很容易地创建更多的 observable 来过滤 PropertyName 和 return Task:

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());

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

现在我建议将 AddHandlers 方法更改为 return 和 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);
}

唯一的新事物是 CompositeDisposable,它将两个 IDiposable 订阅合并为一个 IDisposable

这种方法的好处在于现在大部分代码都很好地位于一个方法中。以这种方式完成后,它使理解和维护变得容易 - 至少在一个小的学习曲线之后。 :-)