An SL MvvM Question

An SL MvvM Question

Old forum URL: forums.lhotka.net/forums/t/9422.aspx


Jav posted on Monday, August 23, 2010

I would like to permit the enduser to periodically Save her work and then continue working with the same object graph.  I do know that the Save method returns the new Object Graph.  I use SL navigation to allow access to different parts of the object graph, with each navigable UI unit serviced by a viewmodel. And everything works great - until the Save is clicked, that is.  I have to make sure that all UI units at that point receive the Model(s) in the newly returned object graph.

My question is relatively simple.  Is it enough to simply reset the viewmodel's Model property to the new object, or is there a ritual of removal of the previous Model that I have to go through first.  The reason I ask is because I recall seeing a Remove method in a viewmodel in one of the demo projects, although I am thinking that there the Model object was being removed from its parent in the object graph, and not from the viewmodel (I can't locate it now)

Jav

 

RockfordLhotka replied on Monday, August 23, 2010

Generally speaking, if you set the Model property everything else updates automatically based on data binding.

The one thing I can think of right offhand though, is to watch out for the ManageObjectLifetime setting, because you could encounter some odd n-level undo issues if that's true (the default) and you have multiple viewmodel objects using the same object graph.

Jav replied on Monday, August 23, 2010

The only object in my object graph with a  ManageObjectLifetime setting of True is the Root object, everything else is set to false.

Initially I would set the Root object to the NewObject.  With that, I found that the Model property of the viewmodel servicing the part of the object graph currently being worked on in the navigation frame when Save button is clicked, remained attached to the old object(s) - I could tell because any further modification in that Model had no effect on the Save button (it remained disabled).  If I navigated to a different part of object graph, things worked as expected.  I am pretty sure that the Models of those viewmdoels were properly swiched to new (I am going to have to check it again just to be sure - on second thought)  They did behave correctly - that's for certain, but it may be the act of navigation to them that refreshed their Models.

The Objects that were being worked on when Save was pressed, also became normal if I navigated away and then navigated back.

Now what I am doing is this.  All my UI units have NavigationCacheMode="Required".  I cannot keep recreating them every time the user navigates to and fro.  originally I was setting the Model property right after the InitializeComponent statement.  This was probably the part of my problem, or so I thoght.  Now I set up everything else as before in the Page's Contructor, but I set the Model property of the viemodel in the OnNavigatedTo() event.  It has fixed every other problem but with an issue and a concern.  The concern (or question):  Is all this repeated setting of the Model property of the viewmodel while the UI unit (page) is cached an inocuous thing or does it have a downside.  And the issue is: I still have to navigate out and then back in - in other words I am back to square one.  I can always send the user to a page that says "Congratulations - you have won a toaster - Now click this", and thus force her to navigate back !!  There's that!

Jav

 

RockfordLhotka replied on Monday, August 23, 2010

I suppose your other option is to maintain relationships between the various viewmodel objects. I always think about viewmodels as have parent-child relationships that mirror the relationships of the model objects they reference. So if you change a parent model instance, all the child viewmodels need to also update their references too.

I think about viewmodels as existing in their own layer.

In a layered architecture, objects within a layer can interact freely (as a general rule). And objects from the next higher layer can interact as well - so the view can interact with the viewmodel, but not the other way around. Similarly, the viewmodel can interact with the model, but not the other way around. (the exception here being upward interactions via events or data binding interfaces)

Given that way of thinking, it is very acceptable for viewmodel objects to have parent-child relationships. And if that's true, when the Model property of a parent is changed, the child viewmodel can change itself too (perhaps because it is handling the parent's PropertyChanged event to be notified when the parent's Model property changes).

Jav replied on Monday, August 23, 2010

All my viewmodels are subclassed from my
                        MyViewModel<T> : Csla.Xaml.ViewModel<T>
which has an override:
                        protected override void OnModelChanged(T oldValue, T newValue)

This code was graciously provided by Jaan a few weeks back.  I tried messing with that but didn't get anywhere - I'm kind'a dunse when it comes to these - OnThis and OnThat (Geez - I didn't have to admit that!) so probably I wasn't doing it right.

How about creating a List property in the Root ViewModel in which each child viewmodel can register when it is set up.  Then the root VM can cycle through the list and tell each child to reset its Model?

Jav

RockfordLhotka replied on Monday, August 23, 2010

It seems like that would work.

I usually reverse that, and only allow child viewmodel objects to be created by the parent. In other words, the only way to get a child viewmodel is to ask the parent viewmodel for the child.

That way the parent can always provide a reference to itself to the child as the child is created. The child can then just set up an event handler for PropertyChanged, and in that event handler it can watch to see when the "Model" property changes. When that happens, the child knows it must set its own Model property to a value based on its parent's Model property.

RockfordLhotka replied on Monday, August 23, 2010

Something like this (off the top of my head):

public class ParentVm : ViewModel<Parent>
{
  private ChildVm _child;

  public ChildVm GetChildVm()
  {
    if (_child == null)
    {
      _child = new ChildVm(this);
    }
    return _child;
  }
}

public class ChildVm : ViewModel<Child>
{
  private ParentVm _parent;

  internal ChildVm(ParentVm parent)
  {
    _parent = parent;
    _parent.PropertyChanged += (o, e) =>
    {
      if (e.PropertyName == "Model")
        SetModel(_parent.Model);
    };
    SetModel(_parent.Model);
  }

  private void SetModel(Parent parentModel)
  {
    Model = parentModel.Child;
  }
}

 

Jav replied on Monday, August 23, 2010

Fantastic.  That sounds like a better way to do it. Thank you.  Now I have something to work with.

Jav

Jav replied on Tuesday, August 24, 2010

In my RootViewModel I am doing this:

        private ViewModels.SubjectViewModel subjectVM;

        protected override void OnRefreshed()
        {
            base.OnRefreshed();
            subjectVM = new SubjectViewModel(this);
        }

The childViewModel has the following code.


        EncounterViewModel parentVM;
        public SubjectViewModel(EncounterViewModel parent)
        {
            ManageObjectLifetime = false;
            parentVM = parent;
            parentVM.PropertyChanged += (o, e) =>
            {
                if (e.PropertyName == "Model")
                {
                    SetModel(parentVM.Model);
                }
            };
            SetModel(parentVM.Model);
         }

        public void SetModel(Encounter parent)
        {
            Model = parent.subject;
        }

I place a breakpoint at  if (e.PropertyName == "Model") and  modify the child as well as the root data.  When I save, I do get a number of breaks on (IsBusy, CanSave, CanFetch etc), but Model isn't one of them.

Jav

 

Jav replied on Tuesday, August 24, 2010

Additional Info:

In MyViewModel, from which all other of my viewmodels are subclassed, I added the following line at the end of

protected override void OnModelChanged(T oldValue, T newValue)
{

             
            OnPropertyChanged("Model");
}

Now I see a break with PropertyName = "Model", and my Save button becomes Enabled when I make another change to my data, like clicking a checkbox.

Jav

RockfordLhotka replied on Tuesday, August 24, 2010

That is strange. Model is a dependency property, so PropertyChanged should be raised automatically by the DependencyObject base class...

Jav replied on Tuesday, August 24, 2010

I don't know!  The answer to that is above my pay grade. Big Smile

I have now implemented the same exact code in all of the current UI units, and they are all working as expected.  Thanks for your help.
Oh, I did try subclassing directly from Csla.ViewModel thinking that MyViewModel may be causing a problem but the results were the same. 

Jav

Copyright (c) Marimer LLC