如何将TabControl绑定到ViewModels的集合?


73

基本上我在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指南中,它们并没有真正涵盖这种情况。
MarkusHütter

他们没有在手册中进行介绍,但是在参考实现中进行了介绍。
PVitt 2011年

这是一个纯粹的C#/ WPF / MVVM问题,是否已集成/未使用PRISM。
IAbstract

Answers:


141

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

您应该将Tab的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来选择正确的模板。)

数据模板内的UserControl:

<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>

4
好吧,尽管选项卡的内容是一个用户控件,所以我还不会在ViewModel中制作新的UI实例吗?
迈克尔

4
@michael:在您的示例中,您实际上是在ViewModel中创建一个UI元素。在我的示例中,我正在创建TabItem类型的模型。在您的示例中,TabControl(假设)将采用您的ViewModel实例化的TabItem,并将其显示给用户。在我的系统中,它查看其ItemsSource,为每个选项卡创建一个选项卡,并根据View中元素的配置及其所显示项的类型来绑定每个选项卡的部分。其主要区别。你懂这个了吗?

5
花了一段时间将其标记为答案,但我终于弄清楚了DataTemplates部分的含义。只要我定义DataTemplate,WPF就会根据选项卡中ViewModel的类型自动连接Views / ViewModels。
迈克尔

3
TabItem如果您问我,则是一个UI元素。为什么在视图模型中创建该视图?
2014年

3
@Gusdor随便你怎么称呼它。“组”,“ foo”,“学究注释”。无论您的设计需要什么。

20

在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 );
}

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


1

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

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

Tab是我的TabItemViewModel中的枚举,并且TabItemConverter将其转换为真实的UI。

在TabItemConverter中,只需获取值并返回所需的用户控件即可。


-3

可能是这样的:

<UserControl x:Class="Test_002.Views.MainView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:Test_002.Views"
         xmlns:generalView="clr-namespace:Test_002.Views.General"
         xmlns:secVIew="clr-namespace:Test_002.Views.Security"
         xmlns:detailsView="clr-namespace:Test_002.Views.Details"
         mc:Ignorable="d" 
         d:DesignHeight="400" d:DesignWidth="650">
<Grid>
    <DockPanel>
        <StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" Margin="2,5">
            <Button Command="{Binding btnPrev}" Content="Prev"/>
            <Button Command="{Binding btnNext}" Content="Next"/>
            <Button Command="{Binding btnSelected}" Content="Selected"/>
        </StackPanel>
        <TabControl>
            <TabItem Header="General">
                <generalView:GeneralView></generalView:GeneralView>
            </TabItem>
            <TabItem Header="Security">
                <secVIew:SecurityView></secVIew:SecurityView>
            </TabItem>
            <TabItem Header="Details">
                <detailsView:DetailsView></detailsView:DetailsView>
            </TabItem>
        </TabControl>
    </DockPanel>
</Grid>

认为这是最简单的方法。MVVM兼容吗?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.