Wrong VM selected and Designer Support

Mar 21, 2012 at 2:38 PM
Edited Mar 21, 2012 at 2:38 PM

Hi,

I started to work with MVVM/Catel not long ago. I did some work with Catel 2.0 and now trying Catel 3.0

I have several concerns:

1. I created a UserControl (AlgorithmSettingsView) and override GetViewModelType() to return corresponding VM - AlgorithmSettingsViewMode. When the application runs instead of taking an instance of AlgorithmSettingsViewMode it is taking an instance of MainWindowViewModel. I put break point in the override of GetViewModelType, I see it is being called and then in the event handler of DataContextChanged the new value is the wrong VM. I have the same behavior in another View/ViewModel that works fine... I try to compare them and they look exactly the same... What can be the issue? How can I debug this.

2. Designer support - I want to keep my VM alive so I need to use the CloseViewModelOnUnLoad to false. If I do it in the constructor of the view, I get a designer error (null reference exception). What I did is added an event handler for DataContentChanged in the view and set the property to false only if the new data context is not null. This works fine (at least in one place) but kind of violates the no-code-behind policy of MVVM. Is there a better way to do it? The same thing happens if I trying to use behaviors in XAML:

<i:Interaction.Behaviors>
        <catel:UserControlBehavior ViewModelType="vm:AlgorithmSettingsViewModel"/>
 </i:Interaction.Behaviors>

btw, if you set
CloseViewModelOnUnLoad in XAML is also throws an exception:
"Error    2    Exception has been thrown by the target of an invocation."

3. When should I use DataWindow? If I don't need the extra behavior of it - can I just use regular window with Catel MVVM? How do I tell such view which VM to use? Is there a way to hide the Cancel/OK buttons of DataWindow?

 

Thanks,

Tomer

Coordinator
Mar 21, 2012 at 2:58 PM

1) First, the context indeed is the main view model (that is the initial DC). Then, it will be transformed into the AlgorithmSettingsViewModel if possible (using injection or a default constructor). In 3.1, this will be better separated (and you will no longer receive in-between view models).

2) If you use a behavior, then you have to set the "CloseViewModelOnUnloaded" after the call to InitializeComponent because that call initializes the behavior. You are talking about the "no-code-behind policy of MVVM". We are very, very strict in MVVM, but we allow code-behind. Simply put: code-behind is valid as long as it contains no logic that needs to be tested or has business logic. For example, setting a theme or applying a user control template is perfectly valid in the code-behind.

3) Yes, you can hide everything the DataWindow generates (such as the InfoBarMessageControl, buttons, etc). Use the right overloads of the DataWindow constructor and pass DataWindowMode.Custom.

Good luck!

Mar 21, 2012 at 3:57 PM

Hi Greet,

Thanks for the prompt reply.

As for 2 and 3 - I will look into it later.

The issue 1 though - I am still not understanding what the problem is.

In order to debug I made the AlgorithmSettingsView the start-up URL (in App.xaml) and even then, it does not bring the correct VW.

A more complete picture of the project - I have in my MainWindowView a TabControl that contains two TabItems, each contains a user control - ExecutionDetailsView and AlgorithmSettingsView. The initially selected tab is the one showing ExecutionDetailsView and this one fetches the VM as per its override of GetViewModelType() - it does so when starting with MainWindowView or when I am making it the initial URL.

The other view does not fetch the correct VM no matter what I do.

Is there a way to see why this happens? How can I debug it? I assume I am doing something wrong but can't see what it is.

Coordinator
Mar 21, 2012 at 4:10 PM

Does the view model have an empty constructor, or does it expect an input (injection) model (in other words: does it have an empty constructor or not)? You can call this in your app.xaml.cs, and you will have lots and lots of info in your output window that shows you exactly what is happening:

LogManager.RegisterDebugListener();

Mar 21, 2012 at 4:47 PM

The view model has an empty constructor and this is very basic project - I am not using any IoC (expect for what comes with Catel)

I enabled the debug listeners and what I get is below. I made a change - just created an empty window (regular WPF) that host my 2 user controls

One of the user controls works perfectly (green lines) fine and the other (red) does not.

