【问题标题】:Nested ObservableCollection - Propogate notification from child to parent嵌套 ObservableCollection - 从子级向父级传播通知
【发布时间】:2014-03-11 19:53:05
【问题描述】:

我正在使用 MVVM Light Toolkit 开发一个 WPF 应用程序。我只想显示一个嵌套的Observablecollection,它将员工出勤详细信息保存到DataGrid 中,并在内部网格中执行一些 CRUD 功能,并且基于这些更改,我必须自动重新计算外部集合记录。内部集合 (PunchDetailModels) 显示在 DataGridRowDetailsTemplate 中。

这里是模型:

    public class AttendanceModel : ObservableObject
     {
        public const string EmpNamePropertyName = "EmpName";

        private string _empName = string.Empty;

        public string EmpName
        {
            get
            {
                return _empName;
            }
            set
            {
                Set(EmpNamePropertyName, ref _empName, value);
            }
        }

        public const string PunchDetailModelsPropertyName = "PunchDetailModels";

        private ObservableCollection<PunchDetailModel> _punchDetailModels = null;

        public ObservableCollection<PunchDetailModel> PunchDetailModels
        {
            get
            {
                return _punchDetailModels;
            }
            set
            {
                Set(PunchDetailModelsPropertyName, ref _punchDetailModels, value);
            }
        }           
        private string _inOutCount;
        public string InOutCount
        {
                get
                {
                    return PunchDetailModels != null
                        ? string.Format("{0}/{1}", PunchDetailModels.Count(i => i.PunchStatus == Enums.PunchType.CheckIn),
                            PunchDetailModels.Count(i => i.PunchStatus == Enums.PunchType.CheckOut))
                        : null;
                }
            }

        public TimeSpan? FirstCheckIn
        {
            get
            {
                if (_punchDetailModels != null)
                {
                    var firstCheckIn =
                        _punchDetailModels.OrderBy(t => t.PunchTime)
                            .FirstOrDefault(i => i.PunchStatus == Enums.PunchType.CheckIn);

                    if (firstCheckIn != null)
                        return firstCheckIn.PunchTime;
                }

                return null;
            }
        }


        public TimeSpan? LastCheckOut
        {
            get
            {
                if (_punchDetailModels != null)
                {
                    var lastCheckOut =
                        _punchDetailModels.OrderBy(t => t.PunchTime)
                            .LastOrDefault(o => o.PunchStatus == Enums.PunchType.CheckOut);
                    if (lastCheckOut != null)
                        return lastCheckOut.PunchTime;
                }

                return null;
            }
        }


        public TimeSpan? TotalInTime
        {
            get
            {
                TimeSpan totalInTime = TimeSpan.Zero;

                if (_punchDetailModels != null)
                {
                    if (!IsValidRecord()) return null;

                    for (int inTime = 0; inTime < _punchDetailModels.Count; inTime += 2)
                    {
                        totalInTime += _punchDetailModels[inTime + 1].PunchTime - _punchDetailModels[inTime].PunchTime;
                    }
                }

                return totalInTime;
            }
        }

        public TimeSpan? TotalOutTime
        {
            get
            {
                TimeSpan totalInTime = TimeSpan.Zero;

                if (_punchDetailModels != null)
                {
                    if (!IsValidRecord()) return null;

                    for (int inTime = 1; inTime < _punchDetailModels.Count - 1; inTime += 2)
                    {
                        totalInTime += _punchDetailModels[inTime + 1].PunchTime - _punchDetailModels[inTime].PunchTime;
                    }
                }

                return totalInTime;
            }
        }    
}

public class PunchDetailModel : ObservableObject
    {
        public const string PunchStatusPropertyName = "PunchStatus";

        private Enums.PunchType _punchStatus;

        public Enums.PunchType PunchStatus
        {
            get
            {
                return _punchStatus;
            }
            set
            {
                Set(PunchStatusPropertyName, ref _punchStatus, value);
            }
        }

        public const string PunchTimePropertyName = "PunchTime";

        private TimeSpan _punchTime = TimeSpan.Zero;

        public TimeSpan PunchTime
        {
            get
            {
                return _punchTime;
            }
            set
            {
                Set(PunchTimePropertyName, ref _punchTime, value);
            }
        }

    }

视图模型:

public const string AttendanceCollectionPropertyName = "AttendanceCollection";

    private ObservableCollection<AttendanceModel> _attendanceCollection = null;
    public ObservableCollection<AttendanceModel> AttendanceCollection
    {
        get
        {
            if (_attendanceCollection == null)
            {
                _attendanceCollection = new ObservableCollection<AttendanceModel>();
                //_attendanceCollection.CollectionChanged+=_attendanceCollection_CollectionChanged;
            }
            return _attendanceCollection;
        }
        set
        {
            Set(AttendanceCollectionPropertyName, ref _attendanceCollection, value);
        }
}

查看:

我面临的问题:

1) 当用户 ADDDELETE 来自 Inner DataGrid 的特定记录时,我需要在 View Model 中获得通知。我知道可以通过为 ObservableCollection 注册一个集合更改事件来实现。但是内部ObservableCollection 怎么可能呢?

2) 我需要在视图模型中收到有关内部 DataGrid 中 CheckIn 或 Checkout 字段的任何更改的通知,以便我可以重新计算 TotalInTime、TotalOutTime 等字段。

我该怎么做?我目前陷入这种情况。请提出您的宝贵意见。

