0
votes

In my experimental Project, I created a View with some nested SubControls (of type Catel.Windows.Controls.UserControl) - kinda like Catel.Examples.WPF.AdvancedDemo does in the HouseView (although that example does NOT have the same problem.)

Parts of the "main view" XAML:

        <ItemsControl ItemsSource="{Binding Orders}" Grid.ColumnSpan="2" >
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Vertical">
                        <views:OrderControl DataContext="{Binding}"/>
                        <Line Height="2" />
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

So I basically have a ItemsControl, which has an ItemTemplate that creates a OderControl View.

The Problem is, that for each OrderControl item, the ViewModel is instantiated twice (and for the first SaveAsync is called).

I extracted the stack-trace for both instantiations and the call to SaveAsync - hope someone can give me some insights as for why I experience those problems:

First Constructor Call ViewModel:

CatelTest.WPF.Shared.dll!CatelTest.WPF.Shared.ViewModels.OrderViewModel.OrderViewModel(CatelTest.Data.Shared.Orders order) Line 18 C# [External Code] Catel.Core.dll!Catel.IoC.TypeFactory.TryCreateToConstruct(System.Type typeToConstruct, System.Reflection.ConstructorInfo constructor, object[] parameters, bool checkConstructor, bool hasMoreConstructorsLeft) Line 546 C# Catel.Core.dll!Catel.IoC.TypeFactory.CreateInstanceWithSpecifiedParameters(System.Type typeToConstruct, object[] parameters, bool autoCompleteDependencies, bool preventCircularDependencies) Line 295 C# Catel.Core.dll!Catel.IoC.TypeFactory.CreateInstanceWithParametersAndAutoCompletion(System.Type typeToConstruct, object[] parameters) Line 155 C# Catel.MVVM.dll!Catel.MVVM.ViewModelFactory.CreateViewModel(System.Type viewModelType, object dataContext) Line 89 C# Catel.MVVM.dll!Catel.MVVM.Providers.LogicBase.ConstructViewModelUsingArgumentOrDefaultConstructor(object injectionObject, System.Type viewModelType) Line 1079 C# Catel.MVVM.dll!Catel.MVVM.Providers.LogicBase.ConstructViewModelUsingArgumentOrDefaultConstructor(object injectionObject) Line 1005 C# Catel.MVVM.dll!Catel.MVVM.Providers.UserControlLogic.UpdateDataContextToUseViewModelAsync(object newDataContext) Line 691 C# [External Code] Catel.MVVM.dll!Catel.MVVM.Providers.UserControlLogic.OnTargetViewLoaded() Line 412 C# [External Code] Catel.MVVM.dll!Catel.MVVM.Providers.LogicBase.OnTargetViewLoadedInternal(object sender, System.EventArgs e) Line 681 C# Catel.MVVM.dll!Catel.MVVM.Providers.LogicBase.OnViewLoadedManagerLoaded(object sender, Catel.MVVM.Views.ViewLoadEventArgs e) Line 610 C# Catel.Core.dll!Catel.WeakEventListener.OnEvent(object source, Catel.MVVM.Views.ViewLoadEventArgs eventArgs) Line 745 C# Catel.MVVM.dll!Catel.MVVM.Views.ViewLoadManager.InvokeViewLoadEvent(Catel.MVVM.Views.IView view, Catel.MVVM.Views.ViewLoadStateEvent viewLoadStateEvent) Line 349 C# Catel.MVVM.dll!Catel.MVVM.Views.ViewLoadManager.RaiseLoaded(Catel.MVVM.Views.IView view) Line 293 C# Catel.MVVM.dll!Catel.MVVM.Views.ViewLoadManager.OnViewInfoLoaded(object sender, System.EventArgs e) Line 241 C# Catel.MVVM.dll!Catel.MVVM.Views.WeakViewInfo.OnLoaded() Line 207 C# Catel.MVVM.dll!Catel.MVVM.Views.WeakViewInfo.OnViewLoadStateLoaded(object sender, System.EventArgs e) Line 181 C# Catel.Core.dll!Catel.WeakEventListener.OnEvent(object source, System.EventArgs eventArgs) Line 745 C# [External Code] Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowWindow.AnonymousMethod__0() Line 499 C# Catel.MVVM.dll!Catel.Windows.Threading.DispatcherExtensions.Invoke(System.Windows.Threading.Dispatcher dispatcher, System.Action action, bool onlyBeginInvokeWhenNoAccess) Line 139 C# Catel.MVVM.dll!Catel.Windows.Threading.DispatcherExtensions.InvokeIfRequired(System.Windows.Threading.Dispatcher dispatcher, System.Action action) Line 95 C# Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowWindow(System.Windows.FrameworkElement window, bool showModal) Line 507 C# Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowWindowAsync(System.Windows.FrameworkElement window, bool showModal) Line 537 C# Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowDialogAsync(string name, object data, System.EventHandler completedProc) Line 338 C# [External Code] Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowDialogAsync(Catel.MVVM.IViewModel viewModel, System.EventHandler completedProc) Line 291 C# [External Code] CatelTest.WPF.Shared.dll!CatelTest.WPF.Shared.ViewModels.MainWindowViewModel.OnEditCustomerCommandExecute() Line 115 C# Catel.MVVM.dll!Catel.MVVM.TaskCommand..ctor.AnonymousMethod__0(object executeParameter, System.Threading.CancellationToken cancellationToken, System.IProgress progress) Line 52 C# Catel.MVVM.dll!Catel.MVVM.TaskCommand.Execute(bool ignoreCanExecuteCheck, object parameter) Line 234 C# [External Code] Catel.MVVM.dll!Catel.MVVM.Command.Execute(object parameter) Line 242 C# Catel.MVVM.dll!Catel.MVVM.Command.Execute(object parameter) Line 233 C# Catel.MVVM.dll!Catel.Windows.Interactivity.CommandTriggerActionBase.ExecuteCommand(object parameter) Line 299 C# Catel.MVVM.dll!Catel.Windows.Interactivity.EventToCommand.Invoke(object parameter) Line 138 C#

