WPF TabControl - 添加带有内容模板的新选项卡

WPF TabControl - Add New Tab With Content Template

我正在尝试制作一个演示应用程序来帮助我理解 WPF/MVVM。我一直在努力查看各种教程和线程 3 天。我想制作一个带有新选项卡按钮(如 here)的选项卡控件,让用户可以使用指定的内容模板创建一个新选项卡。我在这里创建我想成为模板的用户控件:

<UserControl x:Class="MvvmTest.UserControl1"
             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:MvvmTest"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <ListView d:ItemsSource="{d:SampleData ItemCount=5}">
            <ListView.View>
                <GridView>
                    <GridViewColumn/>
                </GridView>
            </ListView.View>
        </ListView>

    </Grid>
</UserControl>

它只是一个带有ListView 的控件。所以,我希望这个 ListView 位于任何打开的新选项卡中。

这是我的主要 window 和实际的选项卡控件:

<Window x:Class="MvvmTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MvvmTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Button Content="New Tab" Margin="703,6,10,401" Click="Button_Click"/>

        <TabControl Name= "TabControl1" Margin="0,33,0,-33" Grid.ColumnSpan="2">
        
        </TabControl>

    </Grid>
</Window>

在此代码隐藏中,我尝试以编程方式创建一个新选项卡并将内容模板设置为新控件。

private void Button_Click(object sender, RoutedEventArgs e)
{
    TabControl1.Items.Add(new TabItem() { ContentTemplate = UserControl1 });
}

这失败了。我还尝试在 XAML 中设置属性,但也失败了。我不确定还能尝试什么。

如果您尝试使用 MVVM,您的视图模型在哪里?到目前为止,您采用的方法不是很 MVVM,因为您使用 code-behind 添加选项卡项。 MVVM 方法是将 TabControlItemSource 属性 绑定到项目集合,然后让视图模型为您添加项目。如果不将 UserControl 包装在 DataTemplate 定义中,您也不能像那样将 UserControl 用作 ContentTemplate

首先要做的是定义一些视图模型:

// MvvmLight (from NuGet) is included for it's INotifyPropertyChanged
// (ViewModelBase) and ICommand (RelayCommand) classes. INotifyPropertyChanged
// is how Binding works between the View and the View Model. You could 
// implement these interfaces yourself if you wanted to. 
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;

using System.Collections.ObjectModel;
using System.Windows.Input;


namespace MvvmTest
{
    public class MainWindowViewModel : ViewModelBase
    {
        // store our list of tabs in an ObservableCollection
        // so that the UI is notified when tabs are added/removed
        public ObservableCollection<TabItemViewModel> Tabs { get; }
            = new ObservableCollection<TabItemViewModel>();

        // this code gets executed when the button is clicked
        public ICommand NewTabCommand 
            => new RelayCommand(() => Tabs.Add(new TabItemViewModel() 
               { Header = $"Tab {Tabs.Count + 1}"})); 
    }

    public class TabItemViewModel : ViewModelBase
    {
        // this is the title of the tab, note that the Set() method
        // invokes PropertyChanged so the view knows if the 
        // header changes
        public string Header
        {
            get => _header;
            set => Set(ref _header, value);
        }
        private string _header;

        // these are the items that will be shown in the list view
        public ObservableCollection<string> Items { get; } 
            = new ObservableCollection<string>() { "One", "Two", "Three" };
    }
}

然后您可以修复您的 XAML,使其引用您定义的 view-models。这需要为您的 MainWindow 定义 DataContext 并将 MainWindow 的元素绑定到视图模型上的属性:

<Window x:Class="MvvmTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MvvmTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <!--Set the DataContent to be an instance of our view-model class -->
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--The Command of the button is bound to the View Model -->
        <Button Grid.Row="0" HorizontalAlignment="Right" Width="100"  
                Content="New Tab" 
                Command="{Binding NewTabCommand}" />

        <!--ItemsSource is bound to the 'Tabs' property on the view-
            model, while DisplayMemeberPath tells TabControl 
            which property on each tab has the tab's name -->
        <TabControl Grid.Row="1" 
                    ItemsSource="{Binding Tabs}" 
                    DisplayMemberPath="Header">

            <!--Defining the ContentTemplate in XAML when is best.
                This template defines how each 'thing' in the Tabs 
                collection will be presented. -->
            <TabControl.ContentTemplate>
                <DataTemplate>
                    <!--The UserControl/Grid were pointless, so I 
                        removed them. ItemsSource of the ListView is 
                        bound to an Items property on each object in
                        the Tabs collection-->
                    <ListView ItemsSource="{Binding Items}">
                        <ListView.View>
                            <GridView>
                                <GridViewColumn Header="Some column"/>
                            </GridView>
                        </ListView.View>
                    </ListView>
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>

    </Grid>
</Window>

结果是当您按下按钮时,会创建并显示一个新选项卡