6

I am using Galasoft Mvvm Light toolkit, to build my application in the MVVM pattern for windows phone. I have to pages that each have their own viewmodel.

When a user starts the app he can choose new game og spin up a questions page. These to pages have each a viewmodel, and everything works using the viewmodellocator. When the user then navigates back to choose between new game and questions again. The viewmodel/page is not removed. which means when the user a second time goes into questions or new game, the constructor for the viewmodel is not called, such that the initialisation in the constructor is not run, and the view is not set correctly.

Solutions I have tried

I tried removing the backstack in the navigations, such as a new navigation to new game or questions, should spin up a new page, and thereby caling the constructor. Not working.

USing the loaded event in the view, and calling the constructor. Not working.

Tried to follow How to reset all instances in IOC Container But could not get it to work, might just be me.

Have anyone solve this issue, if so how should it be solved?

Code Here you can find an example. Press questions, and press the button in there once, use backkey. and press questions again. you see that the number is now 1, this could easily be changed. But the error comes when you press the button again. Suddenly two popups are shown.

So what is the correct way to set up the viewmodel. since the view of newgame will be used when reloading an old game, just with other values, and when one wants to start a new game. Hope you understand :)

This example is only to show my problem with popups count going up for each return to the viewmodel page. https://www.dropbox.com/s/gjbz0l8rmsxqzrd/PhoneApp8.rar

ViewModel Locator I am in my current project using three viewmodels seen in the below code:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;

namespace MVVMTestApp.ViewModel
{
public class ViewModelLocator
{
    public ViewModelLocator()
    {
        //Holder styr på ViewModels
        ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

        //Tilføj linje her for hver ViewModel
        SimpleIoc.Default.Register<MainViewModel>();
        SimpleIoc.Default.Register<MainViewModelTest>();
        SimpleIoc.Default.Register<MenuViewModel>();
    }

    //Tilføj metode som denne for hver ViewModel
    public MainViewModel Map
    {
        get
        {
            return ServiceLocator.Current.GetInstance<MainViewModel>();
        }
    }

    public MainViewModelTest Main
    {
        get
        {
            return ServiceLocator.Current.GetInstance<MainViewModelTest>();
        }
    }

    public MenuViewModel Menu
    {
        get
        {
            return ServiceLocator.Current.GetInstance<MenuViewModel>();
        }
    }

    public static void Cleanup()
    {
            // TODO Clear the ViewModels
    }
}

I have looked into the link I reference above resetting all instances in IOC Container. But are unsure how the key works, and how to make sure the cleanup function is called when navigating away from the views. Since I would not want to clean all the viewmodels at the same time.

Navigation And viewmodelbinding

I bind my viewmodel to the view as

DataContext="{Binding Source={StaticResource Locator},Path=Map}"

I navigate back and forth using NavigationService and backbutton. From Menu to Game:

NavigationService.Navigate(new Uri("/View/MainPage.xaml", UriKind.Relative));

and in the page

protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        //e.Content = NavigationMode.New;
        //e.NavigationMode = NavigationMode(
        ViewModel.MainViewModel test = new ViewModel.MainViewModel();
        GC.Collect();
        base.OnNavigatedTo(e);
    }

and from Game to Menu:

protected override void OnNavigatedFrom(NavigationEventArgs e)
    {
        //e.NavigationMode = NavigationMode.
        this.DataContext = null;
        GC.Collect();
        base.OnNavigatedFrom(e);
        //test = null;
    }

And in the menu I invoke the garbage collector. As can be seen I break the MVVM structure to accommodate the problem.

Community
  • 1
  • 1
JTIM
  • 2,774
  • 1
  • 34
  • 74
  • I think you will get more specific answers if you can post a specific sample demonstrating the problem. But in general, I think the point of a view model is to *persist* data across page loads, so you should not be suprised that the 2nd page view retains data from the first view. You can handle this by moving some of the page init out of the constructor into a function called from constructor and also from page loaded event. – BobHy Dec 14 '13 at 14:21
  • That is exactly what I have done. But there is a memory issue, since the viewmodel is not cleared upon navigating away. And since The datacontext is set to the view I cannot get access to the viewmodel behind. What I had to do so far was setting the datacontext = null, and calling GC.collect(). But this is not a nice way to do it. I wanted to know what is the correct way. – JTIM Dec 14 '13 at 16:11
  • 1
    Please post a sample showing what you're currently doing (with comments showing what you would like to have happen). Having the view model V1 for a given page P1 still be in memory when P1 is loaded a second time, that is what's supposed to happen. Are you seeing some other view model in P1.DataContext? – BobHy Dec 14 '13 at 20:08
  • @BobHy I have Created a small sample That I made a rar file and included in the text. – JTIM Dec 14 '13 at 21:50

1 Answers1

4

Properties of your ViewModelLocator return Singletons. To make the property return a new instance each time you could simply write:

