Can't move a view into a Popup if it contains VM bound ContentControls

Mar 29, 2011 at 4:15 AM
Edited Mar 29, 2011 at 4:17 AM

I have code that can full screen any element (along with all it's children) but it won't work if: 

1. the element is a View which is created by CM via binding a VM to a ContentControl.    Ex. <ContentControl name="VMName" />

2. Any view inside the element which is created by CM via VM ContentControl binding (same as above, except contained within the element).

The difference between #1 and #2 is #2 does fullscreen the element, it just won't display the ContentControl view.  #1, however, causes an ArgumentException and crashes.


The code basically looks like this, except it is implemented as an attached method to UIElement (therefore I can full screen any element).  Here's the absolute fundamentals which you can use to 

reproduce the issue:


<UserControl Name="Element">
<ContentControl Name="VMPublicPropertyThatHasAnAssociatedView" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" />
Element.Parent.Content = null;  // remove the UIElement from the Visual Tree
var popup = new Popup(); 
popup.Child = Element; // re-add the element to the visual tree.  Now the CM bound VM will lose it's associated View for some reason. 



I really didn't expect this because I thought that once Caliburn created the view and binded it to the "VMPublicPropertyThatHasAnAssociatedView"'s content control, that the VisualTree could now be manipulated without regard to any of Caliburn's black magic.  It seems however, that the view needs to be recreated and rebound if I move it to the new Popup parent.  Can anyone shed light?

I would be happy to read any suggested reading on the subject, since clearly my understanding is flawed.

Mar 29, 2011 at 9:28 AM

It could be nice to have a stack trace for the exception, otherwise it is quite hard to understand what's going on.

Nevertheless, I have some doubts about the way you are achieving this 'fullscreen mode'.

I tend to think that swapping controls 'by hand' is not the way to go, since WPF/Silverlight controls are inherently tied up to the branch of the Visual Tree they are attached to (DependencyProperty inheritance). What I would do, in your case, is creating a new view to be used in the Popup.

The issue is how you create the same view inside another control... without screwing up view caching. The answer is using contextes (a context is just a way to identify different views for the same view-model and is the 'key' used to index view caches in the CM Screen implementation), and doing it in a smart way.

Tipically, different contextes are related to different view classes, but in your case I would avoid to replicate views, that's why I would customize the framework a bit.

First of all, let's define a specific context for your fullscreen views (e.g. Fullscreen) and change the ViewLocator to ignore such context when resolving the type name:

        /// <summary>
        /// Locates the view type based on the specified model type.
        /// </summary>
        /// <returns>The view.</returns>
        /// <remarks>Pass the model type, display location (or null) and the context instance (or null) as parameters and receive a view type.</remarks>
        public static Func<Type, DependencyObject, object, Type> LocateTypeForModelType = (modelType, displayLocation, context) => {
            context = context != null ? (context.ToString().Equals("Fullscreen") ? null : context.ToString().Replace("Fullscreen", string.Empty) ) : context; //<- Identify 'virtual' context
            var viewTypeName = modelType.FullName.Substring(0, modelType.FullName.IndexOf("`") < 0
                ? modelType.FullName.Length
                : modelType.FullName.IndexOf("`")
                ).Replace("Model", string.Empty);

            if (context != null)
                viewTypeName = viewTypeName.Remove(viewTypeName.Length - 4, 4);
                viewTypeName = viewTypeName + "." + context;

            var viewType = (from assmebly in AssemblySource.Instance
                            from type in assmebly.GetExportedTypes()
                            where type.FullName == viewTypeName
                            select type).FirstOrDefault();

            return viewType;

This way the Fullscreen context is not really used to resolve views, and is just a place holder (hence the definition of 'virtual') to avoid to screw-up CM view-caching.

Once the modification is done, you can use View.Model to inject the view into the popup:

var viewModel = Element.DataContext;
var fullscreenContent = new ContentContol();
View.SetContext(fullscreenContent, "Fullscreen");
View.SetModel(fullscreenContent, viewModel);
var popup = new Popup() { Child = fullscreenContent }; 


Is this a possible solution for you?

Mar 29, 2011 at 4:24 PM

Hey BladeWise, 

Thanks for your insights.  Your solution works to a degree.  The issue is that all the child controls which were not specified in XAML (i.e. dynamically added) will no longer appear within the full screen view.

1. The shortest way I know of to achieve fullscreening the entire visual tree of an element at runtime, is to edit the visual tree by hand (the way I'm currently doing it).  

2. The other way I could do it is utilizing your solution mixed with a "replay" which would rebuild all the dynamic elements that were currently visible when the user clicked "Fullscreen".

Using #2 will unfortunately open up a lot of surface area for bugs, which is why I was hoping to take the short route.  

Do you have any other suggestions?  


(To address the issue of my failure to provide stack traces: in the above post #2 doesn't crash so no stack trace, and #1's stack trace talks about XcpImports and all kinds of other library code calls which don't

seem to ever tie into any of my application code or the caliburn micro code.  "ArgumentException: Value out of Range" --> XcpImports... blah blah blah")



Mar 29, 2011 at 5:29 PM

I would still go with solution #2.

If a proper MVVM pattern is applied, even dynamic parts should have an underlying VM. This means that as long as the VM is consistent, no bogus behaviour should happen (if the original view and the fullscreen view are both generated dynamically from the VM, I don't see why bugs should occur).

Are you implying that in you application there is a dynamic part that has no underlying VM? If so, I can understand your concern, but I still would encourage you to re-design that part.


If solution #2 is really a no-go, can you trying doing something like this:

var parent = Element.Parent;
parent.Content = null;  // remove the UIElement from the Visual Tree
var popup = new Popup { Child = Element, DataContext = parent.DataContext }; // re-add the element to the visual tree.


I'm just guessing here, but you should check if the DataContext of the View is set properly... I haven't checked if CM sets the DataContext on the host ContentControl or the View itself, but it is worth a try.

I suppose the reason you are getting the exception is that some other control is not happy with your re-parenting...

Mar 29, 2011 at 8:04 PM

I have a 2D simulation running inside a canvas which does not use MVVM to save performance and code bloat.  The rest of the App uses MVVM.

However, there are certain overlays which can appear that are MVVM based which is where my problem begins.  Because I can full screen everything using my method, except all the Caliburn binding stuff 

doesn't work, so none of the overlays will appear.


DataContext was the first thing I thought of as well.  Unfortunately that's not the problem.

I'll evaluate option #2 in more detail and try to provide more information to help narrow down why the Views that were created by CM are not showing up when I change the parent of the top UserControl

to a Popup.

Mar 29, 2011 at 8:30 PM

SOLVED: the reason was because I the top level UserControl was within a TransitioningContentControl and this was indeed making other elements in the Visual Tree unhappy!  

Switching TransitioningContentControl to just ContentControl fixed my issue =D

Thanks again for your help, BladeWise.