25

我在MVVM WPF应用程序中有一个 TabControl 。定义如下。

<TabControl Style="{StaticResource PortfolioSelectionTabControl}" SelectedItem="{Binding SelectedParameterTab}" >
    <TabItem Header="Trades" Style="{StaticResource PortfolioSelectionTabItem}">
        <ContentControl Margin="0,10,0,5" Name="NSDetailTradeRegion" cal:RegionManager.RegionName="NSDetailTradeRegion" />
    </TabItem>
    <TabItem Header="Ccy Rates" Style="{StaticResource PortfolioSelectionTabItem}">
        <ContentControl Margin="0,10,0,5" Name="NSDetailCcyRegion" cal:RegionManager.RegionName="NSDetailCcyRegion" />
    </TabItem>
    <TabItem Header="Correlations / Shocks" Style="{StaticResource PortfolioSelectionTabItem}">
        <ContentControl Name="NSDetailCorrelationRegion" cal:RegionManager.RegionName="NSDetailCorrelationRegion" />
    </TabItem>
    <TabItem Header="Facility Overrides" Style="{StaticResource PortfolioSelectionTabItem}" IsEnabled="False">
        <ContentControl Name="NSDetailFacilityOverrides" cal:RegionManager.RegionName="NSDetailFacilityOverrides" />
    </TabItem>
</TabControl>

因此,每个选项卡项内容都有自己的关联视图。这些视图中的每一个都具有MEF [Export]属性并通过视图发现与相关区域相关联,因此上面的代码就是我需要加载选项卡控件并在它们之间切换的全部内容。它们都引用了它们背后的相同共享 ViewModel 对象,因此都可以无缝交互。

我的问题是,当用户导航到父窗口时,我希望选项卡控件默认为第二个选项卡项。这在第一次加载窗口时很容易做到,只需在 XAMLIsSelected="True"中指定 TabItem 编号 2。当用户离开屏幕然后返回屏幕时,就不太容易做到了。

我想过SelectedItem={Binding SelectedTabItem}在选项卡控件上有一个属性,所以我可以在 ViewModel 中以编程方式设置选定的选项卡,但问题是我不知道 ViewModel 中的 TabItem 对象,因为它们仅在上面的 XAML 中声明,所以我没有要传递给 setter 属性的 TabItem 对象。

我的一个想法是让子视图(形成上面每个选项卡项的内容)在其 XAML 的 UserControl 级别上具有样式,如下所示。

<Style TargetType={x:Type UserControl}>
    <Style.Triggers>
        <DataTrigger Property="IsSelected" Value="True">
             <Setter Property="{ElementName={FindAncestor, Parent, typeof(TabItem)}, Path=IsSelected", Value="True" />
        </DataTrigger>
    </Style.Triggers>
</Style>

我知道 findancestor 位不正确;我只是把它放在那里以说明我的意图,但我不确定确切的语法。基本上每个 UserControl 都有一个监听 ViewModel 上的属性的触发器(不确定我将如何区分每个不同的 UserControl,因为显然它们不能都监听相同的属性,或者当属性设置为时它们都会同时选择是的,但每个用户控件都有一个属性看起来很难看),然后找到它的父 TabItem 容器并将 IsSelected 值设置为 true。

我在正确的轨道上找到解决方案了吗?有没有可能做我正在思考的事情?有没有更整洁的解决方案?

4

5 回答 5

54

如果您查看 MSDN 上的TabControlClass页面,您会发现一个名为SelectedIndexwhich is an的属性int。因此,只需将一个int属性添加到您的视图模型并将Bind其添加到该TabControl.SelectedIndex属性,然后您可以随时从视图模型中选择您喜欢的任何选项卡:

<TabControl SelectedIndex="{Binding SelectedIndex}">
    ...
</TabControl>

更新>>>

使用此方法设置“启动”选项卡更加容易:

在视图模型中:

private int selectedIndex = 2; // Set the field to whichever tab you want to start on

public int SelectedIndex { get; set; } // Implement INotifyPropertyChanged here
于 2013-11-08T11:15:20.003 回答
11

仅供参考,我遇到了同样的问题,我使用 ObservableCollection 源动态添加选项卡,但最后添加的选项卡没有被选中。我已经完成了 Sheridan 所说的根据 SelectedIndex 选择 Tab 的相同更改。现在最后添加的选项卡被选中,但它没有得到关注。因此,要关注 Tab,我们必须添加 set Binding IsAsync 属性 True。

<TabControl ItemsSource="{Binding Workspaces}" Margin="5" SelectedIndex="{Binding TabIndex, Mode=OneWay,UpdateSourceTrigger=PropertyChanged, IsAsync=True}">
于 2014-02-12T08:14:45.543 回答
10

下面的代码示例将使用 MVVM 创建一个动态选项卡。

XAML

<TabControl Margin="20" x:Name="tabCategory"
                ItemsSource="{Binding tabCategory}"
                SelectedItem="{Binding SelectedCategory}">

    <TabControl.ItemTemplate>
        <DataTemplate>
            <HeaderedContentControl Header="{Binding TabHeader}"/>
        </DataTemplate>
    </TabControl.ItemTemplate>

    <TabControl.ContentTemplate>
        <DataTemplate>
            <ContentControl Content="{Binding TabContent}" />
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