06:34:49:345 => [DEBUG] [Catel.Logging.LogManager] Added listener 'Catel.Logging.DebugLogListener' to log manager
06:34:49:376 => [DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.LogicBase] Constructing behavior 'UserControlLogic' for 'ExecutionDetailsView'
06:34:49:382 => [DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.LogicBase] Constructed behavior 'UserControlLogic' for 'ExecutionDetailsView'
'Pop.vshost.exe' (Managed (v4.0.30319)): Loaded 'D:\Documents\Tomer\Visual Studio 2010\Projects\POP\output\Debug\WPFToolkit.Extended.dll'
06:34:52:329 => [DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.LogicBase] Constructing behavior 'UserControlLogic' for 'AlgorithmSettingsView'
06:34:52:330 => [DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.LogicBase] Constructed behavior 'UserControlLogic' for 'AlgorithmSettingsView'
'Pop.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\UIAutomationProvider\v4.0_4.0.0.0__31bf3856ad364e35\UIAutomationProvider.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
06:34:52:632 => [DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.LogicBase] Target control 'ExecutionDetailsView' is loaded
06:34:52:636 => [DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.UserControlLogic] Searching for an instance of the InfoBarMessageControl
06:34:52:640 => [DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.UserControlLogic] Finished searching for an instance of the InfoBarMessageControl
06:34:52:641 => [WARNING] [Catel.Windows.Controls.MVVMProviders.Logic.UserControlLogic] No InfoBarMessageControl is found in the visual tree of 'UserControlLogic', consider using the SkipSearchingForInfoBarMessageControl property to improve performance
06:34:52:645 => [DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.UserControlLogic] Couldn't find parent view model container
06:34:52:658 => [DEBUG] [Catel.MVVM.ViewModelManager] ViewModelManager instantiated
06:34:52:690 => [DEBUG] [Catel.MVVM.ViewModelBase] Creating view model of type 'ExecutionDetailsViewModel' with unique identifier 1
06:34:52:691 => [DEBUG] [Catel.MVVM.ViewModelBase] Using the default instance of the IServiceLocator
06:34:52:692 => [DEBUG] [Catel.MVVM.ViewModelBase] Registering view model services
06:34:52:695 => [DEBUG] [Catel.MVVM.ViewModelBase] Registering default service implementations for IoC container
06:34:52:699 => [DEBUG] [Catel.MVVM.ViewModelBase] Registered default service implementations for IoC container
06:34:52:699 => [DEBUG] [Catel.MVVM.ViewModelBase] Registered view model services
06:34:52:707 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'Title' as type 'String' in mode 'TwoWay'
06:34:52:708 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'ExecutionDetails' as type 'ExecutionDetails' in mode 'TwoWay'
06:34:52:709 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'BrowsePmDirectoryCommand' as type 'Command' in mode 'TwoWay'
06:34:52:710 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'BrowseCmDirectoryCommand' as type 'Command' in mode 'TwoWay'
06:34:52:710 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'UniqueIdentifier' as type 'Int32' in mode 'TwoWay'
06:34:52:711 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'ViewModelConstructionTime' as type 'DateTime' in mode 'TwoWay'
06:34:52:711 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'IsClosed' as type 'Boolean' in mode 'TwoWay'
06:34:52:712 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'HasErrors' as type 'Boolean' in mode 'TwoWay'
06:34:52:713 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'HasDirtyModel' as type 'Boolean' in mode 'TwoWay'
06:34:52:713 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'KeyName' as type 'String' in mode 'TwoWay'
06:34:52:714 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'Mode' as type 'SerializationMode' in mode 'TwoWay'
06:34:52:715 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'IsInEditSession' as type 'Boolean' in mode 'TwoWay'
06:34:52:715 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'IsDirty' as type 'Boolean' in mode 'TwoWay'
06:34:52:716 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'IsReadOnly' as type 'Boolean' in mode 'TwoWay'
06:34:52:716 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'IsEditable' as type 'Boolean' in mode 'TwoWay'
06:34:52:717 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'Validator' as type 'IValidator' in mode 'TwoWay'
06:34:52:717 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'ValidationContext' as type 'IValidationContext' in mode 'TwoWay'
06:34:52:718 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'FieldWarningCount' as type 'Int32' in mode 'TwoWay'
06:34:52:718 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'BusinessRuleWarningCount' as type 'Int32' in mode 'TwoWay'
06:34:52:719 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'FieldErrorCount' as type 'Int32' in mode 'TwoWay'
06:34:52:720 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'BusinessRuleErrorCount' as type 'Int32' in mode 'TwoWay'
06:34:52:720 => [DEBUG] [Catel.MVVM.ViewModelBase+ViewModelPropertyDescriptor] Created property descriptor for 'HasWarnings' as type 'Boolean' in mode 'TwoWay'
06:34:52:729 => [DEBUG] [Catel.MVVM.ManagedViewModel] Added view model instance, currently containing '1' instances of type 'Pop.ViewModels.ExecutionDetailsViewModel'
'Pop.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.ComponentModel.DataAnnotations\v4.0_4.0.0.0__31bf3856ad364e35\System.ComponentModel.DataAnnotations.dll'
06:34:52:821 => [DEBUG] [Catel.MVVM.ViewModelBase] Found command 'BrowsePmDirectoryCommand' on view model 'ExecutionDetailsViewModel'
06:34:52:822 => [DEBUG] [Catel.MVVM.ViewModelBase] Found command 'BrowseCmDirectoryCommand' on view model 'ExecutionDetailsViewModel'
06:34:52:824 => [DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.LogicBase] DataContext of TargetControl 'ExecutionDetailsView' has changed to 'ExecutionDetailsViewModel'
06:34:52:826 => [INFO] [Catel.Windows.Controls.MVVMProviders.Logic.UserControlLogic] View model 'Pop.ViewModels.ExecutionDetailsViewModel' is automatically constructed by DataContext change
06:34:52:829 => [DEBUG] [Catel.MVVM.UI.ViewToViewModelMappingHelper] Initializing view model container to manage ViewToViewModel mappings
06:34:52:838 => [DEBUG] [Catel.MVVM.UI.ViewToViewModelMappingHelper] Initializing view model 'ExecutionDetailsViewModel'
06:34:52:840 => [DEBUG] [Catel.MVVM.UI.ViewToViewModelMappingHelper] Initialized view model 'ExecutionDetailsViewModel'
06:34:52:841 => [DEBUG] [Catel.MVVM.UI.ViewToViewModelMappingHelper] Initialized view model container to manage ViewToViewModel mappings
06:34:52:842 => [DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.LogicBase] Target control 'AlgorithmSettingsView' is loaded
06:34:52:842 => [DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.UserControlLogic] Searching for an instance of the InfoBarMessageControl
06:34:52:843 => [DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.UserControlLogic] Finished searching for an instance of the InfoBarMessageControl
06:34:52:843 => [WARNING] [Catel.Windows.Controls.MVVMProviders.Logic.UserControlLogic] No InfoBarMessageControl is found in the visual tree of 'UserControlLogic', consider using the SkipSearchingForInfoBarMessageControl property to improve performance
06:34:52:844 => [DEBUG] [Catel.Windows.Controls.MVVMProviders.Logic.UserControlLogic] Couldn't find parent view model container

