[Catel]如何将带有构造函数参数的 ViewModel 传递给 TabService 扩展方法?

Posted

技术标签:

【中文标题】[Catel]如何将带有构造函数参数的 ViewModel 传递给 TabService 扩展方法?【英文标题】:[Catel]How do I pass a ViewModel with constructor argument(s) to TabServiceExtensions methods? 【发布时间】:2017-02-03 00:48:35 【问题描述】:

所以我上周刚开始使用 Catel,但我无法让选项卡式界面正常工作。我一直在使用以下资源使用带有 MVVM 的选项卡式界面 (https://catelproject.atlassian.net/wiki/display/CTL/Using+a+tabbed+interface+with+MVVM#UsingatabbedinterfacewithMVVM-CreatingClosableTabItem):

我有一个 MainWindow (catel:Window),其中包含一个带有 xmlns:controls="clr-namespace:AutoProgram.UI.Controls" 的 TabControl:

<Border Background="#50FFFFFF" BorderBrush="StaticResource WindowFrameBrush" BorderThickness="5" Margin="-6" Padding="0">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Border Grid.Row="0" Grid.Column="0" Background="StaticResource WindowFrameBrush" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" CornerRadius="0,0,0,0" Margin="0" Padding="0">
            <Grid>
                <TextBlock Foreground="White" FontWeight="Bold" VerticalAlignment="Center" Margin="10,2,10,2" Text="Binding RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=x:Type Window, Path=Title"/>
                <Button Content="X" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="1" FontSize="7" Width="15" Height="15" Padding="0" Command="ApplicationCommands.Close"/>
            </Grid>
        </Border>
        <catel:StackGrid Grid.Row="1" Grid.Column="0">
            <catel:StackGrid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </catel:StackGrid.RowDefinitions>
            <catel:StackGrid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
            </catel:StackGrid.ColumnDefinitions>

            <ItemsControl x:Name="ItemsControlAutomators" Grid.Row="0" Grid.Column="0" ItemsSource="Binding Automators">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <UniformGrid Columns="3" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Button x:Name="Button" Content="Binding Automator.Name, Mode=OneWay" Command="Binding ElementName=ItemsControlAutomators, Path=DataContext.RunAutomator" CommandParameter="Binding"></Button>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </catel:StackGrid>

        <catel:TabControl x:Name="TabControlAutomators" Grid.Row="2" Grid.Column="0" Margin="-2" LoadTabItems="LazyLoading">
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <controls:ClosableTabItem Title="Binding ViewModel.Title" CanClose="Binding CanClose" />
                </DataTemplate>
            </TabControl.ItemTemplate>

            <TabControl.ContentTemplate>
                <DataTemplate>
                    <ContentControl Content="Binding ViewModel, Converter=catel:ViewModelToViewConverter" />
                </DataTemplate>
            </TabControl.ContentTemplate>
        </catel:TabControl>
    </Grid>
</Border>

相关 MainWindow.xaml.cs

public partial class MainWindow

    public MainWindow()
    
        InitializeComponent();

        var serviceLocator = this.GetServiceLocator();
        var tabService = serviceLocator.ResolveType<ITabService>() as TabService;

        tabService?.SetTabControl(TabControlAutomators);
    

相关 MainWindowViewModel.cs 和故障位置

public class MainWindowViewModel : ViewModelBase

    private readonly IAutomatorService _automatorService;
    private readonly ITabService _tabService;

    public MainWindowViewModel(IAutomatorService automatorService, ITabService tabService)
    
        Argument.IsNotNull(() => automatorService);
        Argument.IsNotNull(() => tabService);

        _automatorService = automatorService;
        _tabService = tabService;

        RunAutomator = new Command<AutomatorModel>(OnRunAutomator, OnRunAutomatorCanExecute);
    

    public override string Title => "AutoProgram";

    public ObservableCollection<AutomatorModel> Automators
    
        get  return GetValue<ObservableCollection<AutomatorModel>>(AutomatorsProperty); 
        set  SetValue(AutomatorsProperty, value); 
    

    public static readonly PropertyData AutomatorsProperty = RegisterProperty("Automators", typeof(ObservableCollection<AutomatorModel>), () => new ObservableCollection<AutomatorModel>());

    public Command<AutomatorModel> RunAutomator  get; private set; 

    public async void OnRunAutomator(AutomatorModel automatorModel)
    
        Debug.WriteLine($"NAME: automatorModel.Automator.Name");
        _tabService.AddAndActivate<AutomatorViewModel>(new AutomatorViewModel(automatorModel), true);   // Throws a null exception in TabItem.cs
        //_tabService.AddAndActivate<AutomatorViewModel>(new AutomatorViewModel(), true);   // But this works (sort of, see bottom error).
    


TabServiceExtensions.cs

public static class TabServiceExtensions

    public static TabItem Add<TViewModel>(this ITabService tabService, object dataContext = null, bool canClose = false)
        where TViewModel : IViewModel
    
        Argument.IsNotNull(() => tabService);

        var tabItem = CreateTabItem<TViewModel>(tabService, dataContext);
        tabItem.CanClose = canClose;

        tabService.Add(tabItem);

        return tabItem;
    

    public static TabItem AddAndActivate<TViewModel>(this ITabService tabService, object dataContext = null, bool canClose = false)
        where TViewModel : IViewModel
    
        Argument.IsNotNull(() => tabService);

        var tabItem = Add<TViewModel>(tabService, dataContext, canClose);
        tabService.Activate(tabItem);

        return tabItem;
    

    public static TabItem CreateTabItem<TViewModel>(this ITabService tabService, object dataContext)
        where TViewModel : IViewModel
    
        Argument.IsNotNull(() => tabService);

        var dependencyResolver = tabService.GetDependencyResolver();
        var viewModelFactory = dependencyResolver.Resolve<IViewModelFactory>();
        var vm = viewModelFactory.CreateViewModel<TViewModel>(typeof(TViewModel), dataContext);

        return new TabItem(vm);
    

    public static void AddAndActivate(this ITabService tabService, TabItem tabItem)
    
        Argument.IsNotNull(() => tabService);
        Argument.IsNotNull(() => tabItem);

        tabService.Add(tabItem);
        tabService.Activate(tabItem);
    

TabItem.cs

public class TabItem

    public TabItem(IViewModel viewModel)
    
        Argument.IsNotNull(() => viewModel);

        ViewModel = viewModel;
        CanClose = true;

        if (!viewModel.IsClosed)
        
            viewModel.ClosedAsync += OnViewModelClosed;
        
    

    public IViewModel ViewModel  get; private set; 

    public bool CanClose  get; set; 

    public object Tag  get; set; 

    public event EventHandler<EventArgs> Closed;

    private async Task OnViewModelClosed(object sender, ViewModelClosedEventArgs e)
    
        var vm = ViewModel;
        if (vm != null)
        
            vm.ClosedAsync -= OnViewModelClosed;
        

        Closed.SafeInvoke(this);
    

我希望 TabItems 是 AutomatorViewModels。后者是这样初始化的:

    public AutomatorViewModel(AutomatorModel automatorModel)
    
        Title = "Test";
    

但是上面的代码在 TabItem.cs 中抛出了一个空异常。如果我省略构造函数参数,即将其更改为 公共 AutomatorViewModel() 选项卡确实使用“测试”标题创建。尽管在这种情况下手动关闭这些选项卡时会出现以下错误: System.Windows.Data 错误:4:找不到与引用“RelativeSource FindAncestor,AncestorType='System.Windows.Controls.TabControl',AncestorLevel='1''的绑定源。 BindingExpression:Path=TabStripPlacement;数据项=空;目标元素是'TabItem'(名称='');目标属性是“NoTarget”(类型“对象”)

App.xaml.cs

    protected override void OnStartup(StartupEventArgs e)
    
        LogManager.AddDebugListener();

        Log.Info("Starting application");

        StyleHelper.CreateStyleForwardersForDefaultStyles();

        var serviceLocator = ServiceLocator.Default;
        serviceLocator.RegisterType<IAutomatorService, AutomatorService>();
        serviceLocator.RegisterType<ITabService, TabService>();

        // SOME THINGS I'VE TRIED/CURRENTLY TRYING.
        //var dependencyResolver = this.GetDependencyResolver();
        //var viewModelLocator = dependencyResolver.Resolve<IViewModelLocator>();
        //viewModelLocator.Register<AutomatorView, AutomatorViewModel>();
        //viewModelLocator.Register(typeof(AutomatorView), typeof(AutomatorViewModel));
        //viewModelLocator.Register<MainWindow, MainWindowViewModel>();

        Log.Info("Calling base.OnStartup");
        base.OnStartup(e);
    

Catel 调试信息:

11:22:39:117 => [DEBUG] [Catel.MVVM.ViewModelBase] [8] 创建具有唯一标识符 2 的“AutomatorViewModel”类型的视图模型

11:22:39:117 => [DEBUG] [Catel.MVVM.ViewModelCommandManager] [8] 使用唯一标识符“2”为视图模型“AutoProgram.UI.ViewModels.AutomatorViewModel”创建 ViewModelCommandManager

11:22:39:118 => [DEBUG] [Catel.MVVM.ViewModelCommandManager] [8] 使用唯一标识符“2”为视图模型“AutoProgram.UI.ViewModels.AutomatorViewModel”创建了 ViewModelCommandManager

11:22:39:119 => [DEBUG] [Catel.MVVM.ManagedViewModel] [8] 添加了视图模型实例,当前包含“AutoProgram.UI.ViewModels.AutomatorViewModel”类型的“1”个实例

11:22:39:123 => [DEBUG] [Catel.IoC.TypeFactory] ​​[8] 使用特定参数创建类型为“AutoProgram.UI.ViewModels.AutomatorViewModel”的实例。在缓存中找不到构造函数,所以寻找正确的构造函数

11:22:39:124 => [DEBUG] [Catel.IoC.TypeFactory] ​​[8] 检查构造函数 'public ctor(AutomatorModel automatorModel)' 是否可以使用

11:22:39:126 => [DEBUG] [Catel.IoC.TypeFactory] ​​[8] 构造函数无效,因为值“AutoProgram.UI.ViewModels.AutomatorViewModel”不能用于参数“AutoProgram.UI”。 ViewModels.AutomatorViewModel'

11:22:39:126 => [DEBUG] [Catel.IoC.TypeFactory] ​​[8] 构造函数有效,可以使用

11:22:39:127 => [DEBUG] [Catel.IoC.TypeFactory] ​​[8] 无法使用构造函数,无法使用指定参数构造类型“AutoProgram.UI.ViewModels.AutomatorViewModel”

11:22:39:128 => [DEBUG] [Catel.IoC.TypeFactory] ​​[8] 使用特定参数创建类型为“AutoProgram.UI.ViewModels.AutomatorViewModel”的实例。在缓存中找不到构造函数,所以寻找正确的构造函数

11:22:39:128 => [DEBUG] [Catel.IoC.TypeFactory] ​​[8] 检查构造函数 'public ctor(AutomatorModel automatorModel)' 是否可以使用

11:22:39:129 => [DEBUG] [Catel.IoC.TypeFactory] ​​[8] 构造函数无效,因为无法从依赖解析器解析参数“automatorModel”

11:22:39:129 => [DEBUG] [Catel.IoC.TypeFactory] ​​[8] 构造函数有效,可以使用

11:22:39:130 => [DEBUG] [Catel.IoC.TypeFactory] ​​[8] 无法使用构造函数,无法使用指定参数构造类型“AutoProgram.UI.ViewModels.AutomatorViewModel”

11:22:39:130 => [DEBUG] [Catel.MVVM.ViewModelFactory] ​​[8] 无法使用注入数据上下文“AutomatorViewModel”构造视图模型“AutoProgram.UI.ViewModels.AutomatorViewModel” AutoProgram.UI.vshost.exe 错误:0 : 11:22:39:131 => [错误] [Catel.Argument] [8] 参数“viewModel”不能为空 抛出异常:Catel.Core.dll 中的“System.ArgumentNullException”


编辑#1:

添加了调试信息(未注释LogManager.AddDebugListener();) 添加了 App.xaml.cs

编辑#2:

通过将视图模型的初始化更改为无构造函数并显式设置模型属性找到了一种解决方法,如下所示:

_tabService.AddAndActivate(new AutomatorViewModel AutomatorModel = automatorModel , false);

【问题讨论】:

【参考方案1】:

您应该将 DataContext 对象而不是 ViewModel 传递到AddAndActivate。所以这应该可以工作(并且会为你构建和注入虚拟机):

_tabService.AddAndActivate<AutomatorViewModel>(automatorModel, true);

【讨论】:

我试过了,但没有用。相同的调试错误。我不知道如何以及在哪里注册 viewmodel 参数。我也有嵌套选项卡控件的异步问题。无论如何,干杯。

以上是关于[Catel]如何将带有构造函数参数的 ViewModel 传递给 TabService 扩展方法?的主要内容,如果未能解决你的问题,请参考以下文章

Catel WPF 延迟验证

如何将参数传递给静态类构造函数?

我如何将参数传递给构造函数? [关闭]

如何在 REMIX(Solidity IDE)中从 web3 调用带有参数的构造函数

带有字符串参数的构造函数的通用约束?

如何从 CPP 中的基类继承构造函数?