1
votes

The latest MVVM Light toolkit uses IoC in the ViewModelLocator (VML) to serve up instances of ViewModels.

Those instances are served from Read Only instance properties (i.e Not Static)

The samples show a MainViewModel exposed from the VML but not Parent View Child View

My Main View is looking at a List of Persons exposed by MainViewModel.

When one of the Person items is tapped in the Main View I want to navigate to PersonView. That PersonView would be bound to a PersonViewModel.

Would PersonViewModel be exposed from VML? If so what would the MainViewModel do to communicate the tapping of PersonViewModel? It cannot set the PersonViewModel in VML as that propery is only a Get.

I have used MVVM Light in the past and used Ready Write properties on the VML

1

1 Answers

0
votes

This is a the exact situation where one would use the MVVM Light Messenger Class. You would first register your Person View with the VML, much like your MainView is linked up

Add this to your ViewModelLocator Constructor:

SimpleIoc.Default.Register<PersonViewModel>();

Add this outside the constructor (use snippet mvvmlocatorproperty):

    /// <summary>
    /// Gets the PersonView property.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
        "CA1822:MarkMembersAsStatic",
        Justification = "This non-static member is needed for data binding purposes.")]
    public PersonViewModel PersonView
    {
        get
        {
            return ServiceLocator.Current.GetInstance<PersonViewModel>();
        }
    }

And make sure your View is hooked up like so:

<Window x:Class="MvvmLight1.PersonView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:ignore="http://www.ignore.com"
    mc:Ignorable="d ignore"
    DataContext="{Binding PersonView, Source={StaticResource Locator}}">

Now, hook up an Event to Command on your ListBox ItemTemplate

  <i:Interaction.Triggers>
       <i:EventTrigger EventName="PreviewMouseUp">
            <cmd:EventToCommand Command="{Binding OpenPersonCommand}"/>
       </i:EventTrigger>
  </i:Interaction.Triggers>

Make sure to include these two namespaces in your XAML window decleration:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"

Now you are all hooked up to use the Locator.

As far as messaging, to open a new View you create a Message. Below is my view model, and I will explain after

public class MainViewModel : ViewModelBase
{
    /// <summary>
    /// Initializes a new instance of the MainViewModel class.
    /// </summary>
    public MainViewModel()
    {
        OpenPersonCommand = new RelayCommand(() =>
            {
                Messenger.Default.Send<Person>(new Person() { FirstName = "Daryl" }, "OPENPERSONVIEW");
            });
    }

    /// GOT THIS USING mvvminpc snippet
    /// <summary>
    /// The <see cref="OpenPersonCommand" /> property's name.
    /// </summary>
    public const string OpenPersonCommandPropertyName = "OpenPersonCommand";

    private ICommand _openPersonCommand;

    /// <summary>
    /// Sets and gets the OpenPersonCommand property.
    /// Changes to that property's value raise the PropertyChanged event. 
    /// </summary>
    public ICommand OpenPersonCommand
    {
        get
        {
            return _openPersonCommand;
        }

        set
        {
            if (_openPersonCommand == value)
            {
                return;
            }

            RaisePropertyChanging(OpenPersonCommandPropertyName);
            _openPersonCommand = value;
            RaisePropertyChanged(OpenPersonCommandPropertyName);
        }
    }
}

}

Make sure to include GalaSoft.MvvmLight.Command, GalaSoft.MvvmLight.Messaging to get this to work. What you are doing is sending a message to anyone who recieves that type of message (in this case a Person object), with that exact token (in this case my token is a string "OPENPERSONVIEW", the second paramater. I usually use Enumerators, but this will work for simplicity sake).

Now, in the code behind of MainView.xaml, add this in the constructor:

 Messenger.Default.Register<Person>(this, "OPENPERSONVIEW", (myPerson) => 
        { 
            PersonView view = new PersonView(); 
            view.ShowDialog();
            Messenger.Default.Send<Person>(myPerson, "PASSVARIABLE");
        });

What we have done is we have registered to recieve any message that is passed with a Person object, and has a token of "OPENPERSONVIEW". Once we receive that message in the Code Behind, we are instantiating a new PersonView. Once that person view is instantiated, we are sending another message with our Person Object, that will only be received by Messenger listeners that are listening for the token "PASSVARIABLE".

Now to get the actual value from Main view model into your person view model you will use another messenger to recieve the message sent with token "PASSVARIABLE". In your constructor for your PersonViewModel, add this:

 Messenger.Default.Register<Person>(this, "PASSVARIABLE", (myPerson) =>
            {
                ThisPerson = myPerson;
            });

And make sure to have a property called ThisPerson like such in the View Model (created using mvvminpc snippet from MVVM Light):

 /// <summary>
    /// The <see cref="ThisPerson" /> property's name.
    /// </summary>
    public const string ThisPersonPropertyName = "ThisPerson";

    private Person _thisPerson;

    /// <summary>
    /// Sets and gets the ThisPerson property.
    /// Changes to that property's value raise the PropertyChanged event. 
    /// </summary>
    public Person ThisPerson
    {
        get
        {
            return _thisPerson;
        }

        set
        {
            if (_thisPerson == value)
            {
                return;
            }

            RaisePropertyChanging(ThisPersonPropertyName);
            _thisPerson = value;
            RaisePropertyChanged(ThisPersonPropertyName);
        }
    }

Basically, what we have done is used the MVVM Light Messenger class to pass around data, but it is all loosely coupled. None of the Registered Messegers care what is passed to them, or from whom, they are just listening for their Token, and doing what is asked of them.

I know this is long, but please comment and I will go through step by step where you run into problems. I just found a lot of answers to MVVM Light questions would tell one little part, but not the whole picture.

There are other ways to do this also, but this is the basic MVVM-Light out of box way.