3

I am using MVVM light and figured out since that the ViewModelLocator can be used to grab any view model and thus I can use it to grab values.

I been doing something like this

public class ViewModel1
{
   public ViewModel1()
   {
        var vm2 = new ViewModelLocator().ViewModel2;
        string name = vm2.Name;
   }
}

This way if I need to go between views I can easily get other values. I am not sure if this would be best practice though(it seems so convenient makes me wonder if it is bad practice lol) as I know there is some messenger class thing and not sue if that is the way I should be doing it.

Edit

  static ViewModelLocator()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

            SimpleIoc.Default.Register<ViewModel1>();
SimpleIoc.Default.Register<ViewModel2>();
        }


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

Edit

Here is a scenario that I am trying to solve.

I have a view that you add price and store name to. When you click on the textbox for store name you are transferred to another view. This view has a textbox that you type the store you are looking for, as you type a select list get populated with all the possible matches and information about that store.

The user then chooses the store they want. They are transferred back to the view where they "add the price", now the store name is filled in also.

If they hit "add" button it takes the price, the store name, and the barcode(this came from the view BEFORE "add price view") and sends to a server.

So as you can see I need data from different views.

chobo2
  • 83,322
  • 195
  • 530
  • 832
  • messaging is better approach because in above example ViewModel2 is static property and if you access this property directly in other view model then there is chance that you will lose control and with messaging approach, you will have knowledge about current state of Viewmodel2 object... – User1551892 Jul 17 '13 at 13:30
  • @User1551892 `ViewModel2` is not a static property in the above code as it's tied to an instance. Messaging is good for fire and forget communication. It can become unduly complicated if you're using messaging to prompt something else to send a message back. It also becomes hard to test. – Matt Lacey Jul 17 '13 at 13:47
  • Are you trying to get user feedback through a dialog? – flup Aug 04 '13 at 11:43

4 Answers4

1

Yes, you can do this, in as much as the code will work but there is a big potential issue you may run into in the future.

One of the strong arguments for using the MVVM pattern is that it makes it easier to write code that can be easily tested.
With you're above code you can't test ViewModel1 without also having ViewModelLocator and ViewModel2. May be that's not too much of a bad thing in and of itself but you've set a precedent that this type of strong coupling of classes is acceptable. What happens, in the future, when you

From a testing perspective you would probably benefit from being able to inject your dependencies. This means passing, to the constructor--typically, the external objects of information you need.

This could mean you have a constructor like this:

public ViewModel1(string vm2Name)
{
    string name = vm2Name;
}

that you call like this:

var vm1 = new ViewModel1(ViewModelLocator.ViewModel2.name);

There are few other issues you may want to consider also.

You're also creating a new ViewModelLocator to access one of it's properties. You probably already have an instance of the locator defined at the application level. You're creating more work for yourself (and the processor) if you're newing up additional, unnecessary instances.

Do you really need a complete instance of ViewModel2 if all you need is the name? Avoid creating and passing more than you need to.

Update

If you capture the store in the first view/vm then why not pass that (ID &/or Name) to the second VM from the second view? The second VM can then send that to the server with the data captured in the second view.

Another approach may be to just use one viewmodel for both views. This may make your whole problem go away.

Matt Lacey
  • 65,560
  • 11
  • 91
  • 143
  • I see what you mean about testing as like you said not i always would have to create a dummy ViewModel1 and possibly the ViewModelLocator. I don't get your example where would that be? In the ViewModelLocator? – chobo2 Jul 17 '13 at 16:08
  • @chobo2 you'd use the example creation code wherever you're creating the ViewModel. (This may or may not be in the VML.) – Matt Lacey Jul 17 '13 at 16:58
  • Well I am creating my Models in the ViewModel Locator using the built in IoC and then a property so not sure where to put that in. I added it to my OP. So you do not recommend messaging unless it is some sort of event you want to fire off and then forget about not something like passing fields back to another view to be used? – chobo2 Jul 18 '13 at 05:58
  • That's right. I only use messaging for fire and forget situations. I hardly use it at all now though. It's good when you want to message from viewmodel to view but that's not a common scenario (any more). – Matt Lacey Jul 18 '13 at 08:15
  • Oh why is it not common anymore? So do you do what you code example does not or do you do some different way to get data back from one view to another(ie I have a view to choose a store and then it goes back to an add product view that has a textbox for price and that choosen store). – chobo2 Jul 18 '13 at 17:33
  • @chobo2 It's now more common/preferred to pass the viewmodel a reference to a service which will perform an action, that, a few years ago may still have been done in code behind. People have, generally, become stricter about not using any code behind and instead use services for code that runs in the context of a view. – Matt Lacey Jul 19 '13 at 12:44
  • Do you think you could write up an example how people do it now? Ya the goal is not to really use the codebehind but sometimes it just makes sense to do so. – chobo2 Jul 19 '13 at 16:35
  • Lacy - I am still not clear on how to use your example with the MVVM light as all of VM's are IOC bound in the ViewModelLocator so I am unclear on how I would pass in parameters through the constructor when none of data would exist at that time. – chobo2 Jul 26 '13 at 20:42
  • Another thing that I don't get is if you got services already as parameters what are you passing into them. Again SimpleIoc would be taking care of those but if your making these objects manually you lose that and have to create the services yourself. – chobo2 Jul 31 '13 at 22:25
  • Sorry I don't know how to pass the ID or Name can you please give me an example. Ya that is one way to go to have just one ViewModel or Model(I think 1 Model might be better as I think each View should have it's own VM). I am going to try Darlene way first, it seems to solve the problems you raised about using the VM directly. – chobo2 Aug 09 '13 at 16:06
