如何将 TabControl 绑定到 ViewModel 集合?

Posted

技术标签:

【中文标题】如何将 TabControl 绑定到 ViewModel 集合?【英文标题】:How do I bind a TabControl to a collection of ViewModels? 【发布时间】:2011-08-04 18:45:49 【问题描述】:

基本上我的 MainViewModel.cs 中有:

ObservableCollection<TabItem> MyTabs  get; private set; 

但是,我需要以某种方式不仅能够创建选项卡,而且能够在维护 MVVM 的同时加载选项卡内容并将其链接到相应的视图模型。

基本上,我如何才能将用户控件作为 tabitem 的内容加载,并将该用户控件连接到适当的视图模型。使这变得困难的部分是 ViewModel 不应该构造实际的视图项,对吗?或者可以吗?

基本上,这是否适合 MVVM:

UserControl address = new AddressControl();
NotificationObject vm = new AddressViewModel();
address.DataContext = vm;
MyTabs[0] = new TabItem()

    Content = address;

我之所以这么问,是因为我正在从 ViewModel 中构建一个 View (AddressControl),对我来说这听起来像是一个 MVVM 禁忌。

【问题讨论】:

+1 好问题。在 PRISM 指南中,他们并没有真正涵盖这种情况。 他们没有在手册中介绍,但在参考实现中介绍了。 这是一个纯粹的 C#/WPF/MVVM 问题,是否集成/使用 PRISM。 【参考方案1】:

这不是 MVVM。您不应该在视图模型中创建 UI 元素。

您应该将选项卡的 ItemsSource 绑定到您的 ObservableCollection,这应该包含包含有关应创建的选项卡信息的模型。

这是代表标签页的VM和模型:

public sealed class ViewModel

    public ObservableCollection<TabItem> Tabs get;set;
    public ViewModel()
    
        Tabs = new ObservableCollection<TabItem>();
        Tabs.Add(new TabItem  Header = "One", Content = "One's content" );
        Tabs.Add(new TabItem  Header = "Two", Content = "Two's content" );
    

public sealed class TabItem

    public string Header  get; set; 
    public string Content  get; set; 

下面是绑定在窗口中的外观:

<Window x:Class="WpfApplication12.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.DataContext>
        <ViewModel
            xmlns="clr-namespace:WpfApplication12" />
    </Window.DataContext>
    <TabControl
        ItemsSource="Binding Tabs">
        <TabControl.ItemTemplate>
            <!-- this is the header template-->
            <DataTemplate>
                <TextBlock
                    Text="Binding Header" />
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <!-- this is the body of the TabItem template-->
            <DataTemplate>
                <TextBlock
                    Text="Binding Content" />
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
</Window>

(注意,如果你想在不同的选项卡中使用不同的东西,请使用DataTemplates。每个选项卡的视图模型应该是它自己的类,或者创建一个自定义的DataTemplateSelector 来选择正确的模板。)支持>

数据模板内的用户控件:

<TabControl
    ItemsSource="Binding Tabs">
    <TabControl.ItemTemplate>
        <!-- this is the header template-->
        <DataTemplate>
            <TextBlock
                Text="Binding Header" />
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
        <!-- this is the body of the TabItem template-->
        <DataTemplate>
            <MyUserControl xmlns="clr-namespace:WpfApplication12" />
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

【讨论】:

好吧,选项卡的内容是一个用户控件,所以我不会还在我的 ViewModel 中创建新的 UI 实例吗? @michael:在您的示例中,您实际上是在 ViewModel 中创建一个 UI 元素。在我的示例中,我正在创建一个 TabItem 类型的模型。在您的示例中,TabControl(假设)将采用您的 ViewModel 实例化的 TabItems 并将它们显示给用户。在我的,它看到它的ItemsSource,为每一个创建一个选项卡,并根据视图中元素的配置和它正在显示的项目类型绑定每个选项卡的部分。它的一个主要区别。你明白吗? 将其标记为答案需要一段时间,但我终于明白了 DataTemplates 部分的含义。只要我定义了 DataTemplate,WPF 就会根据选项卡中 ViewModel 的类型自动连接 Views/ViewModels。 如果你问我 TabItem 是一个 UI 元素。为什么要在视图模型中创建它? @Gusdor 随便你怎么称呼它。 “组”、“foo”、“迂腐评论”。无论您的设计需要什么。【参考方案2】:

在 Prism 中,您通常使选项卡控制一个区域,这样您就不必控制绑定的选项卡页集合。

<TabControl 
    x:Name="MainRegionHost"
    Regions:RegionManager.RegionName="MainRegion" 
    />

现在可以通过将自身注册到区域 MainRegion 来添加视图:

RegionManager.RegisterViewWithRegion( "MainRegion", 
    ( ) => Container.Resolve<IMyViewModel>( ).View );

在这里您可以看到 Prism 的一个特长。 View 由 ViewModel 实例化。在我的情况下,我通过控制反转容器(例如 Unity 或 MEF)解析 ViewModel。 ViewModel 通过构造函数注入获取 View,并将其自身设置为 View 的数据上下文。

另一种方法是将视图的类型注册到区域控制器中:

RegionManager.RegisterViewWithRegion( "MainRegion", typeof( MyView ) );

使用这种方法,您可以稍后在运行时创建视图,例如由控制器:

IRegion region = this._regionManager.Regions["MainRegion"];

object mainView = region.GetView( MainViewName );
if ( mainView == null )

    var view = _container.ResolveSessionRelatedView<MainView>( );
    region.Add( view, MainViewName );

因为你已经注册了视图的类型,所以视图被放置到了正确的区域中。

【讨论】:

【参考方案3】:

我有一个转换器来解耦 UI 和 ViewModel,这就是下面的重点:

<TabControl.ContentTemplate>
    <DataTemplate>
        <ContentPresenter Content="Binding Tab,Converter=StaticResource TabItemConverter"/>
    </DataTemplate>
</TabControl.ContentTemplate>

Tab 是我的 TabItemViewModel 中的一个枚举,TabItemConverter 将它转换为真实的 UI。

在 TabItemConverter 中,只需获取值并返回您需要的用户控件。

【讨论】:

以上是关于如何将 TabControl 绑定到 ViewModel 集合?的主要内容,如果未能解决你的问题,请参考以下文章

MVVM从TabControl绑定到Page.DataContext

wpf 数据绑定 IsVisible 到 TabControl.SelectedItem != null

TabControl - 数据绑定 TabItem 顺序

使用 MVVM 将新项目添加到 TabControl ItemSsource 时选择最后一个 TabItem

将命令属性添加到任何自定义控件

如何将自定义控件派生的 TabItem 添加到 WPF 中的 TabControl?