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

Den*_*nis 7 c# wpf mvvm mvvm-light

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

这是模特:

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

    }
Run Code Online (Sandbox Code Playgroud)

ViewModel:

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);
        }
}
Run Code Online (Sandbox Code Playgroud)

查看: 在此输入图像描述

我面临的问题:

1)当用户从Inner DataGrid 添加删除特定记录时,我需要在视图模型中获取通知.我知道通过为ObservableCollection注册一个集合更改事件是可能的.但内心ObservableCollection怎么可能呢?

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

我怎样才能做到这一点 ?我目前仍然坚持这种情况.请提出您的宝贵意见.

mic*_*rus 5

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

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

     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.
       }
    
    Run Code Online (Sandbox Code Playgroud)

这样,在从内部集合添加或删除元素时,视图应自动重新加载.

  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.
        }
     }
    
    Run Code Online (Sandbox Code Playgroud)

当然,你需要提高PropertyChangedstring价值参数CheckInAttendanceModel时,您认为这是必要的类(例如在handler法)

编辑:

要回答您的评论问题:

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

答案是:你不需要在ViewModel"重新计算"中做任何事情.UI订阅PropertyChangeInOutCount,FirstCheckIn等等.这是因为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
       }
Run Code Online (Sandbox Code Playgroud)