1

I'm trying to understand what your scenario is. In the MVVMlight forum, you added the following context to this question:

"I have some data that needs to be passed to multiple screens and possibly back again."

For passing data between VMs, I would also - as Matt above - use the Messenger class of MVVMLight as long as it is "fire and forget". But it is the "possibly back again" comment that sounds tricky.

I can imagine some scenarios where this can be needed. Eg. a wizard interface. In such a case I would model the data that the wizard is responsible for collecting and then bind all Views to the same VM, representing that model object.

But that's just one case. So maybe if you could provide a little more context, I would be happy to try and help.

Thomas
  • 558
  • 4
  • 14
  • Well I changed my design so I don't think I need the "back part", but my thought was sort of I scan a bar-code and store it, ask for product price. If product does not exist, go to "add product page", that shows entered price and barcode. User see's now they added an extra 0 on price and change it. I am thinking if they would hit the back button or something it would be nice if that price would show up on price screen and not the old incorrect value. Since another person in theory could have added the product int hat time then the price would now be saved. – chobo2 Aug 01 '13 at 23:07
  • So I am not sure if I am now in a "fire and forget" situation but it sounded like that their are better ways of doing this now instead of Messenger class but I don't get Matts way since of the Ioc container is doing all the object creation. – chobo2 Aug 01 '13 at 23:09
  • I think I have one part that is "back part". I have a textbox that if you click on it, it goes to a new view with another textbox, you start typing and stores popup. You choose a store, it goes back to the original view and the textbox should now have that store name filled in and should be stored in that VM as when they hit add it should be taken and sent to the server. – chobo2 Aug 02 '13 at 22:50
  • @chobo2 Can you update the question with this information please? – flup Aug 04 '13 at 11:38
1

If you have properties in 1 view or view model that need to be accessed by a second (or additional) views or view models, I'd recommend creating a new class to store these shared properties and then injecting this class into each view model (or accessing it via the locator). See my answer here... Two views - one ViewModel

Here is some sample code still using the SimpleIoc

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

public IMyClass MyClassInstance
{
    get{ return ServiceLocator.Current.GetInstance<IMyClass>();}
} 

Here is a review of SimpleIOC - how to use MVVMLight SimpleIoc?

However, as I mentioned in my comments, I changed to use the Autofac container so that my supporting/shared classes could be injected into multiple view models. This way I did not need to instantiate the Locator to access the shared class. I believe this is a cleaner solution.

This is how I registered MyClass and ViewModels with the Autofac container-

var builder = new ContainerBuilder();
var myClass = new MyClass();
builder.RegisterInstance(myClass);
builder.RegisterType<ViewModel1>();
builder.RegisterType<ViewModel2>();
_container = builder.Build();
ServiceLocator.SetLocatorProvider(() => new AutofacServiceLocator(_container));   

Then each ViewModel (ViewModel1, ViewModel2) that require an instance of MyClass just add that as a constructor parameter as I linked initially.

MyClass will implement PropertyChanged as necessary for its properties.

Community
  • 1
  • 1
