嵌套的ObservableCollection - 从子到父的Propogate通知

时间:2014-02-13 05:08:03

标签: c# wpf mvvm mvvm-light

我正在使用 MVVM Light Toolkit 开发 WPF 应用程序。我只想显示一个嵌套的Observablecollection,它将Employee Attendance详细信息保存到DataGrid并在内部网格中执行一些CRUD功能,并根据这些更改自动重新计算外部收集记录。内部集合(PunchDetailModels)显示在RowDetailsTemplate的{​​{1}}中。

以下是模特:

DataGrid

ViewModel:

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

    }

查看: enter image description here

我面临的问题:

1)当用户 ADD 删除来自Inner DataGrid的特定记录时,我需要在视图模型中获取通知。我知道通过为ObservableCollection注册一个集合更改事件是可能的。但内部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); } } 怎么可能?

2)我需要在viewmodel中获取Inner DataGrid中CheckIn或Checkout字段的任何更改通知,以便我可以重新计算TotalInTime,TotalOutTime等字段。

我该怎么做?我目前仍然坚持这种情况。请提出您的宝贵意见。

1 个答案:

答案 0 :(得分:5)

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

  1. 您应该在CollectionChanged中注册_punchDetailModels事件并在处理程序中为该变量举起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.
       }
    
  2. 这样,在从内部集合添加或删除元素时,视图应自动重新加载。

    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.
          }
       }
      
    2. 当然,当您认为有必要时,您需要在PropertyChanged课程中使用string CheckIn参数值AttendanceModel来提升handler(例如{{1}方法)

      修改

      回答你的评论问题:

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

      答案是:您无需在ViewModel中执行任何操作即可“重新计算”。用户界面订阅PropertyChange InOutCountFirstCheckIn ...等等。这是Binding(它自动完成)。

      所以,您需要做的就是通知UI需要重新计算给定模型的是调用RaisePropertyChanged("InOutCount")RaisePropertyChanged("FirstCheckIn")。 用户界面将理解它需要获取这些属性,并且因为你在属性获取器中有这些计算,它将被重新计算。

      因此,我发现每次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
             }