First Save Call ViewModel:

CatelTest.WPF.Shared.dll!CatelTest.WPF.Shared.ViewModels.OrderViewModel.SaveAsync() Line 219 C# Catel.MVVM.dll!Catel.MVVM.ViewModelBase.SaveViewModelAsync() Line 1753 C# [External Code] Catel.MVVM.dll!Catel.MVVM.Providers.UserControlLogic.CloseAndDisposeViewModelAsync(bool? result) Line 748 C# [External Code] Catel.MVVM.dll!Catel.MVVM.Providers.UserControlLogic.UpdateDataContextToUseViewModelAsync(object newDataContext) Line 688 C# [External Code] Catel.MVVM.dll!Catel.MVVM.Providers.UserControlLogic.OnTargetViewDataContextChanged(Catel.MVVM.Views.DataContextChangedEventArgs e, object sender) Line 506 C# [External Code] Catel.Core.dll!Catel.EventHandlerExtensions.SplitInvoke(System.Delegate handler, object[] args) Line 233 C# Catel.Core.dll!Catel.EventHandlerExtensions.SafeInvoke(System.EventHandler handler, object sender, Catel.MVVM.Views.DataContextChangedEventArgs e) Line 143 C# Catel.MVVM.dll!Catel.Windows.Controls.UserControl..ctor.AnonymousMethod__11_5(object sender, Catel.Windows.Data.DependencyPropertyValueChangedEventArgs e) Line 126 C# Catel.MVVM.dll!Catel.Windows.Data.DependencyPropertyChangedHelper.OnDependencyPropertyChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e) Line 271 C# [External Code] Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowWindow.AnonymousMethod__0() Line 499 C# Catel.MVVM.dll!Catel.Windows.Threading.DispatcherExtensions.Invoke(System.Windows.Threading.Dispatcher dispatcher, System.Action action, bool onlyBeginInvokeWhenNoAccess) Line 139 C# Catel.MVVM.dll!Catel.Windows.Threading.DispatcherExtensions.InvokeIfRequired(System.Windows.Threading.Dispatcher dispatcher, System.Action action) Line 95 C# Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowWindow(System.Windows.FrameworkElement window, bool showModal) Line 507 C# Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowWindowAsync(System.Windows.FrameworkElement window, bool showModal) Line 537 C# Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowDialogAsync(string name, object data, System.EventHandler completedProc) Line 338 C# [External Code] Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowDialogAsync(Catel.MVVM.IViewModel viewModel, System.EventHandler completedProc) Line 291 C# [External Code] CatelTest.WPF.Shared.dll!CatelTest.WPF.Shared.ViewModels.MainWindowViewModel.OnEditCustomerCommandExecute() Line 115 C# Catel.MVVM.dll!Catel.MVVM.TaskCommand..ctor.AnonymousMethod__0(object executeParameter, System.Threading.CancellationToken cancellationToken, System.IProgress progress) Line 52 C# Catel.MVVM.dll!Catel.MVVM.TaskCommand.Execute(bool ignoreCanExecuteCheck, object parameter) Line 234 C# [External Code] Catel.MVVM.dll!Catel.MVVM.Command.Execute(object parameter) Line 242 C# Catel.MVVM.dll!Catel.MVVM.Command.Execute(object parameter) Line 233 C# Catel.MVVM.dll!Catel.Windows.Interactivity.CommandTriggerActionBase.ExecuteCommand(object parameter) Line 299 C# Catel.MVVM.dll!Catel.Windows.Interactivity.EventToCommand.Invoke(object parameter) Line 138 C#

