WindowManager.ShowDialog doesn't not attach the correct view to the view model

Topics: Conventions, Framework Services
Jul 12, 2013 at 2:21 PM
Edited Jul 12, 2013 at 2:22 PM
Hi all,

I have ViewModels in my application that can be shown in a dialog or inside a panel in the shell dock layout manager and for that reason its View is a UserControl. The problem I'm having is that when I do WindowManager.ShowDialog, the View that its being attached to the ViewModel is the Window created by the WindowManager and not the ViewModel View.

I looked at the WindowManager code and its binding the ViewModel to the Window returned by the EnsureWindow method instead of the View returned by the ViewLocator.LocateForModel.

I thought in changing WindowManager to bind to the ViewModel View returned by the ViewLocator.LocateForModel and do a push request but I'm not sure if this is really a bug or was a conscious decision based on some scenarios I'm not aware of.

Can anyone enlighten me?
Jul 14, 2013 at 12:58 AM
can you show us how you're calling the method and where, also what you are supplying it?
Jul 15, 2013 at 5:10 PM
Edited Jul 15, 2013 at 8:32 PM
You can see this issue by creating the view model and the view bellow:

View model:
    public class FooViewModel : Screen
    {
        protected override void OnViewAttached(object view, object context)
        {
            base.OnViewAttached(view, context);

            var realAttachedView = GetView(context);

            Debug.Assert(view == realAttachedView, "view == realAttachedView");
        }
    }
View:
<UserControl x:Class="CliburnMicroProject.FooView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d"  Height="300" Width="300"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
            
    </Grid>
</UserControl>
then create the view model and show it using the window manager show dialog, it does not matter from where you call it:
            var fooViewModel = new FooViewModel();

            _windowManager.ShowDialog(fooViewModel);
If you run in debug mode you will see the assert failing, because the method GetView returns the Window generated to host the view model view, but the view that is actually passed to the OnViewAttached is the correct view (FooView) this is due to how the ViewAware class implements the AttachView method:
        void IViewAware.AttachView(object view, object context) {
            if (CacheViews) {
                Views[context ?? View.DefaultContext] = view;
            }

            var nonGeneratedView = View.GetFirstNonGeneratedView(view);

            var element = nonGeneratedView as FrameworkElement;
            if (element != null && !(bool) element.GetValue(PreviouslyAttachedProperty)) {
                element.SetValue(PreviouslyAttachedProperty, true);
                View.ExecuteOnLoad(element, (s, e) => OnViewLoaded(s));
            }

            OnViewAttached(nonGeneratedView, context);
            ViewAttached(this, new ViewAttachedEventArgs {View = nonGeneratedView, Context = context});
        }
if it should cache the view, its stores whatever is passed (this cache is used to return the view when you call GetView in the view model), then it gets the first non generated view and calls the OnViewAttached with the correct view. Since the window manager is calling ViewModelBinder with the generated window (EnsureWindow returns a generated Window if the view for the view model is not a Window) for the view model
        public virtual void ShowDialog(object rootModel, object context = null, IDictionary<string, object> settings = null) {
            var view = EnsureWindow(rootModel, ViewLocator.LocateForModel(rootModel, null, context));
            ViewModelBinder.Bind(rootModel, view, context);

            var haveDisplayName = rootModel as IHaveDisplayName;
            if(haveDisplayName != null && !ConventionManager.HasBinding(view, ChildWindow.TitleProperty)) {
                var binding = new Binding("DisplayName") { Mode = BindingMode.TwoWay };
                view.SetBinding(ChildWindow.TitleProperty, binding);
            }

            ApplySettings(view, settings);

            new WindowConductor(rootModel, view);

            view.Show();
        }
and I'm relying on the GetView, I'm getting the wrong view for the view model. Unless this was done this way for some particular reason I'm not aware of, it sounds like a bug.