【问题标题】:What's the best MVVM way to expose a 'add new item' sentinel in an items control?在项目控件中公开“添加新项目”标记的最佳 MVVM 方法是什么?
【发布时间】:2012-05-21 20:50:54
【问题描述】:

假设我有一个TabControl,它呈现Foo 对象的集合(每个对象都通过FooViewModel)。在选项卡项列表的末尾,我想要一个不建模任何东西的人造选项卡项,但它会创建一个新模型项并在单击时将其添加到集合中。

如果概念不清楚,一个真实的例子是 Internet Explorer 中的选项卡。它有n+1 标签项:n 有页面内容,最后一个添加了一个新的“真实”标签。

在 MVVM 中对此类交互进行建模的正确方法(如果有)是什么?我想到的选项是:

  • 使其成为视图模型的一部分。在控件绑定到的IEnumerable<FooViewModel> Foos 集合的末尾,添加一个“新项目”标记并将“我是真正的 foo 还是新的 foo 标记”逻辑构建到视图模型中。

    李>
  • 使其完全成为视图的一部分。重新模板(和/或子类)TabControl 以呈现所有真实项目,然后还有一个按钮,该按钮调用命令以创建和插入新项目。

第一个选项一开始感觉不对,就像它会将视图细节泄漏到视图模型中(“添加新的Foo”似乎很可能是一个通用命令,并且某些视图可能不希望从选项卡中调用它完全列出)。但这确实有一定的意义,因为对于初始化过程,我已经需要为“半构造的Foo”建模,所以“尚不存在的Foo”的模型似乎并不存在这么远。

第二部分看起来工作量很大,也很容易搞砸(假设希望它看起来和感觉像其他选项卡)。

但我是 MVVM 的新手;这肯定经常出现。也许我完全错过了一些东西。传统的处理方式是什么?

【问题讨论】:

    标签: c# wpf mvvm


    【解决方案1】:

    MVVM 模式的几个关键租户是:

    • 可测试性 - 通过使您的视图尽可能简单,通过将逻辑移动到您的视图模型中,您可以通过单元测试增加可以测试的逻辑量。
    • Designer-Support - 模拟视图模型可用于为您的视图提供设计时数据。

    您的哪个建议最符合上述要求?我会说选项(1)。您可以编写单元测试以确保列表中的最后一项始终是“哨兵”项。

    为了“在视图模型中构建‘我是一个真正的 foo 还是一个新的 foo 哨兵’逻辑。”,您可以简单地使用类型化的 DataTemplates。

    【讨论】:

    • 将此标记为答案,即使我并不完全满意。我认为没有好的答案。与您提出的两个论点相反的是,如果选择了选项 (2),那么可测试/可设计部分将只是“制作新项目”命令。如何触发该命令将纯粹是视图的一部分,因此这些点不适用。但是,感谢您的输入。我想我现在要尝试走这条路。
    【解决方案2】:

    重新模板化 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
    

    【讨论】:

    • 这是一个不错的开始,但实际上不会那么简单。 “新”选项卡的流动方式与其他项目(由选项卡面板处理)非常不同。我认为让选项卡面板外部的项目表现得像其中的项目一样是困难的,甚至是不可能的。这是我对重新模板的主要担忧。不过,谢谢你的例子。
    【解决方案3】:

    我会选择第一个:

    所以你有一个TabViews 的集合,其中每个,如果它是最后一个,当被激活时会创建一个新的并在它自己之前注入它。

    样式怎么样:如果你想让它成为 UI 的一部分,你也可以在之后做。这是WPF 的强项。

    因此,一般来说,解决方案变成了您所说的两种解决方案的合并。 没有什么能阻止您为集合中的最后一个 TabView 制作样式模板并将其应用于它。

    希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-04-14
      • 2021-09-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多