如何在不违反MVVM模式的情况下从WPF中的TabControl更改选项卡

8
我的WPF窗口包含一个TabControl,它可以在不同的选项卡上显示内容。点击下面的按钮会通过ICommand接口/绑定执行一个方法。被调用的方法会生成要显示在第二个选项卡中的文本。
如何在单击按钮时切换到第二个选项卡而不违反MVVM模式?
我尝试将TabItem.IsSelected属性绑定到ViewModel中的某些内容,但我也想使用其他选项卡(Tab1)。
你有什么建议吗?

4
请不要把WPF的“Window”称为“form”。那是一种侮辱。 - Federico Berasategui
哈哈,@HighCore - 几天前我跟一个同事说了完全一样的话... - JerKimball
3
请查看这个解释,它解释了什么是TabControl以及从MVVM的角度来看应该如何处理TabControl。链接在此:[https://dev59.com/p2_Xa4cB1Zd3GeqPyTTk#15210593]。 - Federico Berasategui
@HighCore 对不起,我不是故意的。 谢谢你的链接,非常有帮助。 - Joel
5个回答

15
我自己找到了答案。
关键在于双向绑定。当按钮被点击时,它将属性 DisplayXamlTab 设置为 true。 IsSelected 属性与此变量绑定。如果单击另一个选项卡,则绑定将将 DisplayXamlTab 属性设置为 false。
注意:UpdateSourceTrigger=PropertyChanged 也非常重要。
以下是代码:
XAML:
        <TabItem Header="XAML" IsSelected="{Binding DisplayXamlTab, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
            <Grid Background="#FFE5E5E5">
                <TextBox x:Name="TxtXamlOutput" IsReadOnly="True" Text="{Binding XamlText, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible"/>
            </Grid>
        </TabItem>

C# 属性:

private bool displayXamlTab;
public bool DisplayXamlTab
{
    get { return this.displayXamlTab; }
    set
    {
        this.displayXamlTab = value;
        this.RaisePropertyChanged("DisplayXamlTab");
    }
}

你是如何获取 RaisePropertyChanged 的? - Tom Stickel
RaisePropertyChanged 是 MVVMLight 对 INotifyPropertyChanged 接口的实现。 - Joel

12

如果你采用MVVM方式,那么你需要在代码后台创建两个依赖属性:

  • ObservableCollection<ItemType> Items;
  • ItemType MySelectedItem;

然后将TabControl的ItemsSource属性绑定到Items,并将SelectedItem属性绑定到MySelectedItem

    <TabControl ItemsSource="{Binding Items}"
        SelectedItem="{Binding MySelectedItem, Mode=TwoWay}">
<TabControl.ItemTemplate>
    <DataTemplate>
        <... here goes the UI to display ItemType ... >
    </DataTemplate>
  </TabControl.ItemTemplate>
</TabControl>

如果想要更改所选选项卡,只需更新MySelectedItem依赖属性即可。


2

虽然这个问题已经相当老,而且已经有了很好的回答,但我想添加这个额外的答案来演示一种在TabControl中更改所选TabItem的替代方法。如果你为每个TabItem都有一个视图模型,那么在其中具有一个IsSelected属性可以帮助确定它是否被选中。可以使用ItemContainerStyle属性将此IsSelected属性与TabItem.IsSelected属性进行数据绑定:

<TabControl ItemsSource="{Binding MenuItems}" TabStripPlacement="Top">
    <TabControl.ItemTemplate>
        <DataTemplate DataType="{x:Type ControlViewModels:MenuItemViewModel}"> 
            <StackPanel Orientation="Horizontal">
                <Image Source="{Binding ImageSource}" Margin="0,0,10,0" />
                <TextBlock Text="{Binding HeaderText}" FontSize="16" />
            </StackPanel>
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
        <DataTemplate DataType="{x:Type ControlViewModels:MenuItemViewModel}">
            <ContentControl Content="{Binding ViewModel}" />
        </DataTemplate>
    </TabControl.ContentTemplate>
    <TabControl.ItemContainerStyle>
        <Style TargetType="{x:Type TabItem}">
            <Setter Property="IsSelected" Value="{Binding IsSelected}" />
        </Style>
    </TabControl.ItemContainerStyle>
</TabControl>

现在您可以像这样从父视图模型更改所选的TabItem

MenuItems[0].IsSelected = true;

请注意,由于此属性与TabItem.IsSelected属性绑定,因此调用此属性的get访问器将返回该TabItem是否被选中。
MenuItems[1].IsSelected = true;

实际上,这也会自动将MenuItems [0] .IsSelected属性设置为false。因此,如果您正在使用的视图模型其IsSelected属性设置为true,则可以确保其相关视图在TabControl中被选中。


1
你可以在视图模型和 TabControl.SelectedIndex 属性之间创建绑定 - 例如,0 选择第一个 TabItem,1 选择第二个,以此类推。
<TabControl DataContext="..." SelectedIndex="{Binding SomeVmProperty}" ...

另外,根据您的设置方式,您还可以绑定到 SelectedItem...


2
为什么这对我不起作用?属性更改,通知已发送,但UI从未更改选项卡。 - Brock Hensley

0

你可能想要使用某种 "事件聚合器" 模式(例如 MVVM Light 中的 Messenger 类),来广播某种 "导航" 消息。你的视图 - TabControl - 可以监听特定的消息,并在接收到消息时导航到 Tab2。

另外一种方法是将TabControl的"SelectedItem"属性绑定到您的ViewModel,并在ViewModel中调用CurrentTab = MySecondTabViewModel。这是@HighPoint在评论中推荐的做法,但我个人不太喜欢;请参考下面的内容。此方法的另一个注意事项是您需要熟悉DataTemplates,因为您需要将每个要显示的ViewModel映射到一个视图。

个人而言,我更喜欢第一种方法,因为我认为处理选项卡导航并不属于ViewModel的"责任"。如果您只需在ViewModel中的数据发生更改时通知View,您就允许View决定是否要更改选项卡。


这并不是必要的,会增加不必要的开销并降低可维护性。请查看我评论中的链接。 - Federico Berasategui
@HighCore - 请看我上面的评论。我不喜欢使用“SelectedItem”绑定。 - BTownTKD
我不同意。但你说得有道理。另外,对于任何WPF开发人员来说,DataTemplates都是必不可少的。从另一个更抽象的角度来看,你可以认为设置“活动小部件”实际上是VM的责任。 - Federico Berasategui
我想上下文是一个重要的因素。我已经多次使用了SelectedItem绑定,并且觉得它很合适。我也多次使用了事件聚合器,也觉得它很合适 ;) - BTownTKD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接