【问题标题】:subscribing to different events on Property changed if different properties如果属性不同,则订阅属性上的不同事件会发生变化
【发布时间】:2016-05-10 12:38:05
【问题描述】:

我有一个类 Step ,它有一个 Task 集合,即 List 。 Step 具有属性 Status , Time 。任务也具有相同的属性。每当任何任务的时间或状态发生变化时,都需要更新 Step 的 Status 和 Time 的值。 为此,我为 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

        }

这是 Task 中的 Property changed 事件处理程序 公共事件 PropertyChangedEventHandler PropertyChanged;

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

    }

我的问题是,即使我只更改 Task Time ,它也会调用处理程序 Status 和 time 因为它们订阅了任务上的相同属性更改事件。

如何根据调用的属性来分叉属性更改事件,并确保仅调用相应的处理程序而不是同时调用两者?

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

问候, P

【问题讨论】:

    标签: c# wpf inotifypropertychanged


    【解决方案1】:

    你需要检查传入的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 语句中。


    附:您可以使用 BindingList<Task> 作为保存任务的集合,而不是手动订阅每个 Task,然后您可以订阅 ListChanged 事件,如果列表中的任何项目引发 PropertyChanged,则将引发该事件(请务必启用RaiseListChangedEvents 并检查ListChangedEventArgs.ListChangedType 是否等于ListChangedType.ItemChanged)。

    【讨论】:

      【解决方案2】:

      每个事件都有“访问器”添加或删除。类似于获取/设置属性的东西。该访问器可以向您展示事件的性质。每个事件都有一个 InvocationList,它表示在引发事件时将通知的对象集合。使用此访问器,您可以更好地控制通知的内容和不通知的内容。当您订阅事件时,订阅的对象会被插入到 Invocation 列表中。

      由于您为两个事件订阅了同一个对象,因此您将触发两次。

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

      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 或 MethorVeawers(如 FODY)等各种东西来改进这一点

      【讨论】:

        【解决方案3】:

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

        但我更喜欢使用 Microsoft 的响应式扩展 (Rx) - NuGet “Rx-Main” - 来处理任何事件,而不是制作许多复杂的方法并让代码到处乱跑。在学习了一些基本的运算符之后,它确实使处理事件变得更加容易。

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

        首先,我会创建这个 observable:

        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 事件转换为单个可观察对象,当该任务具有属性更改时返回每个Task 和任务的PropertyName改变了。

        现在可以很容易地创建更多的 observables,它们通过 PropertyName 过滤并返回 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 方法以返回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

        这种方法的好处是大部分代码现在可以很好地放在一个方法中。以这种方式完成后,它很容易理解和维护——至少在经过一段小的学习曲线之后。 :-)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-01-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-09-22
          • 1970-01-01
          相关资源
          最近更新 更多