Darlene
  • 808
  • 5
  • 16
  • How do I inject them though? I am using the built in SimpleIoc.I looked at your example and yes I am using the Model Locator like you did to hook up ViewModel1 to View1 but I don't get where you create "MyClass" so not sure how you insert it. It is not an interface so it is not register and thus won't be auto created by SimpleIoc. That is my whole problem with Matts answer I don't know where he creates his stuff to pass it in. – chobo2 Aug 07 '13 at 20:05
  • @chobo2 If you want to keep using the SimpleIoc I don't know of a way to inject. However, you could also use the MVVMLight locator. You can create an interface for MyClass so that you can use the SimpleIoc. Instead of accessing the ViewModel2 property on your ViewModelLocator you could add a property for MyClass to your locator. You can register MyClass with the SimpleIoc using SimpleIoc.Default.Register() and then when you ask for that property the SimpleIoc will either instantiate it for you or return the one that it already instantiated. – Darlene Aug 07 '13 at 20:36
  • @chobo2 Instead of the SimpleIoc I used the MVVMLight Locator with an Autofac container so that I just needed to register my classes with the Autofac container and then they were injected into whatever classes required them. http://www.spikie.be/blog/post/2013/04/12/10-things-you-might-have-missed-about-MVVM-Light.aspx – Darlene Aug 07 '13 at 20:47
  • From a quick search is seems like Autofac is another Ioc so I am guessing it would face the same problems. If that is the case you have any model that spans more than 1 view/viewmodel as interface? I will try that out but for some reason it just seems so strange having the model as an interface of properties(guessing you keep it as an ObeservableOjbect as well?) – chobo2 Aug 07 '13 at 20:53
  • Autofac is an Ioc, but it injected my shared class into whatever ViewModels required that class as a constructor parameter. I have a model that spans multiple view models. I did not create it as an interface. Autofac has a RegisterInstance(myInstance) method on it's builder object. I will update my answer to include this sample code. – Darlene Aug 07 '13 at 20:55
  • Interesting, I have to look if ninject has something like that. So you removed the SimpleIoc then, this RegisterInstance is there like a default option that makes the class for the lifetime of the program? Since at first I would have thought it would make a new instantiation for each ViewModel. – chobo2 Aug 07 '13 at 21:25
  • I guess you are using this one for wp7? http://www.nuget.org/packages/Autofac.Wp7 – chobo2 Aug 07 '13 at 21:31
  • By default that class is the only instance for the lifetime of the program. It does not create a new instance for each ViewModel. I am using Autofac for a desktop application, not a WP7 application, but I would expect the pattern I followed to work for a WP7 application. – Darlene Aug 07 '13 at 21:35
  • hmm. Just got around to trying it and I don't know if the Autofac.Wp7 has "AutofacServiceLocator" as I am not seeing it. So not sure if this will work. – chobo2 Aug 08 '13 at 17:51
  • I had to install an additional nuget package for the AutofacServiceLocator (http://www.nuget.org/packages/Autofac.Extras.CommonServiceLocator/). Alternatively, here is some code for it - https://code.google.com/p/autofac/source/browse/contrib/Source/AutofacContrib.CommonServiceLocator/AutofacServiceLocator.cs?r=7e4a28a71ab1b61a9f1c6570763eaf227e35733e – Darlene Aug 08 '13 at 18:23
  • That requires autofac version 3 what does not support Wp7. I will try source code and see if that works. – chobo2 Aug 08 '13 at 19:03
  • Sorry I missed that. Would you be able to write your own using the source code I provided? – Darlene Aug 08 '13 at 19:08
  • Maybe, I took that source code and put it in my project with the autofac plugin I found for WP7 and it seem promising as it does not crash and displays the sample MVVM light text but I got to investigate more. – chobo2 Aug 08 '13 at 19:14
  • It seems to be working but I am kinda confused about why my ViewModels constructors get hit like 3 times now. I would think they get created once. – chobo2 Aug 10 '13 at 20:52
  • Also, how my app works is that you do one search and then a few minutes you may want to do another search. I am little bit concerned that on the second search MyClass might not get cleared properly and I might have old data in it. Any ideas on how to clear it but still keep it across all instances for that current search? – chobo2 Aug 10 '13 at 22:17
  • (1) I also would not expect the constructor to get hit 3 times. Did you look at the callstack when the constructor was hit to see who was calling it? (2) Could you raise an event indicating that a new search is starting or the old search is complete and clear your view model then? How do you know that a new search is occurring? When you identify this I would raise an event. – Darlene Aug 12 '13 at 14:54
  • what do you mean clear the VM do you mean null everything out or a new one? Also do you mean Model as well that is where all the fields are stored. I am not sure where to raise the event, at one point I thought maybe do it on NavigateFrom but I am using this method http://gavindraper.com/2013/01/14/mvvm-page-navigation-using-mvvm-light-on-wp7/ and have no clue how to get a hold of that event. So I am not sure where to put it. With the hitting 3 times I have not looked into it but I think it could be because I had old code of what I did in my OP that could be triggering it. – chobo2 Aug 12 '13 at 16:54
  • I meant reset whatever variables in your ViewModel and Model you need to so that you can do a "clean" search. If you wanted to send an event or message in the NavigateFrom you could call Messenger.Default.Send to send a message and then to "receive" the message you could use Messenger.Default.Register in your ViewModel. – Darlene Aug 12 '13 at 17:30
  • Hmm, I have to look into this Messenger.Default.Send not sure how that works. Anyway to recreate the object completely? There are quite a bit of properties I would have to put back to "null". – chobo2 Aug 12 '13 at 20:34
0

Ok, my shot at an answer for your original question first is: Yes, I think it is bad to access one VM from another VM, at least in the way it is done in the code example of this question. For the same reasons that Matt is getting at - maintainability and testability. By "newing up" another ViewModelLocator in this way you hardcode a dependency into your view model.

So one way to avoid that is to consider Dependency Injection. This will make your dependencies explicit while keeping things testable. Another option is to use the Messenger class of MVVMLight that you also mention.

In order to write maintainable and testable code in the context of MVVM, ViewModels should be as loosely coupled as possible. This is where the Messenger of MVVMLight can help. Here's a quote from Laurent on what Messenger class was intended for:

I use it where decoupled communication must take place. Typically I use it between VM and view, and between VM and VM. Strictly speaking you can use it in multiple places, but I always recommend people to be careful with it. It is a powerful tool, but because of the very loose coupling, it is easy to lose the overview on what you are doing. Use it where it makes sense, but don't replace all your events and commands with messages.

So, to answer the more specific scenario you mention, where one view pops up another "store selection" view and the latter must set the current store when returning back to the first view, this is one way to do it (the "Messenger way"):

1) On the first view, use EventToCommand from MVVMLight on the TextBox in the first view to bind the desired event (eg. GotFocus) to a command exposed by the view model. Could be eg. named OpenStoreSelectorCommand.