【问题讨论】:

    标签: c# wpf mvvm mvvm-light


    【解决方案1】:

    我猜ObservableObject 类是你自己实现的INotifyPropertyChanged 接口。现在来解决您的问题:

    1. 您应该在_punchDetailModels 中注册CollectionChanged 事件并在处理程序中为该变量引发PropertyChanged 事件,如下所示:

       public ObservableCollection<PunchDetailModel> PunchDetailModels
      {
        get
        {
          return _punchDetailModels;
        }
        set
        {
          Set(PunchDetailModelsPropertyName, ref _punchDetailModels, value);
           _punchDetailModels.CollectionChanged += handler;
        }
       }           
        private void handler(object sender, NotifyCollectionChangedEventArgs e)
        {
          base.RaisePropertyChanged(PunchDetailModelsPropertyName); // If you don't have a method with such signature in ObservableObject (one that takes a string and raises PropertyChanged for it) you'll have to write it.
         }
      

    这样,当从内部集合中添加或删除元素时,视图应该会自动重新加载。

    1. 除了订阅收听这些字段上的PropertyChanged 之外,别无他法。这就是View 所做的,这也是ViewModel 应该做的。像这样:

       public const string AttendanceCollectionPropertyName = "AttendanceCollection";
      
       private ObservableCollection<AttendanceModel> _attendanceCollection = null;
       public ObservableCollection<AttendanceModel> AttendanceCollection
        {
         get
         {
          if (_attendanceCollection == null)
          {
              _attendanceCollection = new ObservableCollection<AttendanceModel>();
          }
          return _attendanceCollection;
        }
         set
        {
          Set(AttendanceCollectionPropertyName, ref _attendanceCollection, value);
          _attendanceCollection.CollectionChanged+= handler
        }
      } 
      
       private void handler(object sender, NotifyCollectionChangedEventArgs e)
       {
        foreach (AttendanceModel model in AttendanceCollection)
              model.PropertyChanged += somethingChanged;
        }
      
        // Very ineffective to subscribe to all elements every time a list changes but I leave optimization to you.
       private somethingChanged (object obj, PropertyChangedEventArgs args)
       {
         if ( args.PropertyName == "CheckIn" ) // for example
          { 
                AttendanceModel ModelToRecalculate = obj as AttendanceModel;
                // You can do anything you want on that model.
          }
       }
      

    当然,当您认为有必要时(例如在handler 方法中),您需要在AttendanceModel 类中使用stringCheckIn 的参数来提高PropertyChanged

    编辑:

    回答您的评论问题:

    “来第二个 - 我需要在 PunchTime 字段更新时重新计算出勤模型属性,例如 InOutCount、TotalInTime、TotalOutTime。”

    答案是:您无需在ViewModel 中执行任何操作即可“重新计算”。 UI 订阅了 PropertyChangefor InOutCount , FirstCheckIn ... 等等。这是因为Binding(它会自动执行)。

    因此,您只需调用RaisePropertyChanged("InOutCount")RaisePropertyChanged("FirstCheckIn") 即可通知 UI 需要重新计算给定模型。 UI 将理解它需要获取这些属性,并且因为您在属性 getter 中有这些计算,所以它会被重新计算。

    所以,我看到每次 INNER 列表更改时都需要重新计算 UI,因此您只需将 handler 代码更改为 CollectionChanged 即可,PunchDetailModels 如下所示:

    // the handler for CollectionChanged for the INNER collection (PunchDetailModels)
    private void handler(object sender, NotifyCollectionChangedEventArgs e)
          {
            base.RaisePropertyChanged(PunchDetailModelsPropertyName); // If you don't have a method with such signature in ObservableObject (one that takes a string and raises PropertyChanged for it) you'll have to write it.
            base.RaisePropertyChanged("InOutCount")
            base.RaisePropertyChanged("FirstCheckIn")
            base.RaisePropertyChanged("LastCheckOut")
            // and so on for all the properties that need to be refreshed
           }
    

    【讨论】:

    • 出勤集合已更改事件仅在外部集合添加或删除时触发。它不会在任何属性更改时触发。所以不可能引发属性改变事件。
    • 是的,OUTER 集合不会触发PropertyChanged,除了它自己。我不明白问题是什么。 - 您希望刷新 INNER(PunchDetailModels) 集合,在第 1 点完成。当它发生变化时,只需为该集合调用 PropertyChanged。 - 您想知道某些 INNER 字段何时更改。在第 2 点完成。您需要订阅每个 AttendanceModel 对象的 PropertyChanged 并回复它。
    • 我想我知道混乱在哪里了。 ViewModel 监听 AttendanceCollection 上的 CollectionChanged 事件以订阅它的 AttendanceModels PropertyChanged 不引发它。
    • 您可能错过了最后几行:“当然,当您认为有必要时(例如在处理程序方法中),您需要在 AttendanceModel 类中使用值为 CheckIn 的字符串参数引发 PropertyChanged”我不为此编写代码,因为我认为您可以自己做。只需在AttendanceModel 的正确位置使用适当的参数触发PropertyChanged 事件
    • 第一部分还可以。来到第二个 - 我需要在 PunchTime 字段更新时重新计算出勤模型属性,如 InOutCount、TotalInTime、TotalOutTime。 PunchTime 字段来自 PunchDetailModels 集合。那么我应该如何知道必须为特定更新更改哪个 AttendanceModel 记录?
    猜你喜欢
    • 2017-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-01
    • 2016-09-29
    • 1970-01-01
    • 1970-01-01
    • 2021-05-27
    相关资源
    最近更新 更多