2
votes

I'm trying to get to grips with the concept of a ViewModelLocator (in MVVM Light, although the question applies to the concept of a ViewModelLocator in general, regardles of which MVVM framework is used), and I have a hard time figuring out how to use it.

As I understand it, your views use one of the properties on the singleton instance of the locator as their datacontext. The locator defines these various properties, and returns the proper viewmodel instance for each.

That's all fine, but I have trouble understanding how you actually populate these viewmodels with the model data the views are supposed to present.

For instance, suppose I have a view that presents a list of employees. I can create an EmployeesView and an EmployeesViewModel. In the ViewModelLocator, I can create a property that returns this EmployeesViewModel:

public EmployeesViewModel Employees
{
    get
    {
        return ServiceLocator.Current.GetInstance<EmployeesViewModel>();
    }
}

Now, the viewmodel needs a list of employees, so I can create some sort of dataservice that returns all employees, and register that with the Servicelocator in the ViewModelLocator's constructor:

public ViewModelLocator()
{
    ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
    SimpleIoc.Default.Register<IDataService, AllEmployeesDataService>();
}

So, this'll work, if I instantiate the EmployeesView the EmployeesViewModel will be instantiated and injected with a dataservice that returns all employees.

However, now I want to see the details of a certain employee that I just clicked in the EmployeesView. This employee presumably has some sort of Id by which to retrieve him/her from a database, or whatever.

I can create an EmployeeDetailsView and an EmployeeDetailsViewModel, and add a property to the ViewModelLocator:

public EmployeeDetailsViewModel EmployeeDetails
{
    return ServiceLocator.Current.GetInstance<EmployeeDetailsViewModel>();
}

and perhaps register some sort of dataservice in the ViewModelLocator's constructor:

SimpleIoc.Default.Register<IDataService, EmployeeDetailsDataService>();

But how do I tell either the dataservice or the viewmodel which employee they're supposed to present the details for? Where do I pass the employee id?

Am I looking at this all wrong? Anyone know of any good examples? All the examples I can find just return the same single instance of each viewmodel.

2

2 Answers

0
votes

A simple example I use is to use a dataservice that gets injected into the constructor of the viewmodel, like you have. That data service returns an observable collection of my objects (in your case employees.) I create a listbox and Detail grid in the same view. So I bind the listbox to the observable collection that I can style and sort by using a collectionviewsource. For the details, I create a grid with the required fields that I want to display. I create a property in my viewmodel for the selected item (SelectedEmployee) of the listbox and bind them together. THen I bind the detail grid to the SelectedEmployee. THis will cause the fields to show the values from the selected employee.

Now you can us this for all CRUD operations and you can bind the slecteditemchanged event of the listbox to a relay command and add your business logic as needed. Another thing to note is you can break this apart to support async operations. I have another implementation where I take the selected item changed event of the listbox and execute a async get function to get the selcted item.

I hope this helps

0
votes

I find J King answer quite good, but I will like to shed some more light and options never the less.

In the comment you asked about what happens if it's not the same view?
Here's an implementation I have:

<ListBox x:Name="YourListView"  
          ItemsSource="{Binding SomeCollection}"
          SelectedItem="{Binding SelectedItemObject, 
                            UpdateSourceTrigger=PropertyChanged}"
         ToolTip="Double click to edit"
          >
    <ListBox.ContextMenu>
        <ContextMenu>
            <MenuItem Header ="Edit me" Command="{Binding Edit_Command}" 
                      CommandParameter="{Binding SelectedItemObject}"
            />
            <MenuItem Header ="Delete me" Command="{Binding Delete_Command}" 
                       CommandParameter="{Binding SelectedItemObject}"
            />
        </ContextMenu>
    </ListBox.ContextMenu>

    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseDoubleClick">
            <Command:EventToCommand Command="{Binding Edit_Command}" 
                            CommandParameter="{Binding ElementName=YourListView, 
                                                Path=SelectedItem}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>

</ListBox>

Notice that either double clicking on an object in that list, or right clicking and selecting one of the two options (namely edit and delete), will Call a command with a paramater.

Now, when you use the ContextMenu since you right clicked and object, it's selected, and you can just send it.

In the case of the double click, I'm using the name of the listbox to get the item.

From here what I'll do in the viewmodel is something like:

private void Execute_Edit(object param)
{
    var your_object = (cast_to_your_type)param;
    Messenger.Default.Send(new SwitchView(new SomeViewModel(_dataService,your_object)));
}

The ICommand will call Execute_Edit, which in turn will send a message using the Messenger.

This is how I defined SwitchView:

/// <summary>
/// Used as message, to switch the view to a different one.
/// </summary>
public class SwitchView
{
    public SwitchView(MyViewModelBase viewmodel)
    {
        ViewModel = viewmodel;
    }

    public MyViewModelBase ViewModel { get; set; }
}

My MainWindow is registered to listen to those messages,and we know what to change to (obviously, to the view model given : SomeViewModel). The SwitchView on the main class will change the ViewModel property to the property the message passed.

Here's what I have in my main view:

<Border >
    <ContentControl Content="{Binding Current_VM}" />
</Border>

So, whatever the Current_VM property is set to, will show you that view.

Hope that helped :)