ViewLocator problem in WPF

Topics: Conventions, UI Architecture
Jan 8, 2012 at 6:01 PM

First, I'm new to Caliburn.Micro.  In reviewing the samples, the HelloScreens example appeared to have the functionality I needed for a WPF application I'm developing.  So, I decided to try to convert it to WPF.


In doing so, I reduced the application to just having the Customers workspace, models and views.  The problem I'm having is displaying the correct view in the "Master" context.  I've tracked the problem down to the ViewLocator.LocateTypeForModelType function.  The problem appears to be the order in which the Types in the statement:

            var viewType = (from assembly in AssemblySource.Instance
                            from type in assembly.GetExportedTypes()
                            where viewTypeList.Contains(type.FullName)
                            select type).FirstOrDefault();

are returned.

The value of the context parameter in "Master".

In both WPF and Silverlight, viewTypeList is:

  • [0] "Caliburn.Micro.HelloScreens.Customers.CustomersWorkspace.Master"
  • [1] "Caliburn.Micro.HelloScreens.Customers.CustomersWorkspaceView"


However, the order of the Types returned by assembly.GetExportedTypes() is different between WPF and Silverlight.


In WPF, the values and their order are:

  1. [0] {Name = "IDialogManager" FullName = "Caliburn.Micro.HelloScreens.Framework.IDialogManager"}
  2. [1] {Name = "DialogConductorViewModel" FullName = "Caliburn.Micro.HelloScreens.Shell.DialogConductorViewModel"}
  3. [2] {Name = "IShell" FullName = "Caliburn.Micro.HelloScreens.Framework.IShell"}
  4. [3] {Name = "IWorkspace" FullName = "Caliburn.Micro.HelloScreens.Framework.IWorkspace"}
  5. [4] {Name = "IDocumentWorkspace" FullName = "Caliburn.Micro.HelloScreens.Framework.IDocumentWorkspace"}
  6. [5] {Name = "DocumentWorkspace`1" FullName = "Caliburn.Micro.HelloScreens.Framework.DocumentWorkspace`1"}
  7. [6] {Name = "IHaveShutdownTask" FullName = "Caliburn.Micro.HelloScreens.Framework.IHaveShutdownTask"}
  8. [7] {Name = "DocumentBase" FullName = "Caliburn.Micro.HelloScreens.Framework.DocumentBase"}
  9. [8] {Name = "ApplicationCloseCheck" FullName = "Caliburn.Micro.HelloScreens.Framework.ApplicationCloseCheck"}
  10. [9] {Name = "NullToCollapsedConverter" FullName = "Caliburn.Micro.HelloScreens.Framework.Converters.NullToCollapsedConverter"}
  11. [10] {Name = "CustomersWorkspaceViewModel" FullName = "Caliburn.Micro.HelloScreens.Customers.CustomersWorkspaceViewModel"}
  12. [11] {Name = "App" FullName = "Caliburn.Micro.HelloScreens.App"}
  13. [12] {Name = "ShellViewModel" FullName = "Caliburn.Micro.HelloScreens.Shell.ShellViewModel"}
  14. [13] {Name = "TransitioningContentControl" FullName = "Caliburn.Micro.HelloScreens.Framework.Controls.TransitioningContentControl"}
  15. [14] {Name = "CustomTransitionControl" FullName = "Caliburn.Micro.HelloScreens.Framework.Controls.CustomTransitionControl"}
  16. [15] {Name = "IMessageBox" FullName = "Caliburn.Micro.HelloScreens.Framework.IMessageBox"}
  17. [16] {Name = "MessageBoxViewModel" FullName = "Caliburn.Micro.HelloScreens.Shell.MessageBoxViewModel"}
  18. [17] {Name = "DialogConductorView" FullName = "Caliburn.Micro.HelloScreens.Shell.DialogConductorView"}
  19. [18] {Name = "DocumentWorkspaceState" FullName = "Caliburn.Micro.HelloScreens.Framework.DocumentWorkspaceState"}
  20. [19] {Name = "ScreensBootstrapper" FullName = "Caliburn.Micro.HelloScreens.Shell.ScreensBootstrapper"}
  21. [20] {Name = "MessageBoxOptions" FullName = "Caliburn.Micro.HelloScreens.Framework.MessageBoxOptions"}
  22. [21] {Name = "AddressViewModel" FullName = "Caliburn.Micro.HelloScreens.Customers.AddressViewModel"}
  23. [22] {Name = "ApplicationCloseStrategy" FullName = "Caliburn.Micro.HelloScreens.Shell.ApplicationCloseStrategy"}
  24. [23] {Name = "TiledBackground" FullName = "Caliburn.Micro.HelloScreens.Framework.Controls.TiledBackground"}
  25. [24] {Name = "CustomerViewModel" FullName = "Caliburn.Micro.HelloScreens.Customers.CustomerViewModel"}
  26. [25] {Name = "MessageBoxView" FullName = "Caliburn.Micro.HelloScreens.Shell.MessageBoxView"}
  27. [26] {Name = "ShellView" FullName = "Caliburn.Micro.HelloScreens.Shell.ShellView"}
  28. [27] {Name = "Detail" FullName = "Caliburn.Micro.HelloScreens.Customers.CustomersWorkspace.Detail"}
  29. [28] {Name = "CustomersWorkspaceView" FullName = "Caliburn.Micro.HelloScreens.Customers.CustomersWorkspaceView"}
  30. [29] {Name = "CustomerView" FullName = "Caliburn.Micro.HelloScreens.Customers.CustomerView"}
  31. [30] {Name = "Master" FullName = "Caliburn.Micro.HelloScreens.Customers.CustomersWorkspace.Master"}
  32. [31] {Name = "AddressView" FullName = "Caliburn.Micro.HelloScreens.Customers.AddressView"}


