Search code examples
c#wpfmvvmmvvm-lighticommand

Using ICommand and ServiceLocator to pass Parent object to load Child objects in an MVVM way


I am creating a WPF application with the MVVMLight toolkit. I have a "parent" form (ParentView) and would like to load a "child" form (ChildView) by passing the "parent" object (Parent) that will load all of the "child" objects.

I have included code snippets of 3 main classes I feel are important in anyone's analysis: ViewModelLocator, ChildViewModel, ParentViewModel. I was initially thinking of implementing an ICommand property within the ParentViewModel by instantiating the ParentView (see below), but this doesn't seem right since I am trying to do this in an MVVM way. Could someone assist with the following current structure and how I can pass the parent to a child?

// ----- ChildViewModel -----
public class ChildViewModel
{

  // this ctor doesn't make sense in the MVVM world, right?...
  public ChildViewModel(Parent selectedParent)
  {
     this.Parent = selectedParent;
     // ...
  }

  // how do I plug into this one?...
  public ChildViewModel(IParentService parentService)
  {
     this.ParentService = parentService
     // ...
  }

  // ... removed other code for brevity
}

// ----- ParentViewModel -----
{
  public ICommand ShowChildCommand
  {
     get { return new RelayCommand(() => new ChildView(SelectedParent).Show()); }
  }

  // ... removed other code for brevity
}

// ----- ViewModelLocator -----
public class ViewModelLocator
{
  public ChildModel ChildViewModel
  {
     get { return ServiceLocator.Current.GetInstance<ChildViewModel>(); } 
  }

  public ParentModel ParentViewModel
  {
     get { return ServiceLocator.Current.GetInstance<ParentViewModel>(); } 
  }

  public ViewModelLocator()
  {
      SimpleIoc.Default.Register<IChildService, ChildService>();
      SimpleIoc.Default.Register<IParentService, ParentService>();
      // ... removed other code for brevity
      SimpleIoc.Default.Register<ChildViewModel>();
      SimpleIoc.Default.Register<ParentViewModel>();
  }

  // ... removed other code for brevity
}

// ------ REVISION 2 EDITS ------------

I incorporated the latest suggestions from user @kidshaw, but I was stumped by the comment in which I had to create an instance of the ChildView within the ParentView. I am not sure how to do that. I read again the MSDN article about the Messenger, but do not see how it could help me with that question. I have included the following latest code. Please reference the commented sections.

public class ParentViewModel
{

 public ICommand ShowChildCommand
  {
      get { return new RelayCommand(OnLoadChildCommand); }
  }

 private void OnLoadChildCommand()
  {
     Messenger.Default.Send(new ParentToChildMessage { Parent = this.CurrentParent });
     // ****** this is where I instantiated the child view
     // I am sure this is wrong...
     var view = new ChildView().Show();
  }

 public class ParentToChildMessage : MessageBase
  {
      public Parent Parent { get; set; }
  }
...
}

And the ChildViewModel looks like:

public class ChildViewModel
{
  public ChildViewModel(IChildService service)
        {
            this.ServiceProxy = service;
            this.MessengerInstance.Register<ParentViewModel.ParentToChildMessage>(this, this.OnParentToChildMessage);
            this.ChildCollection = new ObservableCollection<Child>();
            GetChildInfo(this.CurrentParent);
        }
}

Solution

  • In your sample, it suggests that a ViewModel is talking to a view. This is to be avoided if you've already made the jump to an MVVM pattern.

    I would look to use the message framework in MVVM light to do this. You can keep your parent and child views separate this way and pass data without coupling the two together. You can control visibility of your child view in your child view model and toggle that in your message handler.

    This msdn article will introduce the concepts. http://msdn.microsoft.com/en-us/magazine/dn745866.aspx.

    Based upon your edits...

    First basic step is to create a message class to carry your data from parent to child.

    public class ParentToChildMessage : MessageBase
    {
        /// Include any properties you want to send
    }
    

    If you have no data to pass, you can use the GenericMessage<> object from MVVM Light to pass a bool to save yourself a bit of code. However, messages are registered by type so using the generic means your register method will have to do more inspection - I'll show his in a moment.

    In your parent view model, you have a command ShowChildCommand - we can use that as a point to send the message from.

    public class ParentViewModel : ViewModelBase
    {
        public ParentViewModel()
        {
    
        }
    
        public ICommand ShowChildCommand
        {
            get
            {
                return new RelayCommand(()=>this.MessengerInstance.Send<ParentToChildMessage>(new ParentToChildMessage()));
            }
        }
    }
    

    That's it for the parent view model. Next we register a message receiver in the child view model.

    public class ChildViewModel : ViewModelBase
    {
        public ChildViewModel()
        {
            this.MessengerInstance.Register<ParentToChildMessage>(this, this.OnParentToChildMessage);
        }
    
        private void OnParentToChildMessage(ParentToChildMessage obj)
        {
            // Inspect obj to decide what to do - let's just set as visible
            this.IsVisible = true;
        }
    
        public bool IsVisible
        {
            get
            {
                return _IsVisible;
            }
            set
            {
                if (value != _IsVisible)
                {
                    _IsVisible = value;
                    RaisePropertyChanged();
                }
            }
        }
        private bool _IsVisible;
    }
    

    You can see here, in the constructor we register a message to the strong type. If we used a generic the body of the OnParentToChildMessage would have to distinguish between this and other possible messages of the same type - not pretty code, especially as you'll likely end up building several message interactions over time.

    The child view model updates a simple bool IsVisible property - for this to work, your Parent view will need to have an instance of Child view within it. That will ensure your child view is created, and an instance of your child view model.

    I hope this helps.