嵌套控件:多次实例化 ViewModel

Nested Controls: ViewModel instantiated multiple times

在我的实验项目中,我创建了一个带有一些嵌套子控件(Catel.Windows.Controls.UserControl 类型)的视图 - 有点像 Catel.Examples.WPF.AdvancedDemo 在 HouseView 中所做的(尽管该示例没有相同的问题。 )

部分 "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>

所以我基本上有一个 ItemsControl,它有一个创建 OderControl 视图的 ItemTemplate。

问题是,对于每个 OrderControl 项目,ViewModel 被实例化两次(第一次调用 SaveAsync)。

我提取了两个实例化和对 SaveAsync 调用的堆栈跟踪 - 希望有人能给我一些关于为什么我会遇到这些问题的见解:

第一个构造函数调用 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#

第一次保存调用 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#

第二个构造函数调用 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#

更新:我检查了 DataContext 是否确实更改了多次,stacktrace 建议 - 通过附加到 "UserControl.DataContextChanged" 事件 - 但它只被调用一次。
有趣的是:我附加到 Control 的 Event "Unloaded",猜猜 DataContext 是什么?人们会认为它是 OrderViewModel,但它是一个 "Order" ModelObject?!

更新#2:附加到 "RoomView" 上的相同事件(AdvancedDemo)- 相同的行为...DataContext 是 Unloaded-Event 中的 "RoomModel"。

更新#3:我现在在 UI 中打印出 DataContext 的名称....猜猜是什么 - 它是 ViewModel。现在我完全糊涂了呵呵。那么卡特尔是做什么的呢?更改 UserControl 中第一个子项的 DataContext? ("LayoutRoot")

更新#4:Catel UserControl 确实注入了 ViewModel DataContext 实际上不是 "in itself",而是在子控件中(在 Catel UserControl 4.4 的情况下 "first Grid Child"。

更新#5:我按照你的建议做了,这里是调试输出的一部分——我突出显示了 catel-usercontrol 变得疯狂的部分:

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

首先,让我稍微解释一下 Catel 如何处理它的嵌套 child 控件功能。它向 "host" 视图模型注入一个网格容器(或使用您定义的具有特殊名称的容器)。这样 parent 控件可以有一个数据上下文(从外部源设置)并且 Catel 会自动为您创建一个视图模型。过去,它取代了现有的控件 DataContext,但这会导致很多问题(TwoWay 绑定、不知道新更新等)。有关详细信息,请参阅 advanced docs.

因此,您的用户控件将始终具有原始 DataContext(模型)。它还将公开一个 ViewModel(即设置为第一个内部 child 的视图模型)。

您可以选择将这行代码添加到您的应用程序启动中:

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

这会将 Catel 的诊断信息添加到您的输出 window(在调试模式下),并会为您提供大量信息,了解幕后情况。它应该足以让您分析为什么要创建视图模型。

我发现,如果我交换以下代码:

    /// <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>));

(所以基本上只是模型中 ICollection 的映射)

此代码:

    /// <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,
            }
        });
    }

所以 - 没有 viewmodeltomodelmapping,在 InitializeAsync 中初始化 ICollection,它 "works as expected"。 (没有 "OrderViewModel" 的多重构造,没有额外的保存)

知道哪里出了问题吗?

问候 约翰内斯·科尔姆塞

事实证明这是 Catel 4.4 中的错误:

Link to Catel Issue-Tracker