In Silverlight, the order and the values returned using the HelloScreens sample code are:

  1. [0] {Caliburn.Micro.HelloScreens.Customers.CustomerView}
  2. [1] {Caliburn.Micro.HelloScreens.Framework.IWorkspace}
  3. [2] {Caliburn.Micro.HelloScreens.Settings.SettingsViewModel}
  4. [3] {Caliburn.Micro.HelloScreens.Framework.Controls.CustomTransitionControl}
  5. [4] {Caliburn.Micro.HelloScreens.Framework.IHaveShutdownTask}
  6. [5] {Caliburn.Micro.HelloScreens.Framework.DocumentBase}
  7. [6] {Caliburn.Micro.HelloScreens.Orders.OrderViewModel}
  8. [7] {Caliburn.Micro.HelloScreens.Framework.Converters.NullToCollapsedConverter}
  9. [8] {Caliburn.Micro.HelloScreens.Framework.DocumentWorkspaceState}
  10. [9] {Caliburn.Micro.HelloScreens.Customers.CustomerViewModel}
  11. [10] {Caliburn.Micro.HelloScreens.Customers.AddressView}
  12. [11] {Caliburn.Micro.HelloScreens.Framework.IMessageBox}
  13. [12] {Caliburn.Micro.HelloScreens.Framework.IDialogManager}
  14. [13] {Caliburn.Micro.HelloScreens.Shell.DialogConductorView}
  15. [14] {Caliburn.Micro.HelloScreens.Orders.OrderView}
  16. [15] {Caliburn.Micro.HelloScreens.Shell.MessageBoxView}
  17. [16] {Caliburn.Micro.HelloScreens.Framework.IDocumentWorkspace}
  18. [17] {Caliburn.Micro.HelloScreens.Framework.DocumentWorkspace`1[TDocument]}
  19. [18] {Caliburn.Micro.HelloScreens.Framework.ApplicationCloseCheck}
  20. [19] {Caliburn.Micro.HelloScreens.Customers.CustomersWorkspace.Master}
  21. [20] {Caliburn.Micro.HelloScreens.App}
  22. [21] {Caliburn.Micro.HelloScreens.Shell.ApplicationCloseStrategy}
  23. [22] {Caliburn.Micro.HelloScreens.Shell.ShellView}
  24. [23] {Caliburn.Micro.HelloScreens.Orders.OrdersWorkspace.Master}
  25. [24] {Caliburn.Micro.HelloScreens.Orders.OrdersWorkspaceViewModel}
  26. [25] {Caliburn.Micro.HelloScreens.Customers.CustomersWorkspace.Detail}
  27. [26] {Caliburn.Micro.HelloScreens.Framework.Controls.TiledBackground}
  28. [27] {Caliburn.Micro.HelloScreens.Framework.IShell}
  29. [28] {Caliburn.Micro.HelloScreens.Shell.ShellViewModel}
  30. [29] {Caliburn.Micro.HelloScreens.Settings.SettingsView}
  31. [30] {Caliburn.Micro.HelloScreens.Orders.OrdersWorkspaceView}
  32. [31] {Caliburn.Micro.HelloScreens.Customers.CustomersWorkspaceView}
  33. [32] {Caliburn.Micro.HelloScreens.Shell.DialogConductorViewModel}
  34. [33] {Caliburn.Micro.HelloScreens.Customers.CustomersWorkspaceViewModel}
  35. [34] {Caliburn.Micro.HelloScreens.Orders.OrdersWorkspace.Detail}
  36. [35] {Caliburn.Micro.HelloScreens.Shell.MessageBoxViewModel}
  37. [36] {Caliburn.Micro.HelloScreens.Customers.AddressViewModel}
  38. [37] {Caliburn.Micro.HelloScreens.Framework.MessageBoxOptions}
  39. [38] {Caliburn.Micro.HelloScreens.Shell.ScreensBootstrapper}



As a result, in WPF the when the context is "Master" the statement:

            var viewType = (from assembly in AssemblySource.Instance
                            from type in assembly.GetExportedTypes()
                            where viewTypeList.Contains(type.FullName)
                            select type).FirstOrDefault();

returns Caliburn.Micro.HelloScreens.Customers.CustomersWorkspaceView.

In Silverlight, the result is Caliburn.Micro.HelloScreens.Customers.CustomersWorkspace.Master, which is correct.

In a nutshell, the .FirstOrDefault() clause appears to be the problem.

Any advice?

Thanks in advance.





Coordinator
Jan 8, 2012 at 6:19 PM

Is this using v1.2 or is this happening with the latest source?

Jan 8, 2012 at 6:36 PM

I'm using 1.3.0.0

Jan 8, 2012 at 9:38 PM

This has to do with the new UseEagerRuleSelection feature. When the option context argument is passed, the rule selection can't be eager. Can you add this if-block in ViewLocator and try it out?

            var viewTypeList = TransformName(viewTypeName, context);
            if (context != null) //<-- add this if block
            {
                viewTypeList = viewTypeList.Where(n => n.EndsWith("." + context));
            }

            var viewType = (from assembly in AssemblySource.Instance
                            from type in assembly.GetExportedTypes()
                            where viewTypeList.Contains(type.FullName)
                            select type).FirstOrDefault();

Keep in mind that this is just temporary code to determine if this will work. I'm going to eventually change the calling convention for NameTransformer.Transform() to allow overriding the UseEagerRuleSelection property value.

Jan 9, 2012 at 3:22 AM
Edited Jan 9, 2012 at 3:25 AM

I made a couple of changes:

  • ViewLocator.TransformName() will now apply a filter when a context is passed as an argument so that only the names with the "SomeBaseType.<context>" will be returned
  • The LINQ statement that enumerates the Types from the assemblies registered in AssemblySource will be searched according to the sequence of the name list returned by the NameTransformer rather than randomly based on how Assembly.GetExportedTypes() enumerates them. Here is the relevant change:
var viewType = (from assembly in AssemblySource.Instance
    from type in assembly.GetExportedTypes()
    where viewTypeList.Contains(type.FullName)
    select type).FirstOrDefault();

has been changed to:

var viewType = viewTypeList.Join(AssemblySource.Instance.SelectMany(a => a.GetExportedTypes()), n => n, t => t.FullName, (n, t) => t).FirstOrDefault();

Note the use of the Join() extension method to make this possible.

The analogous change was made to the ViewModelLocator as well.

Jan 9, 2012 at 3:19 PM

Anychance this could impact http://caliburnmicro.codeplex.com/discussions/285177?  I'm just trying to pull out the scenario into a standalone test.  I can pull down the source code later this evening and give it a whirl but off the top of your head could it be related?

Jan 9, 2012 at 3:30 PM

It may very well be related.

Jan 9, 2012 at 11:39 PM

The updates you provided have corrected the problem.  Thank you.

Feb 27, 2012 at 4:55 PM

DonHauri - How did you get on with converting HelloScreens from Silverlight to WPF? 

I don't suppose you have the source you could share for that?