Second Constructor Call ViewModel:

CatelTest.WPF.Shared.dll!CatelTest.WPF.Shared.ViewModels.OrderViewModel.OrderViewModel(CatelTest.Data.Shared.Orders order) Line 18 C# [External Code] Catel.Core.dll!Catel.IoC.TypeFactory.TryCreateToConstruct(System.Type typeToConstruct, System.Reflection.ConstructorInfo constructor, object[] parameters, bool checkConstructor, bool hasMoreConstructorsLeft) Line 546 C# Catel.Core.dll!Catel.IoC.TypeFactory.CreateInstanceWithSpecifiedParameters(System.Type typeToConstruct, object[] parameters, bool autoCompleteDependencies, bool preventCircularDependencies) Line 295 C# Catel.Core.dll!Catel.IoC.TypeFactory.CreateInstanceWithParametersAndAutoCompletion(System.Type typeToConstruct, object[] parameters) Line 155 C# Catel.MVVM.dll!Catel.MVVM.ViewModelFactory.CreateViewModel(System.Type viewModelType, object dataContext) Line 89 C# Catel.MVVM.dll!Catel.MVVM.Providers.LogicBase.ConstructViewModelUsingArgumentOrDefaultConstructor(object injectionObject, System.Type viewModelType) Line 1079 C# Catel.MVVM.dll!Catel.MVVM.Providers.LogicBase.ConstructViewModelUsingArgumentOrDefaultConstructor(object injectionObject) Line 1005 C# Catel.MVVM.dll!Catel.MVVM.Providers.UserControlLogic.UpdateDataContextToUseViewModelAsync(object newDataContext) Line 691 C# [External Code] Catel.MVVM.dll!Catel.MVVM.Providers.UserControlLogic.OnTargetViewDataContextChanged(Catel.MVVM.Views.DataContextChangedEventArgs e, object sender) Line 506 C# [External Code] Catel.Core.dll!Catel.EventHandlerExtensions.SplitInvoke(System.Delegate handler, object[] args) Line 233 C# Catel.Core.dll!Catel.EventHandlerExtensions.SafeInvoke(System.EventHandler handler, object sender, Catel.MVVM.Views.DataContextChangedEventArgs e) Line 143 C# Catel.MVVM.dll!Catel.Windows.Controls.UserControl..ctor.AnonymousMethod__11_5(object sender, Catel.Windows.Data.DependencyPropertyValueChangedEventArgs e) Line 126 C# Catel.MVVM.dll!Catel.Windows.Data.DependencyPropertyChangedHelper.OnDependencyPropertyChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e) Line 271 C# [External Code] Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowWindow.AnonymousMethod__0() Line 499 C# Catel.MVVM.dll!Catel.Windows.Threading.DispatcherExtensions.Invoke(System.Windows.Threading.Dispatcher dispatcher, System.Action action, bool onlyBeginInvokeWhenNoAccess) Line 139 C# Catel.MVVM.dll!Catel.Windows.Threading.DispatcherExtensions.InvokeIfRequired(System.Windows.Threading.Dispatcher dispatcher, System.Action action) Line 95 C# Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowWindow(System.Windows.FrameworkElement window, bool showModal) Line 507 C# Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowWindowAsync(System.Windows.FrameworkElement window, bool showModal) Line 537 C# Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowDialogAsync(string name, object data, System.EventHandler completedProc) Line 338 C# [External Code] Catel.MVVM.dll!Catel.Services.UIVisualizerService.ShowDialogAsync(Catel.MVVM.IViewModel viewModel, System.EventHandler completedProc) Line 291 C# [External Code] CatelTest.WPF.Shared.dll!CatelTest.WPF.Shared.ViewModels.MainWindowViewModel.OnEditCustomerCommandExecute() Line 115 C# Catel.MVVM.dll!Catel.MVVM.TaskCommand..ctor.AnonymousMethod__0(object executeParameter, System.Threading.CancellationToken cancellationToken, System.IProgress progress) Line 52 C# Catel.MVVM.dll!Catel.MVVM.TaskCommand.Execute(bool ignoreCanExecuteCheck, object parameter) Line 234 C# [External Code] Catel.MVVM.dll!Catel.MVVM.Command.Execute(object parameter) Line 242 C# Catel.MVVM.dll!Catel.MVVM.Command.Execute(object parameter) Line 233 C# Catel.MVVM.dll!Catel.Windows.Interactivity.CommandTriggerActionBase.ExecuteCommand(object parameter) Line 299 C# Catel.MVVM.dll!Catel.Windows.Interactivity.EventToCommand.Invoke(object parameter) Line 138 C#

