Issues with PRISM Navigation removing Views

Topics: Prism v4 - Silverlight 4
Apr 11, 2011 at 3:28 PM

I have created a custom region adaptor for a SilverLight Docking framework and am able to add Windows into said region adaptor via the RegionManager.RequestNavigate approach.

I have two issues that I can’t explain.

The first issue is that when I attempt to remove the application I cannot locate the View by its name.

This is how I add the View to the region which creates and displays my view without issue …

RegionManager.RequestNavigate(ShellRegionIdentifiers.DockSite, new Uri("OnHoldActionDialogView", UriKind.Relative));
 
Later on though, when I attempt to locate the view using this approach it returns a null …

var view = RegionManager.Regions[ShellRegionIdentifiers.DockSite].GetView("OnHoldActionDialogView");

 

So instead I resorted to this approach which does find the View;

 

var view = RegionManager.Regions[ShellRegionIdentifiers.DockSite].ActiveViews.First(); 

 

 

This does get me the view but is not how I wish to leave my code, so why can’t I access the View by Name since it opens OK, I would think that I should be able to access the view using the GetView. Within the Visual Studio debugger I am able to see the View is active. Any ideas?

The second issue is using the view location approach of RegionManager.Regions[ShellRegionIdentifiers.DockSite].ActiveViews.First() to locate my view I get an exception with I call the remove method passing in the view …

 var view = RegionManager.Regions[ShellRegionIdentifiers.DockSite].ActiveViews.First();

 if(view != null)

   RegionManager.Regions[ShellRegionIdentifiers.DockSite].Remove(view);

 

The interesting part of this is that as I mentioned, I am using my own Custom Region Adapter and when I call Remove it does call through my adapter and does remove the view from the Region. I can see that the exception copied below is actually calling the Remove twice. Why is that?

   at Microsoft.Practices.Prism.Regions.Region.GetItemMetadataOrThrow(Object view)

   at Microsoft.Practices.Prism.Regions.Region.Remove(Object view)

   at Microsoft.Practices.Prism.Regions.Behaviors.RegionMemberLifetimeBehavior.OnActiveViewsChanged(Object sender, NotifyCollectionChangedEventArgs e)

   at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)

   at Microsoft.Practices.Prism.Regions.ViewsCollection.OnCollectionChanged(NotifyCollectionChangedEventArgs e)

   at Microsoft.Practices.Prism.Regions.ViewsCollection.NotifyRemove(IList items, Int32 originalIndex)

   at Microsoft.Practices.Prism.Regions.ViewsCollection.RemoveFromFilteredList(Object item)

   at Microsoft.Practices.Prism.Regions.ViewsCollection.SourceCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)

   at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)

   at System.Collections.ObjectModel.ObservableCollection`1.RemoveItem(Int32 index)

   at System.Collections.ObjectModel.Collection`1.Remove(T item)

   at Microsoft.Practices.Prism.Regions.Region.Remove(Object view)

   at AUBPOS.Assets.QPMControls.Tasks.ViewModel.TaskLogsViewModel.ResumeTaskLogCancel()

   at AUBPOS.Assets.QPMControls.Tasks.ViewCommands.ResumeTaskLogCancelCommand.Execute(Object parameter)

   at System.Windows.Controls.Primitives.ButtonBase.ExecuteCommand()

   at System.Windows.Controls.Primitives.ButtonBase.OnClick()

   at System.Windows.Controls.Button.OnClick()

   at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)

   at System.Windows.Controls.Control.OnMouseLeftButtonUp(Control ctrl, EventArgs e)

   at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, Int32 actualArgsTypeIndex, String eventName)

Apr 11, 2011 at 3:40 PM

I just figured out the second issue.. I was implementing the IRegionMemberLifetime KeepAlive in my view but if I remove this interface I am able to remove and navigate back the view as many times as requested. Why is that?

 

Developer
Apr 11, 2011 at 6:08 PM
Edited Apr 11, 2011 at 6:08 PM

Hi,

The Uri you're specifying in the RequestNavigate method is used to determine the contract name that will be used when resolving the view, which in the default ContentLoader does not add the view with that name in to a region. It's worth noting that the name of a view inside a region isn't necessarily the same as the contract name it was used to export it.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Apr 11, 2011 at 6:51 PM

Hi,

If I am following what you say, then using the default ContentLoader I am not able to retrieve a view by it's name. 

How do I get to a point where I can get back my view by name?

ie.

var view = RegionManager.Regions[ShellRegionIdentifiers.DockSite].GetView("OnHoldActionDialogView");

 Do I need to implement my own custom content Loader?  I looked at the PRISM developers guide and it shows how to over-ride the GetContractFromNavigationContext.... Is this what I need to implement and if so, how do I wire that into the MEF Bootstraper?

 

protected override string GetContractFromNavigationContext(NavigationContext
navigationContext)
{
string contract = base.GetContractFromNavigationContext(navigationContext);
...
return contract;
}


 

Developer
Apr 12, 2011 at 3:19 PM

Hi,

As explained in this blog post by Karl Shifflett, when performing the navigation request, the Navigation Service will attempt to locate the view in the region in which you're navigating to. If it can't find it, it will ask the ServiceLocator to find the view. This is done in the RegionNavigationContentLoader.LoadContent method, as shown in the code below:

public object LoadContent(IRegion region, NavigationContext navigationContext)
        {

            (...)

            view = this.CreateNewRegionItem(candidateTargetContract);

            region.Add(view);

            return view;
        }

If you'd like to add the view to the region with a name, you could create a custom IRegionNavigationContentLoader and change the LoadContent method so that the call to the IRegion.Add method looks like this:

string name = navigationContext.Uri.OriginalString;
region.Add(view, name, false);

Also, if you have used the default MEF Export tag, which defines the PartCreationPolicy as CreationPolicy.Shared, or if you have registered your instance as a singleton (by using the RegisterInstance method, or passing a ContainerControlledLifetimeManager in the RegisterType method) in Unity, you can obtain a reference to the view you've navigated to by calling the ServiceLocator.GetInstance<T> method, as the instance retrieved by it will be the same that was injected to the view.

You might find the following chapters from the Prism MSDN documentation useful:

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Aug 29, 2012 at 8:30 AM
Edited Aug 29, 2012 at 8:31 AM

I had same problem for closing views. I implemented following extension method:

 

        public static bool RemoveViewByViewModel(this IRegion region, object viewModel)
        {
            var view = region.Views.Cast<FrameworkElement>().SingleOrDefault(v => object.ReferenceEquals(v.DataContext, viewModel));

            if (view == null)
                return false;

            region.Remove(view);
            return true;
        }

This method use ViewModel to identify views. :P

Now everywhere in your ViewModel, if you want to close it's view, call it like this:

_region.RemoveViewByViewModel(this);
and your view will be closed. :)