2) The OpenStoreSelectorCommand uses the Messenger to send a message, requesting that the Store Selector dialog should be opened. The StoreSelectorView (the pop-up view) subscribes to this message (registers with the Messenger for that type of message) and opens the dialog.

3) When the view closes with a new store selected, it uses the Messenger once again to publish a message that the current store has changed. The main view model subscribes to this message and can take whatever action it needs when it receives the message. Eg. update a CurrentStore property, which is bound to a field on the main view.

You may argue that this is a lot of messaging back and forth, but it keeps the view models and views decoupled and does not require a lot code.

That's one approach. That may be "old style" as Matt is hinting, but it will work, and is better than creating hard dependencies.

A service-based approach: For a more service-based approach take a look at this recent MSDN Magazine article, written by the inventor of MVVMLight. It gives code examples of both approaches: The Messenger approach and a DialogService-based approach. It does not, however, go into details on how you get values back from a dialog window.

That issue is tackled, without relying on the Messenger, in this article. Note the IModalDialogService interface:

public interface IModalDialogService
{
  void ShowDialog<TViewModel>(IModalWindow view, TViewModel viewModel, Action<TViewModel> onDialogClose);

  void ShowDialog<TDialogViewModel>(IModalWindow view, TDialogViewModel viewModel);
}

The first overload has an Action delegate parameter that is attached as the event handler for the Close event of the dialog. The parameter TViewModel for the delegate is set as the DataContext of the dialog window. The end result is that the view model that caused the dialog to be shown initially, can access the view model of the (updated) dialog when the dialog closes.

I hope that helps you further!

Thomas
  • 558
  • 4
  • 14
  • Yes, I wish matt would come back and give an example on the "newer" way as I would like to compare it. In another scenario where I need to send multiple values(this time only 1 way). Not sure if that would require multiple messengers then or if you could have one that sends an object back. Would like to see if Matts way would cut down of code, also someone suggested passing them through the navigate method but do it a way so it still MVVM. Seems like 3 ways to go about it yet no examples for me to compare against :( – chobo2 Aug 05 '13 at 19:22
  • Re: multiple messages for this second scenario: With the MVVMLight Messenger you can easily send and receive your own custom "payload" objects. So yes - you can definitely send an object back. Check [this example](http://jesseliberty.com/2011/01/06/windows-phone-from-scratch%E2%80%93mvvm-light-toolkit-soup-to-nuts-3/) for details. – Thomas Aug 08 '13 at 06:21
  • I've also updated my previous answer with details on the service-based approach. HTH! – Thomas Aug 08 '13 at 06:54