WPF 选项卡控件和 MVVM 选择

Posted

技术标签:

【中文标题】WPF 选项卡控件和 MVVM 选择【英文标题】:WPF tab control and MVVM selection 【发布时间】:2013-11-20 08:27:26 【问题描述】:

我在 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"。当用户离开屏幕然后返回屏幕时,就不太容易做到了。

我曾想过在选项卡控件上有一个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>

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

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

【问题讨论】:

为什么不创建将选择第二个标签的事件? 理想情况下,我想在 MVVM 中执行此操作,因此后面没有任何代码。同意我可以在后面的代码中添加一个事件,但希望有一个 MVVM 解决方案 TabItem 在 MVVM 中没有意义。 TabItem 只是 UI。 SelectedTab 行为将在后面的代码中。 @user1122909 您可以使用交互库,然后,例如,当您的应用程序将获得焦点或其他事件时,取决于您的需要,您可以在 viewModel 中而不是在代码中编写方法后面。 【参考方案1】:

如果您查看 MSDN 上的 TabControl Class 页面,您会发现一个名为 SelectedIndex 的属性,它是一个 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

【讨论】:

简单的答案和好的结果。我想到的另一个想法是,如果您想通过在 XAML 中指定来控制哪个选项卡项是启动选项卡,只需在该 TabItem 上放置一个绑定,例如 IsSelected=Binding CcyTabSelected,并在 ViewModel 上拥有一个名为 CcyTabSelected 的布尔属性。当您想返回启动选项卡时,只需将该属性设置为 true。略有不同的应用程序,此应用程序可让您轻松返回主选项卡,您的解决方案允许按索引选择列表中的任何选项卡项 -1 表示不礼貌,+1 表示回答好。 SO 是为非常具体的问题找到明确答案的好地方。我认为人们对 MSDN 页面上已有的内容进行“总结”是有好处的。当您知道自己的问题是什么时,SO 往往比 MSDN 更具可读性和更好、更快的资源。 我不能指出用户可以自己在 MSDN 上快速轻松地找到这些答案吗? -1 表示不礼貌且对 MVVM 使用不当。将选定的页面索引绑定为 int类型不安全的。使用 enumIValueConverter 会更符合良好的 MVVM 实践 @HelloWorld - IValueConverter sample【参考方案2】:

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

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

【讨论】:

【参考方案3】:

以下代码示例将使用 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。两个选项卡都将包含文本框。您可以使用任何 UI 元素代替文本框。

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

【讨论】:

【参考方案4】:

接受的答案不适用于您的 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 出价的同时为组件命名似乎是我所有问题的主要原因。

【讨论】:

以上是关于WPF 选项卡控件和 MVVM 选择的主要内容,如果未能解决你的问题,请参考以下文章

WPF 选项卡控件样式

从用户控件中导航WPF选项卡控件?

如何将 WPF 选项卡项标题拉伸到父控件宽度

WPF 控件库——可拖动选项卡的TabControl

在 WPF 中开始时绑定所有选项卡项视图模型

根据选定的选项卡更改 Tabcontrol 外部的控件属性