wpfdata-binding

WPF - Reset viewmodel list back to initial state


I am trying to maintain two copies of a list of the same type inside of my view model. One list is property of the base model the view model is bound to, and the other list is a property of the view model. My intention is for one of the copies to be the initially loaded list, so that at any point I can perform a "reset" that would update the model's list back to the original state. The problem is that every time I make an update to a property in the model's list, it also updates that property in the copy I am trying to keep as the original. It's acting like I'm creating a shadow copy but I am initializing it with "new" so I'm not sure why I'm not preserving the original state of the m_originalSubmissionQuestions. Thank you!

Model:

public class SubmissionModel : ModelBase
{
        public int SubmissionId { get; set; }
        private bool m_noBuildingPlacedInService;
        public bool NoBuildingPlacedInService
        {
            get { return m_noBuildingPlacedInService; }
            set
            {
                if (m_noBuildingPlacedInService == value)
                    return;

                m_noBuildingPlacedInService = value;
                RaisePropertyChanged();
            }
        }

        private bool m_beginPeriodInFollowingYear;
        public bool BeginPeriodInFollowingYear
        {
            get { return m_beginPeriodInFollowingYear; }
            set
            {
                if (m_beginPeriodInFollowingYear == value)
                    return;

                m_beginPeriodInFollowingYear = value;
                RaisePropertyChanged();
            }
        }

        public List<SubmissionSectionModel> Sections { get; } = new List<SubmissionSectionModel>();

}

Model:

    public class SubmissionSectionModel : ModelBase
    {
        public bool ShowSectionHeader { get; set; }
        public string SectionHeader { get; set; }

        public List<SubmissionQuestionModel> Questions { get; } = new List<SubmissionQuestionModel>();

}

Model:

    public class SubmissionQuestionModel : ModelBase
    {
        public int AnswerId { get; set; }
        public int SubmissionFk { get; set; }
        public short QuestionFk { get; set; }

        public List<SubmissionAnswerModel> Answers { get; } = new List<SubmissionAnswerModel>();

        private bool m_revisionRequired;
        public bool RevisionRequired
        {
            get { return m_revisionRequired; }
            set
            {
                if (m_revisionRequired == value)
                    return;

                m_revisionRequired = value;
                RaisePropertyChanged();
            }
        }
}

View Model:

public class SubmissionDetailsViewModel : ViewModelBase
{
    private readonly IEventAggregator m_eventAggregator;
    private readonly IAOCData m_aocData;
    private readonly IUserData m_userData;

    private ShowControlEvent m_showControlEvent;
    private Guid m_submissionGuid;

    private readonly int m_userId;

    private bool? m_originalIsNoBuildingsInService = null;
    private bool? m_originalIsOneBuildingInService = null;

    public SubmissionDetailsViewModel (IEventAggregator eventAggregator, IAOCData aocData, IUserData userData) 
    {
        m_eventAggregator = eventAggregator;
        m_aocData = aocData;
        m_userData = userData;

        Setup_EventAggregator_EventHandlers();

        CurrentViewState = Visibility.Collapsed;

    }

    private void Setup_EventAggregator_EventHandlers()
    {
        //event raisers
        m_showControlEvent = new ShowControlEvent();

        //event handlers
        m_eventAggregator.EventHandler<ShowControlEvent>(e =>
        {
            if (e.ShowControlType == Enumerations.ShowControlTypes.AOCDetail)
            {
                if (!Guid.TryParse(e.Key, out Guid _id))
                {
                    m_showControlEvent.ShowControlType = Enumerations.ShowControlTypes.AOCSearch;
                    m_eventAggregator.RaiseEvent(m_showControlEvent);
                    MessageBox.Show("Uknown submission id");
                    return;
                }

                //scroll to top and display
                CurrentViewState = Visibility.Visible;

                //data is already loaded - no reason to do it again
                if (_id == m_submissionGuid)
                {
                    return;
                }

                //clear all prior data 
                SubmissionModel = null;

                DetailLoadingVisibilty = Visibility.Visible;

                //set the ID
                m_submissionGuid = _id;

                //load the data
                LoadDataAsync();

            }
            else if (e.ShowControlType == Enumerations.ShowControlTypes.AOCSearch)
            {
                CurrentViewState = Visibility.Collapsed;
            }
        });
    }

