【发布时间】: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);
【问题讨论】: