在项控件中公开“添加新项”哨兵的最佳 MVVM 方法是什么
本文关键字:哨兵 最佳 MVVM 是什么 方法 添加新项 新项 控件 添加 | 更新日期: 2023-09-27 18:35:05
假设我有一个TabControl
,它展示了Foo
对象的集合(每个对象都通过一个FooViewModel
)。在选项卡项列表的末尾,我想要一个人造选项卡项,它不对任何内容进行建模,但它会创建一个新的模型项,并在单击时将其添加到集合中。
如果概念不明确,一个真实的例子是Internet Explorer中的选项卡。它有n+1
选项卡项:n
具有页面内容,最后一个添加新的"真实"选项卡。
在 MVVM 中对这种类型的交互进行建模的正确方法(如果有)是什么?我想到的选项是:
-
使其成为视图模型的一部分。在控件绑定到的
IEnumerable<FooViewModel> Foos
集合的末尾,添加一个"新项"哨兵,并将"我是真正的 foo 还是新的 foo 哨兵"逻辑生成到视图模型中。 -
使其成为视图的完全一部分。重新模板(和/或子类)
TabControl
显示所有真实项目,然后还有一个按钮,该按钮调用命令来创建和插入新项目。
第一个选项一开始感觉是错误的,就像它将视图详细信息泄漏到视图模型中一样(似乎"添加新Foo
"很可能是一个常规命令,并且某些视图可能根本不希望从选项卡列表中调用它)。但这确实有一定的意义,因为对于初始化过程,我已经需要对"半构造Foo
"进行建模,因此"尚不存在的Foo
"的模型似乎并不遥远。
第二部分似乎工作量很大,也很容易搞砸(假设希望它的外观和感觉像其他选项卡)。
但我是MVVM的新手;当然,这经常出现。也许我完全错过了一些东西。传统的处理方式是什么?
MVVM 模式的几个关键租户是:
- 可测试性 - 通过使视图尽可能简单,通过将逻辑移动到视图模型中,可以增加可以通过单元测试测试的逻辑量。
- 设计器支持 - 模拟视图模型可用于向视图提供设计时数据。
您的哪些建议最能满足上述要求?我会说选项(1)。您可以编写单元测试,以确保列表中的最后一项始终是"哨兵"项。
为了"将'我是真正的 foo 还是新的 foo 哨兵'逻辑构建到视图模型中",您可以简单地使用类型化的数据模板。
重新模板化 TabControl 并在选项卡右侧添加假选项卡或按钮可能更简单。
基本示例:
<Window x:Class="ContextMenuSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<SolidColorBrush x:Key="WindowBackgroundBrush"
Color="#FFF" />
<SolidColorBrush x:Key="SolidBorderBrush"
Color="#888" />
<SolidColorBrush x:Key="DisabledForegroundBrush"
Color="#888" />
<SolidColorBrush x:Key="DisabledBorderBrush"
Color="#AAA" />
<Style TargetType="{x:Type TabControl}">
<Setter Property="OverridesDefaultStyle"
Value="True" />
<Setter Property="SnapsToDevicePixels"
Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid KeyboardNavigation.TabNavigation="Local">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TabPanel Name="HeaderPanel"
Grid.Row="0"
Panel.ZIndex="1"
Margin="0,0,4,-1"
IsItemsHost="True"
KeyboardNavigation.TabIndex="1"
Background="Transparent" />
<Button Grid.Row="0"
Grid.Column="1"
Content="Add new" />
<Border Name="Border"
Grid.Row="1"
Grid.ColumnSpan="2"
Background="{StaticResource WindowBackgroundBrush}"
BorderBrush="{StaticResource SolidBorderBrush}"
BorderThickness="1"
CornerRadius="2"
KeyboardNavigation.TabNavigation="Local"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2">
<ContentPresenter Name="PART_SelectedContentHost"
Margin="4"
ContentSource="SelectedContent" />
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled"
Value="False">
<Setter Property="Foreground"
Value="{StaticResource DisabledForegroundBrush}" />
<Setter TargetName="Border"
Property="BorderBrush"
Value="{StaticResource DisabledBorderBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<TabControl>
<TabItem Header="Item 1" />
<TabItem Header="Item 2" />
</TabControl>
</Grid>
</Window>
enter code here
我会选择第一个:
因此,您有一个TabViews
集合,其中每个(如果是最后一个)在激活时都会创建一个新集合并将其注入到自身之前。
样式呢:如果你想让它成为UI的一部分,你也可以在之后做。这是WPF
的强项。
因此,一般来说,解决方案变成了您所说的两种解决方案的合并。没有什么可以阻止您为集合中的最后一个TabView
制作样式模板并将其仅应用于它。
希望这有帮助。