Update: I checked if the DataContext does change multiple times, which the stacktrace suggests - by attaching to "UserControl.DataContextChanged" Event - but it is called only once.
What is also interesting: I attached to the Event "Unloaded" of the Control, and guess what the DataContext is? One would expect it to be the OrderViewModel, yet it is an "Order" ModelObject?!

Update#2: Attaching to the same Events on "RoomView" (AdvancedDemo) - same behaviour...the DataContext is a "RoomModel" in Unloaded-Event.

Update#3: I print out the Name of the DataContext in the UI now....and guess what - it is the ViewModel. Now I am totally confused hehe. So what does Catel do? Change the DataContext of the first child in the UserControl? (the "LayoutRoot")

Update#4: The Catel UserControl indeed injects the ViewModel DataContext infact not "in itself", but in a Sub-Control (in case of Catel UserControl 4.4 the "first Grid Child".

Update #5: I did as you suggested, here are parts of the debug-output - I highlighted the part where the catel-usercontrol goes crazy:

13:35:18:857 => [DEBUG] [Catel.MVVM.Views.ViewToViewModelMappingHelper] [10] Initializing view model container to manage ViewToViewModel mappings 13:35:18:860 => [DEBUG] [Catel.MVVM.Views.ViewToViewModelMappingHelper] [10] Initializing view model 'OrderViewModel' 13:35:18:860 => [DEBUG] [Catel.MVVM.Views.ViewToViewModelMappingHelper] [10] Initialized view model 'OrderViewModel' 13:35:18:861 => [DEBUG] [Catel.MVVM.Views.ViewToViewModelMappingHelper] [10] Initialized view model container to manage ViewToViewModel mappings 13:35:18:865 => [DEBUG] [Catel.MVVM.Providers.LogicBase] [10] DataContext of TargetView 'OrderControl' has changed to 'Orders' CatelTest.WPF.DataService.vshost.exe Information: 0 : 13:35:19:553 => [INFO] [Catel.MVVM.ViewModelBase] [10] Saved view model 'CatelTest.WPF.Shared.ViewModels.OrderViewModel' 13:35:19:555 => [DEBUG] [Catel.MVVM.ViewModelManager] [10] Unregistering model 'Orders' with view model 'OrderViewModel' (id = '3') 13:35:19:555 => [DEBUG] [Catel.MVVM.ViewModelManager] [10] Unregistered model 'Orders' with view model 'OrderViewModel' (id = '3') 13:35:19:557 => [DEBUG] [Catel.Data.ModelBase] [10] IEditableObject.EndEdit 13:35:19:559 => [DEBUG] [Catel.MVVM.ViewModelManager] [10] Unregistering all models of view model 'OrderViewModel' (id = '3') 13:35:19:560 => [DEBUG] [Catel.MVVM.ViewModelManager] [10] Unregistered all '0' models of view model 'OrderViewModel' (id = '3') 13:35:19:564 => [DEBUG] [Catel.MVVM.ViewModelCommandManager] [10] Unregistering commands on view model 'CatelTest.WPF.Shared.ViewModels.OrderViewModel' with unique identifier '3' 13:35:19:564 => [DEBUG] [Catel.MVVM.ViewModelCommandManager] [10] Unregistered commands on view model 'CatelTest.WPF.Shared.ViewModels.OrderViewModel' with unique identifier '3' CatelTest.WPF.DataService.vshost.exe Information: 0 : 13:35:19:566 => [INFO] [Catel.MVVM.ViewModelBase] [10] Closed view model 'CatelTest.WPF.Shared.ViewModels.OrderViewModel' 13:35:19:568 => [DEBUG] [Catel.MVVM.ManagedViewModel] [10] Removed view model instance, currently containing '0' instances of type 'CatelTest.WPF.Shared.ViewModels.OrderViewModel' 13:35:19:568 => [DEBUG] [Catel.MVVM.Views.ViewToViewModelMappingHelper] [10] Initializing view model 'null' 13:35:19:569 => [DEBUG] [Catel.MVVM.Views.ViewToViewModelMappingHelper] [10] Uninitializing view model 'OrderViewModel' 13:35:19:569 => [DEBUG] [Catel.MVVM.Views.ViewToViewModelMappingHelper] [10] Uninitialized view model 'OrderViewModel' 13:35:19:570 => [DEBUG] [Catel.MVVM.Views.ViewToViewModelMappingHelper] [10] Initialized view model 'null' 13:35:19:570 => [DEBUG] [Catel.MVVM.Providers.LogicBase] [10] Using IViewModelFactory 'Catel.MVVM.ViewModelFactory' to instantiate the view model 13:35:19:571 => [DEBUG] [Catel.IoC.TypeFactory] [10] Calling constructor.Invoke with the right parameters 13:35:19:571 => [DEBUG] [Catel.MVVM.ViewModelBase] [10] Creating view model of type 'OrderViewModel' with unique identifier 4 13:35:19:572 => [DEBUG] [Catel.MVVM.ViewModelCommandManager] [10] Creating a ViewModelCommandManager for view model 'CatelTest.WPF.Shared.ViewModels.OrderViewModel' with unique identifier '4' 13:35:19:573 => [DEBUG] [Catel.MVVM.ViewModelCommandManager] [10] Created a ViewModelCommandManager for view model 'CatelTest.WPF.Shared.ViewModels.OrderViewModel' with unique identifier '4' 13:35:19:573 => [DEBUG] [Catel.MVVM.ManagedViewModel] [10] Added view model instance, currently containing '1' instances of type 'CatelTest.WPF.Shared.ViewModels.OrderViewModel' 13:35:20:139 => [DEBUG] [Catel.MVVM.ViewModelManager] [10] Registering model 'Orders' with view model 'OrderViewModel' (id = '4') 13:35:20:140 => [DEBUG] [Catel.MVVM.ViewModelManager] [10] Registered model 'Orders' with view model 'OrderViewModel' (id = '4') 13:35:20:140 => [DEBUG] [Catel.Data.ModelBase] [10] IEditableObject.BeginEdit