        private SubmissionModel m_submissionModel;
        public SubmissionModel SubmissionModel
        {
            get => m_submissionModel;
            set
            {
                if (m_submissionModel == value)
                {
                    return;
                }

                m_submissionModel = value;
                base.RaisePropertyChanged();              
            }
        }

        public void HandleInServiceChange()
        {
           AnnualOwnerCertificationSubmissionSectionModel _questionSection = 
           SubmissionModel.Sections.FirstOrDefault();

           foreach (var _question in _questionSection.Questions)
           {
              _question.RevisionRequired = true;
           }
        }

        private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(SubmissionModel.NoBuildingPlacedInService) || e.PropertyName == nameof(SubmissionModel.BeginPeriodInFollowingYear))
            {
                HandleInServiceChange();
            }
        }

        private ObservableCollection<AnnualOwnerCertificationSubmissionQuestionModel> m_originalSubmissionQuestions;
        public ObservableCollection<AnnualOwnerCertificationSubmissionQuestionModel> OriginalSubmissionQuestions
        {
            get { return m_originalSubmissionQuestions; }
            set
            {
                if (m_originalSubmissionQuestions == value)
                    return;

                m_originalSubmissionQuestions = value;
                base.RaisePropertyChanged();
            }
        }

private async void LoadDataAsync()
{
    //clear everything
    SubmissionModel = null;
    
    //start with getting the submission
    SubmissionModel _submission;
    _submission = await m_aocData.GetSubmission(m_submissionGuid);

    SubmissionModel = _submission;
    SubmissionModel.PropertyChanged += Model_PropertyChanged;
    SubmissionSectionModel _questionSection = SubmissionModel.Sections.FirstOrDefault();

    m_originalSubmissionQuestions = new ObservableCollection<SubmissionQuestionModel>(_questionSection.Questions);
    
    DetailLoadingVisibilty = Visibility.Collapsed;
}


    }


Solution

  • If I understand your question correctly, I suggest you add deep cloning methods.

    For example, something like this:

    public class SubmissionAnswerModel
    {
       // Some Code
    
       public SubmissionAnswerModel DeepClone()
       => new SubmissionAnswerModel()
          {
              // Init properties and fields
          };
    }
    
        public class SubmissionQuestionModel : ModelBase
        {
    
            public SubmissionQuestionModel DeepClone()
            => new SubmissionQuestionModel()
               {
                   AnswerId = AnswerId,
                   SubmissionFk = SubmissionFk,
                   QuestionFk = QuestionFk,
    
                   Answers = new List<SubmissionAnswerModel>(Answers.Select(ans => ans.DeepClone()),
    
                   RevisionRequired = RevisionRequired
               };
    
            // Other Code
        }
    
        public class SubmissionSectionModel : ModelBase
        {
            public SubmissionSectionModel DeepClone()
            => new SubmissionSectionModel()
               {
                   ShowSectionHeader = ,
                   SectionHeader = ,
    
                   Questions = new List<SubmissionQuestionModel> (Questions.Select(qst => qst.DeepClone))
               }
    
           // Other Code
        }
    
    private async void LoadDataAsync()
    {
        //clear everything
        SubmissionModel = null;
        
        //start with getting the submission
        SubmissionModel _submission;
        _submission = await m_aocData.GetSubmission(m_submissionGuid);
    
        SubmissionModel = _submission;
        SubmissionModel.PropertyChanged += Model_PropertyChanged;
        SubmissionSectionModel _questionSection = SubmissionModel.Sections.FirstOrDefault(); // Original
        SubmissionSectionModel _questionSectionClone = _questionSectionClone.DeepClone(); // Clone
    
        m_originalSubmissionQuestions = new ObservableCollection<SubmissionQuestionModel>(_questionSectionClone.Questions);
        
        DetailLoadingVisibilty = Visibility.Collapsed;
    }
    

    There are also many other options. If it is possible to make large changes, then I would prefer to remove the properties from the SubmissionModel class and return the desired values ​​using methods. In particular, the list should immediately be returned as a deep copy, not the original.
    Also, the implementation of INotifyPropertyChanged in the model classes looks very questionable to me. I would prefer to make custom events in the SubmissionModel class, and make the rest of the classes immutable DTOs.