This project is read-only.

Access to NavigationStack property

Jun 20, 2013 at 11:53 AM
Hi All,

We are implementing a BreadCrumb in our application and we would like to use the NavigationStack property to present the user the path followed to let him or her know where he or she is. The problem is that the NavigationStack property is protected, though we can't access it with the NavigationManager object.

We have created a CustomerNavigationManager that inherits from the NavigationManager and has a public property that returns the NavigationStack. We have also encapsulated the class with the Export(typeof(INavigationManagaer)). The problem is that the app crashes and we cannot debug it, because Visual Studio says it can't.

Here is the code of the Custom NavigationManager created:
[Export(typeof(INavigationBase))]
    public class CustomNavigationManager:NavigationManager
    {        
        [ImportingConstructor]
        public CustomNavigationManager(INavigationTarget navigationTarget, IViewFactory viewFactory, ILifetimeManager lifetimeManager, IStorageManager storageManager)
            :base(navigationTarget, viewFactory, lifetimeManager, storageManager)
        {

        }

        public Stack<INavigationEntry> CurrentNavigationStack
        {
            get { return this.NavigationStack; }        
        }     
    }
Does anybody knows how we an achieve this?

Thanks in advanced,
Pau
Jun 20, 2013 at 10:00 PM
Pau,

Thanks for your interest in the Okra App Framework. A breadcrumb is an interesting idea that I hadn't thought of before, and as you suggest being able to access this via the navigation manager is probably the best place. Off the top of my head I can't see why exposing the NavigationStack property would be a bad idea, although from a framework point of view this probably should be read-only. I'll look into adding this feature into the next release of the Okra App Framework.

CustomNavigationManager

Regarding your CustomNavigationManager workaround, I think the problem is in how MEF is composing the dependencies.

If you are using Export(typeof(INavigationManager)) [your text says INavigationManager which is correct, whilst your code shows INavigationBase] then MEF will fail composition because there are two exports that match, your custom navigation manager, and the default one provided with the framework. To get around this you will need to override you GetContainerConfiguration() method in your application bootstrapper to specify the Okra classes to register without the NavigationManager. You can see what you need to export in the OkraBootstrapper.GetOkraContainerConfiguration() method in the source code. This should result in the following method added to your bootstrapper,
protected override ContainerConfiguration GetContainerConfiguration()
{
    ConventionBuilder okraConventionBuilder = new ConventionBuilder();

    okraConventionBuilder.ForType<SettingsPaneManager>().Export<ISettingsPaneManager>().Shared();
    okraConventionBuilder.ForType<ActivationManager>().Export<IActivationManager>().Shared();
    okraConventionBuilder.ForType<SearchManager>().Export<ISearchManager>().Shared();
    okraConventionBuilder.ForType<ShareSourceManager>().Export<IShareSourceManager>().Shared();
    okraConventionBuilder.ForType<ShareTargetManager>().Export<IShareTargetManager>().Shared();
    okraConventionBuilder.ForType<LifetimeManager>().Export<ILifetimeManager>().Shared();
    okraConventionBuilder.ForType<StorageManager>().Export<IStorageManager>().Shared();
    okraConventionBuilder.ForType<LaunchActivationHandler>().Export<ILaunchActivationHandler>().Shared();

    return new ContainerConfiguration()
        .WithAssembly(typeof(INavigationManager).GetTypeInfo().Assembly, okraConventionBuilder)
        .WithAssembly(typeof(OkraBootstrapper).GetTypeInfo().Assembly);
        .WithAssembly(Application.Current.GetType().GetTypeInfo().Assembly);
}
You will also need to mark the custom navigation manager as 'Shared' (since this is a singleton) and mark the INavigationTarget parameter in the constructor as allowing default values (since if this is not provided then a default navigation target will be provided).
[Export(typeof(INavigationBase))]
[Shared]
public class CustomNavigationManager:NavigationManager
{        
    [ImportingConstructor]
    public CustomNavigationManager(([Import(AllowDefault = true)]INavigationTarget navigationTarget, IViewFactory viewFactory, ILifetimeManager lifetimeManager, IStorageManager storageManager)
        :base(navigationTarget, viewFactory, lifetimeManager, storageManager)
    {
    }

    public Stack<INavigationEntry> CurrentNavigationStack
    {
        get { return this.NavigationStack; }        
    }     
}

Alternative Method 1

I have one idea for an alternative method to build up a list of pages for a breadcrumb. Since the INavigationBase interface has events for when the user navigates (including whether the user is navigating forward or backward), you should be able to create a breadcrumb service that observes these events and recreates the list of pages in the navigation stack. You will need to import this into your bootstrapper to ensure that it is initialized at app startup, and it will probably also need to handle application suspension.

Alternative Method 2

Or... a simple alternative would be to download the Okra App Framework source code and just modify the NavigationStack property to be be public. You will also want to add this property into the INavigationManager interface so that it can be consumed elsewhere.

Sorry that this has been such a long response - as I mentioned at the start I'll look into adding this as a feature in the next release of the Okra App Framework. Until then maybe 'Alternative Method 2' would be a suitable workaround.

Let me know how you get on and any other problems you are having.

Regards,
Andy
Jun 21, 2013 at 1:52 PM
Hi Andy,

Thanks for this awesome answer. It helps me a lot!
I've solved overriding the GetContainerConfiguration() method as you've said.

Both alternatives are great, but we've prefered not to do them.

The Okra Framewoek works great and fits our needs properly. If we have more problems we'll let you know.

Thans,
Pau
Jun 23, 2013 at 5:03 PM
Hi Andy,

I've got another question.

We've been able to access the NavigationStack implementing a CustomNavigationManager that inherits the NavigationManager class and implementing a public property that returns the NavigationStack value and overriding the GetContainerConfiguration() as you've mentioned in the post above.

The problem know is the name of the page. In my application I've got, by now, 4 different types of pages. The application allows the user to navigate inside a CMS as it was a folder explorer, that's why the breadcrumb will be a great feature to be implemented. The application starts in a "Home" page (Okra page name is "Home" :)), and when the user selects one folder it goes to another page using the following sentence:
NavigateManager.NavigateTo("Group Detail", item);
So, as you can see the application sent a parameter called "item" with a Title property.
Once the users starts navigating through a folder it opens as many Group Detail pages as he needs, always with the PageName called "Group Detail"
The problem with this implementation is that the current NavigationStack connected to the BreadCrumb only can display de PageName property, because is the only public property from the INavigationEntry interface. Each item of the NavigationStack inherits from the NavigationEntry class, but as it is implemented as an internal class we cannot access the Arguments property, wich will suit our needs, because we will have access to the item Title property.