here I'd expect similar code to the one from above initiating the ViewModel of the AlgorithmSettingsView but for some reason this does not happen...

Mar 21, 2012 at 5:21 PM

Sorry for writing here all that much.

I may have found something though it is bewildering...

In the problematic VM (one not being constructed) I have a property of a type that is defined in another project (external type from here on).

Why I mention this?

I tried to simplify the structure of the windows and put the 2 views (usercontrols+VM) on the same window without tabs or anything

One View-ViewModel combination works fine (this one expects a VM without external types)

Other View-ViewModel combination does not work (this one expect a VM with external types)

Now I tried to simplify the situation even more and moved the property that depends on the external type to the main window's VM

When I did that - also the first view-viewmodel combination (and is placed on the main window) that worked before stopped working

Now I created the external type as a local type, left the property in the main window's view model and again it works.

This leads me to a conclusion that there is some issue with using the external types - any VM that references an external types (defined elsewhere) do not load with the Catel base code.

Does this make any sense? Should I define something differently?

Thanks,

Tomer

Coordinator
Mar 21, 2012 at 6:50 PM

Thank you for all the detailed info. Do you have a small repro? If you want, you can send it to me privately so I can try to reproduce it (and thus fix it).

Mar 22, 2012 at 10:04 AM

I will try to create a small example.

Where should I send to?

Coordinator
Mar 24, 2012 at 11:07 AM

Did you receive my message?

Coordinator
Mar 25, 2012 at 7:35 PM

Ok, I have taken a look at your repro app. Here is the exact error message:

"{"Could not load file or assembly 'MyModelAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. An attempt was made to load a program with an incorrect format."}"

This is usually the case when you have an x86 and x64 project types together. That's why we recommend to always use Any CPU or at least make sure they are all the same. 

I have taken a look at your model assembly, and it was pointing to x86 while your main application point was set to Any CPU. After setting the model assembly to Any CPU as well, it all worked like a charm.

When you create a project on an x64 machine, Visual Studio automatically makes it x86. I have no idea why MS choose to do this, but it is stupid. The .NET framework should take care of this. Anyway, always make sure the types are set to Any CPU, it is a lifesaver...

Mar 26, 2012 at 5:37 PM

Thanks!