private int questCount;

public Question Quest
{
    get
    {
        return ServiceLocator.Current.GetInstance<Question>((++questCount).ToString());
    }
}

However, it will result in Question ViewModel caching. You need to release unused ViewModels by closely following the answer you linked. This results in my opinion in too much code for a simple result. There are other IOC Containers that you could use in place of SimpleIoc on Windows Phone (like ninject or unity), which may be better suited for your needs.

In a simple project (a couple-of-pages app), especially in the case of not having to much experience with IOC container, I would advise you to abandon all that SimpleIoc wiring and just call the constructor:

public Question Quest
{
    get { return new Question(); }
}
lisp
  • 4,138
  • 2
  • 26
  • 43
  • Thank you for your reply, my project is rather big and I am using three viewmodels. I have tried implementing the cleanup function without getting it to work as I was unsure how to call cleanup, And how to ensure that the cleanup will be done to all variables, even if they are static. I can do the code that I linked to and get it working, but I do not undestand how the key is working. So I am not much for using code, I do not know how to maintain. So I would like to use SimpleIoc since I am using MVVM light, but unsure how to do it. I will updatae my question with my Viewmodellocator. – JTIM Mar 18 '14 at 09:51
  • @JTIM Three ViewModels with very simple constructors (judging by your SimpleIoc setup), are *definitely* the case where I would *not* use an IOC container. Explicit constructor calls will yield much less code that you definitely understand. – lisp Mar 18 '14 at 10:55
  • @JTIM As for SimpleIoc - GetInstance returns the same instance, GetInstance with given key yields the same instance for the given key. Unregister allows deleting an instance assigned to the key. That's just SimpleIoc specification. Which makes it usefull in some scenarios, in some it makes it hard to apply. SimpleIoc is just one of the helpers in MVVMLight - you don't have to use all of them in every project. ViewModelLocator can be easily written without it. – lisp Mar 18 '14 at 10:59
  • okay, I did not know that. But still it is hard for me to understand, because this is what I learned :) But if do not use the ViewmodelLocater I have to interface the datacontext myself, in the views. Could I somehow convince you to make a sample ? I would not know how to do it, I have made some work arounds, but they are not pretty, and I would therefore love another way to do it. – JTIM Mar 18 '14 at 11:55
  • @JTIM I *do* use ViewModelLocator (for binding DataContext in .xaml), but I *don't* use SimpleIoc. ViewModelLocator properties are like in the answer (`get { return new QuestionViewModel(); }`). Each time new Page is opened, new ViewModel is created and I let GarbageCollector do the rest (when the page is closed, the ViewModel is removed). – lisp Mar 18 '14 at 12:03
  • This is kind of what I did as a solution, but in Windows phone, the screens are not removed when I navigate away, which was the problem. So when I navigated back the constructor was not called, but I could call the constructor in the loaded event. But this is not so pretty. But okay, how do you make a page get closed to do this? I remove the navigation backstack and everything, but still when navigating back the constructor of the view is not called only the loaded event? – JTIM Mar 18 '14 at 16:18
  • @JTIM in Windows Phone the Pages *are* removed when **pressing back-key** (or simulating back-key navigation with NavigationService.GoBack). No need to programatically clear the back-stack then. In your case it might have seemed that you were using the same View, because your ViewModelLocator returned the same ViewModels. I bind to ViewModel in .xaml using ViewModelLocator, I don't use IOC container and always when navigating to a new page I get a new instance of ViewModel. Perhaps there's something wrong with how you perform navigation? – lisp Mar 19 '14 at 08:29
  • I have updated the question with how I do the navigation, I do a bit like you say but I am still using the IOC. So I remove datacontext when I navigate away, to enable the garbage collector to catch it. And then I create a new viewmodel on navigating to. As you can see, the garbage collector is used to extensively, because this was done during testing, I know I can remove some of them :) But you do it like this, but changed the viewmodel Locator to not use the SimpleIoc? – JTIM Mar 19 '14 at 09:29
  • @JTIM Have you actually tried using explicit constructors instead of SimpleIoc? (Setting DataContext to null has no effect as the ViewModel is still stored in SimpleIoc and will be used next time you navigate to Map.) – lisp Mar 19 '14 at 11:12
  • Okay I did not know that. No I have not I am unsure how to bind this, if I do not do it like You see, if you could show me in a sample or some basic code I would much appreciate it! – JTIM Mar 19 '14 at 15:24
  • @JTIM I have shown you a sample - in my answer. Instead of using SimpleIoc in your ViewModelLocator properties, call constructor. The rest - Bindings, Navigation etc. remains unchanged. – lisp Mar 20 '14 at 09:04
  • If the constructor have a parameter how to pass the parameter value in the ViewModelLocator ? I mean i have parameter like IFrameNavigationService in my viewmodel, please advice. – Ragesh P Raju Dec 12 '19 at 06:56