Qusetions about navigation

Topics: Questions
Mar 28, 2013 at 9:54 AM
Edited Mar 28, 2013 at 9:59 AM
We finally started to writing our navigation system. And i have few questions about objects lifetime.

How my systems works atm. I have a few layers.

CppCli layer - converting data from native to managed types,native to managed callbacks mechanics. Use C++ library to read objects. Nothing special.
Managed layer - managed domain objects, data for which i fetch from C++ server (i.e. from CppCli layer). IModel's everywhere.
Screens Objects - object, which contains data, which will be displayed in UI. Combining objects from Managed Data Layer. This layer works like ViewModels data repository, to save data when vm die. Also here is navigation system. Every screen have reference to navigation service.
View Models - there is two kinds of view models. View models for screens, and view models for domain objects. Screens viewmodels get in their constructor IScreen object.
View - nothing special.

Navigation system:
INavigationItem - navigation object. Has path property with format "Configuration/FuelKindsScreen", and screen property with IScreen object.
INavigationTree - tree, which define navigation structure. Contains INavigationItem's.
INavigationHistory - allows to move around opened screens.
IScreenNavigationService - constructed with INavigationTree, have CurrentItem field, which is current screen to show. Have methods like NavigateTo(Path, Params), Back, Forward, Up.

Class WorkingScreen control the navigation - construct the tree, control navigation service lifetime.

WorkingScreenView have two main parts - navigation bar at top, and content area in the middle. Content area is a ContentControl and has binding to NavigationService.CurrentItem.Screen. Any commands to navigation service should change current Screen, current screen with DataTemplate should select corret view, then Catel should find correct ViewModel and everything should work great.

How it works ATM:
for example i have PosScreen - screen where operator should select what he wants to do. Few buttons to show baskets screen, tanks screen, etc.
In WorkingScreen i can call on init
NavigationService.NavigateTo("Pos", null, out res);
Every ScreenViewModel have command NavigateTo. When user wants to wotk with sales he press the button and execute command
<Button Content="Продажа" CommandParameter="Pos/SalesScreen" Command="{Binding Path=NavigateTo}"/>
private void OnNavigateToExecute(string Path)
{
    Debug.Assert(ScreenNavigationService != null);

   if (ScreenNavigationService == null)
        return;

    Result res;
    ScreenNavigationService.NavigateTo(Path, null, out res);
}
So there is a question - in command handler i'm changing CurrentItem in navigation service. As result current view is changing to another view. As i remember old view automatically destroying, and as view destroyed - view model will be destroyed too. But can it happen so fast and command, which live in viewmodel would be destroyed too, before control returns from method? Should i bind to another viewmodel with RelaitiveSource to be sure VM will be still alive?

Will i have any problems with my solution?

And thanks again for your work!
Mar 29, 2013 at 11:18 AM
Edited Mar 29, 2013 at 11:34 AM
And one more question. why Catel trying to create ViewModels with no parameters?

How it works now:
I have CurrentScreen property binded to ContentControl. User open sales screen:
CurrentScreen = SalesScreen;
Then user open POS screen:
CurrentScreen = PosScreen;
What i can see in log:
[DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.LogicBase] DataContext of TargetControl 'SalesView' has changed to 'null'
...
[INFO] [Catel.MVVM.ViewModelBase] Closed view model 'GasKitUILib.ViewModels.Pos.Sales.SalesViewModel'
[DEBUG] [Catel.MVVM.ManagedViewModel] Removed view model instance, currently containing '0' instances of type 'GasKitUILib.ViewModels.Pos.Sales.SalesViewModel'

Ok, it's fine. But then:
[DEBUG] [Catel.IoC.TypeFactory] No constructor could be used, using Activator.CreateInstance. Cannot cache this either.
[DEBUG] [Catel.MVVM.ViewModelBase] Creating view model of type 'SalesViewModel' with unique identifier 17
[DEBUG] [Catel.MVVM.ViewModelBase] Using the default instance of the IServiceLocator
Why?

Then Catel starts to to construct ViewModel which i expect.
[DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.LogicBase] Constructing behavior 'UserControlLogic' for 'PosView' with unique id '14'
...
The problem is - when user select SalesScreen again and select basket i got exception:
public SalesViewModel(IScreen SalesScreen)
{
    Argument.IsNotNull("SalesScreen", SalesScreen);

    this.SalesScreen        = SalesScreen as ISalesScreen;
    ScreenNavigationService = this.SalesScreen.NavigationService;
    SalesManager            = this.SalesScreen.SalesManager;

    InitSalesViewModel();
}

[ViewModelToModel("SalesManager")]
public IBasket SelectedBasket
{
    get { return GetValue<IBasket>(SelectedBasketProperty); }
    private set
    {
        SetValue(SelectedBasketProperty, value);
    }
}

public static readonly PropertyData SelectedBasketProperty = RegisterProperty("SelectedBasket", typeof (IBasket),
                                                                                null, (sender, e) => ((SalesViewModel)sender).SelectedBasketPropertyChangedEventHandler(sender, e));

private void SelectedBasketPropertyChangedEventHandler(object Sender, AdvancedPropertyChangedEventArgs ChangedEventArgs)
{
    if (ChangedEventArgs.NewValue != null)
    {
        Debug.WriteLine("SelectedBasket changed. VM id = {0}", this.UniqueIdentifier);
        Result res;

        IBasket basket = (IBasket) ChangedEventArgs.NewValue;
        Dictionary<string, object> basketData = new Dictionary<string, object>();
        basketData.Add("Basket", basket);
        ScreenNavigationService.NavigateTo("Pos/Sales/Basket", basketData, out res); <------ NullReferenceException
    }
}
Sender is a ViewModel object, which was constructed without parameters and have no navigation service injected.
And in loge i found very strange thing:

SelectedBasket changed. VM id = 20
Open screen <Pos/Sales/Basket>
SelectedBasket changed. VM id = 10
Open screen <Pos/Sales/Basket>
SelectedBasket changed. VM id = 17

Whole log file. I did simple steps - open Pos screen, open Sales screen, then again open Pos screen, againSales screen. Then i select basket.
http://pastebin.com/7XHXZrz5

Looks like SalesViewModel objects still listening events. Any solutions?

Added.
Ok, i understood how it works. Looks like memory leak, but with events. As i dont know how to unsubscribe from events with PropertyData i rewrite this part of my code with default PropertyChangedEvent. If there is any other solutions - i will be happy to hear it.

Current solution:
protected override void OnClosed(bool? result)
{
    base.OnClosed(result);
    SalesManager.PropertyChanged -= SalesManagerOnPropertyChanged;
}

public SalesViewModel(IScreen SalesScreen) : base()
{
    Argument.IsNotNull("SalesScreen", SalesScreen);

    this.SalesScreen        = SalesScreen as ISalesScreen;
    ScreenNavigationService = this.SalesScreen.NavigationService;
    SalesManager            = this.SalesScreen.SalesManager;

    SalesManager.PropertyChanged += SalesManagerOnPropertyChanged;

    InitSalesViewModel();
}
Coordinator
Mar 30, 2013 at 6:17 AM
"And one more question. why Catel trying to create ViewModels with no parameters?"

It goes from the one with the most to the one with the least. If it cannot find any parameter to inject, it will go to the next ctor. Eventually it will end up calling the one with no parameters.
Coordinator
Mar 30, 2013 at 6:19 AM
If you are dealing with memory leaks, take a look at the WeakEventListener if you don't have a location where you can unsubscribe. However, overriding the close methods is exactly for this scenario :-)