Do you have any idea on how to solve this issue?
Regards,
Pau
Jun 25, 2013 at 9:41 PM
Pau,

This one is going to a bit more tricky. As you have correctly identified the INavigationEntry interface only exposes the page name and not the arguments. I decided to make this internal as the arguments property may not actually be initialized under some circumstances - in particular after application suspension the arguments property for pages in the back stack are null and only rehydrated when a navigation occurs (the ArgumentsData property contains the serialized argument data).

Following your original question my plans for the next release of the Okra App Framework are to include a PageTitle property on INavigationEntry which would have a public getter/setter. When you navigate to a new page the view-model could then set the correct page title during the Activate method based upon the arguments or any other custom logic. This string would then be persisted upon suspension so will always be available. This of course doesn't help with the current release...

My best thought I have at the moment is to implement INavigationBase explicitly on your CustomNavigationManager. You could then provide new implementations of NavigateTo(...) and GoBack() that added the page titles to a list whenever navigation occurs.

I think something like the following should work (NB: I'm not on my dev machine so I'm doing this from memory and may have some syntax errors!)
[Export(typeof(INavigationBase))]
[Shared]
public class CustomNavigationManager:NavigationManager, INavigationBase
{        
    [ImportingConstructor]
    public CustomNavigationManager(([Import(AllowDefault = true)]INavigationTarget navigationTarget, IViewFactory viewFactory, ILifetimeManager lifetimeManager, IStorageManager storageManager)
        :base(navigationTarget, viewFactory, lifetimeManager, storageManager)
    {
         this.BreadCrumbs = new Stack<string>();
    }

    public Stack<string> BreadCrumbs
    {
        get;
        private set;   
    }

    void INavigationBase.NavigateTo(string pageName)
    {
        NavigateTo(pageName, null);
    }

    void INavigationBase.NavigateTo(string pageName, object arguments)
    {
        string pageTitle = pageName; // Insert custom logic here to use arguments if required

        BreadCrumbs.Push(pageTitle);

        base.NavigateTo(pageName, arguments);
    }

    void INavigationBase.GoBack()
    {
        BreadCrumbs.Pop();
        base.GoBack();
    }
}
Note that you would also need to implement some logic to handle storage of the breadcrumbs if the application is suspended, terminated, then later reactivated.

Sorry that I haven't got a straightforward solution to this. I will try to think of some other solutions, and long term address these issues in the next release of the Okra App Framework.

Andy
Jun 27, 2013 at 2:05 PM
Andy,

I've already solved the problem with an intermediate solution.
I've added a PageTitle propery in the viewmodelbase. When the BreadCrumbControl checks the CurrentNavigationStack property in the CustomNavigationManager it also calls the GetElements, which returns the viewmodelbase associated and the value of the PageTitle.

It works, so for this release it suits our needs!

Thanks for the answer and congrats for the framework!

Regards,
Pau
Jul 1, 2013 at 9:20 PM
Pau,

Good to hear that you have got a solution that fits your requirements. My only warning would be if you are using the built in support for suspension/termination. Following reactivation the navigation stack will be rehydrated, however the page elements may still be 'null' until they are first navigated to (they are rehydrated on demand for performance reasons).

Just something that would be worth testing before release.

Regards,
Andy
Jul 2, 2013 at 7:44 AM
Andy,

As this application is for internal use now we don't consider the posible problems for suspension / termination as an important fact. But anyway i'll test it before release :)

Regards,
Pau