模态类

TabCategoryItem表示每个选项卡项。在两个属性上, TabHeader将显示选项卡标题,TabContent包含要填充每个选项卡的内容/控件。

Public Class TabCategoryItem

    Public Property TabHeader As String
    Public Property TabContent As UIElement
End Class

虚拟机类

Public Class vmClass

    Public Property tabCategory As ObjectModel.ObservableCollection(Of TabCategoryItem)
    Public Property SelectedCategory As TabCategoryItem
End Class

下面的代码将填充和绑定内容。我正在创建两个选项卡,tab1 和 tab2。两个选项卡都将包含文本框。您可以使用任何 UIelement 代替文本框。

Dim vm As New vmClass

vm.tabCategory = New ObjectModel.ObservableCollection(Of TabCategoryItem)

'VM.tabCategory colection will create all tabs

vm.tabCategory.Add(New TabCategoryItem() With {.TabHeader = "Tab1", .TabContent = new TextBlock().Text = "My first Tab control1"})
vm.tabCategory.Add(New TabCategoryItem() With {.TabHeader = "Tab2", .TabContent = new TextBlock().Text = "My first Tab control2"})

mywindow.DataContent = vm
于 2014-07-09T11:22:18.927 回答
0

接受的答案不适用于您的 ViewModel 上的 DependencyObject 。

我将 MVVM 与 DependencyObject 一起使用,而仅设置 TabControl 对我不起作用。我遇到的问题是,当我从 ViewModel 设置选项卡 selectedIndex 时,属性没有在 View 上获得更新。

我确实将模式设置为两种方式,但没有任何效果。

<TabControl  SelectedIndex="{Binding SelectedTab,Mode=TwoWay}" >
    ...
</TabControl>

当我在选项卡之间导航时,ViewModel 属性“SelectedTab”一直在更新。这是确认我的绑定工作正常。每次我浏览选项卡时,都会在我的 ViewModel 中调用 Get 和 Set。但是,如果我尝试在 ViewModel 中设置 SelectedIndex,它不会更新视图。即:SelectedTab=0 或 SelectedTab=1 等...当从 ViewModel 进行设置时,将调用 SelectedTab 的“设置”方法,但视图永远不会执行“获取”。

我在网上只能找到使用 INotifyPropertyChanged 的​​示例,但我不希望将它与我的 ViewModel 一起使用。

我在此页面中找到了解决方案:http ://blog.lexique-du-net.com/index.php?post/2010/02/24/DependencyProperties-or-INotifyPropertyChanged

使用 DependencyObject,您需要注册 DependencyProperties。不是所有属性,但我猜你需要一个 tabcontrol 属性。

在我的代码下面:

视图.xaml

//Not sure below if I need to mention the TwoWay mode
<TabControl  SelectedIndex="{Binding SelectedTab,Mode=TwoWay}" >
        ...
</TabControl>

视图模型.cs

public class ViewModel : DependencyObject
{
       public static readonly DependencyProperty SelectedTabDP =  DependencyProperty.Register("SelectedTab", typeof(int), typeof(ViewModel));

       public int SelectedTab
       {
          get { return (int)GetValue(SelectedTabDP); }
          set { SetValue(SelectedTabDP, value); }
       }
}

基本上我所要做的就是实际注册依赖属性(DependencyProperty),如上所示。

让这很难弄清楚的是我在那个视图上有一堆其他的属性,我不需要像那样注册它们来让它以两种方式工作。出于某种原因,在 TabControl 上,我必须像上面那样注册该属性。

希望这对其他人有帮助。

原来我的问题是因为我的组件有名称:

x:Name="xxxxxxxx"

在使用 DependencyObject 为组件命名的同时为组件命名似乎是我所有问题的主要原因。

于 2020-05-05T00:54:46.670 回答
0

为了改善我的视图模型的语义并且在使用代码检查所选选项卡时不使用 int,我对接受的答案做了一些补充,以便使用 Enum 而不是 int。这些是步骤:

  • 定义一个表示不同选项卡的 Enum:

     public enum RulesVisibilityMode {
         Active,
         History
     }
    
  • 使用枚举而不是 int 将 SelectedTab 公开为属性:

    public RulesVisibilityMode SelectedTab { get; set; }
    
  • 创建一个转换器以从 int 转换为您的枚举(我不需要 ConvertBack,因为我从不从代码中选择活动选项卡,但您也可以添加它):

    internal class RulesVisibilityModeConverter : IValueConverter
    {
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
             throw new NotImplementedException("Conversion from visibility mode to selected index has not been implemented");
         }
    
         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
         {
             int selectedTabIndex;
             if (int.TryParse(value.ToString(), out selectedTabIndex))
             {
                 return (RulesVisibilityMode)selectedTabIndex;
             }
             return null;
         }
     }
    
    
  • 通过转换器将 tabcontrol 绑定到 SelectedTab 属性:

    <TabControl SelectedIndex="{Binding SelectedTab, Mode=OneWayToSource, Converter={StaticResource RulesVisibilityModeConverter}}" ...
    

现在,每次您需要检查代码中的选定选项卡时,您都会处理一个可读的枚举:

if (this.SelectedTab != RulesVisibilityMode.Active) ...
于 2022-01-21T10:52:12.083 回答