WPF选项卡控件和MVVM选择

时间:2013-11-08 10:49:34

标签: wpf mvvm prism tabcontrol

我在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对象,因此所有对象都无缝地进行交互。

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

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

我有一个想法是让子视图(形成上面每个标签项的内容)在他们的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>

我知道发现位不正确;我只是把它放在那里指定我的意图,但我不确定确切的语法。基本上每个UserControl都有一个触发器来监听ViewModel上的属性(不知道我将如何区分每个不同的UserControl,因为显然他们不能全部监听相同的属性,或者当属性设置为是的,但是为每个usercontrol设置一个属性似乎很难看,然后找到它的父TabItem容器并将IsSelected值设置为true。

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

4 个答案:

答案 0 :(得分:41)

如果您查看MSDN上的TabControl Class页面,您会找到一个名为SelectedIndex的属性int。因此,只需在视图模型中添加int属性,然后将Bind属性添加到TabControl.SelectedIndex属性,然后您就可以随时从视图模型中选择您喜欢的任何标签:

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

更新&gt;&gt;&gt;

使用此方法设置“启动”标签更加容易:

在视图模型中:

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

public int SelectedIndex { get; set; } // Implement INotifyPropertyChanged here

答案 1 :(得分: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

VM类

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

答案 2 :(得分:8)

仅供参考, 我经历了同样的问题,我使用ObservableCollection源动态添加标签,但最后添加的Tab没有被选中。 Sheridan说按照SelectedIndex选择Tab,我做了相同的更改。现在最后添加了Tab被选中,但它没有得到集中。 因此要集中Tab,我们必须添加set Binding IsAsync属性True。

<TabControl ItemsSource="{Binding Workspaces}" Margin="5" SelectedIndex="{Binding TabIndex, Mode=OneWay,UpdateSourceTrigger=PropertyChanged, IsAsync=True}">

答案 3 :(得分: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属性。

在我的代码下面:

view.xaml

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

ViewModel.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投标的同时给组件命名是我所有问题的主要原因。