3

3 Answers

0
votes

First of all, let me explain a bit how Catel takes care of it's nested child controls feature. It injects a grid container (or uses one you define with a special name) to "host" the view model. This way the parent control can have a data context (set from an external source) and Catel will automatically create a view model for you. In the past, it was replacing the existing control DataContext but that lead to a lot of issues (TwoWay binding, not being aware of new updates, etc). For more information about that, see the advanced docs.

Because of this, your user control will always have the original DataContext (the model). It will also expose a ViewModel (which is the view model set to the first inner child).

One option you have is to add this line of code to your app startup:

#if DEBUG
    LogManager.AddDebugListener(false);
#endif

This will add the diagnostics of Catel to your output window (in debug mode) and will give you tons of information what's happening under the hood. It should give you enough to analyze why view models are being created.

0
votes

I found out, that if I Exchange the following Code:

    /// <summary>
    /// Gets or sets the property value.
    /// </summary>
    [ViewModelToModel()]
    public ICollection<Orders> Orders
    {
        get { return GetValue<ICollection<Orders>>(OrdersProperty); }
        set { SetValue(OrdersProperty, value); }
    }

    /// <summary>
    /// Register the Orders property so it is known in the class.
    /// </summary>
    public static readonly PropertyData OrdersProperty = RegisterProperty("Orders", typeof(ICollection<Orders>));

(So basically just a mapping to an ICollection in the Model)

to this Code:

    /// <summary>
    /// Gets or sets the property value.
    /// </summary>
    //[ViewModelToModel()]
    public ICollection<Orders> Orders
    {
        get { return GetValue<ICollection<Orders>>(OrdersProperty); }
        set { SetValue(OrdersProperty, value); }
    }

    /// <summary>
    /// Register the Orders property so it is known in the class.
    /// </summary>
    public static readonly PropertyData OrdersProperty = RegisterProperty("Orders", typeof(ICollection<Orders>));


    protected override System.Threading.Tasks.Task InitializeAsync()
    {
        return System.Threading.Tasks.Task.Factory.StartNew(() => this.Orders = new List<Orders>()
        {
            new Orders()
            {
                Freight = 666,
            }
        });
    }

So - no viewmodeltomodelmapping, initialize the ICollection in InitializeAsync, it "works as expected". (no multiple construct of "OrderViewModel", no additional save)

Any ideas what goes wrong?

Regards Johannes Colmsee

0
votes