65.9K
CodeProject 正在变化。 阅读更多。
Home

在WPF中动态添加/删除选项卡

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (24投票s)

2012年11月18日

CPOL

3分钟阅读

viewsIcon

200195

downloadIcon

10045

本文展示如何在WPF中实现 TabControl 的添加/删除选项卡功能。

引言

本文展示了如何在WPF(Windows Presentation Foundation)中创建动态选项卡,类似于你在大多数Web浏览器中看到的,在末尾有一个空选项卡用于添加新选项卡,并且每个选项卡上都有一个关闭按钮来关闭该选项卡。有几种技术可以实现这一点。这里,使用List 类型的 TabItem 来保存绑定到 TabControl 的选项卡。List 的最后一个项目是一个空选项卡,用于在选择它时向列表中添加新的 TabItemDataTemplate 用于选项卡标题,以便在每个选项卡中添加一个 **Delete** 按钮。

XAML

第一步是为 TabControl 定义 XAML。将 ItemsSource 属性设置为 {Binding},而 DataContext 将由代码隐藏中的 TabItemList 提供。接下来添加 SelectionChanged 事件。

<TabControl Name="tabDynamic" ItemsSource="{Binding}" 
SelectionChanged="tabDynamic_SelectionChanged">
</TabControl>  

TabControl.Resources 标记下为选项卡标题定义 DataTemplate,并将 DataType 设置为 TabItem。使用任何容器添加和对齐 TextBlock 和删除 Button。我使用了 DockPanel,因为它很容易使用 DockPanel.Dock 属性将删除 Button 对齐到右侧。将 Click 事件添加到按钮,并将 CommandParameter 属性绑定到选项卡的 Name。这将用于识别按钮的 Click 事件处理程序中要删除的选项卡。将 TextBlockText 属性绑定到 TabItemHeaderHeader 是一个 string 值,将在代码隐藏中设置。

<DataTemplate x:Key="TabHeader" DataType="TabItem">
    <DockPanel>
        <Button Name="btnDelete" DockPanel.Dock="Right" Margin="5,0,0,0" 
         Padding="0" Click="btnDelete_Click" 
         CommandParameter="{Binding RelativeSource=
         {RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
            <Image Source="/delete.gif" Height="11" Width="11"></Image>
        </Button>
        <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=
                         {x:Type TabItem}}, Path=Header}" />
    </DockPanel>
</DataTemplate>  

这是完整的 XAML,其中还包括添加到每个选项卡的 TextBox 控件的样式

<TabControl Name="tabDynamic" ItemsSource="{Binding}" 
 SelectionChanged="tabDynamic_SelectionChanged">
    <TabControl.Resources>
        <DataTemplate x:Key="TabHeader" DataType="TabItem">
            <DockPanel>
                <Button Name="btnDelete" DockPanel.Dock="Right" 
                 Margin="5,0,0,0" Padding="0" Click="btnDelete_Click" 
                 CommandParameter="{Binding RelativeSource=
                 {RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
                    <Image Source="/delete.gif" Height="11" Width="11"></Image>
                </Button>
            <TextBlock Text="{Binding RelativeSource=
            {RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
            </DockPanel>
        </DataTemplate>
        <Style TargetType="TextBox">
            <Setter Property="VerticalAlignment" Value="Stretch"></Setter>
            <Setter Property="HorizontalAlignment" Value="Stretch"></Setter>
            <Setter Property="AcceptsReturn" Value="True"></Setter>
            <Setter Property="TextWrapping" Value="WrapWithOverflow"></Setter>
            <Setter Property="MaxLines" Value="5000"></Setter>
            <Setter Property="ScrollViewer.VerticalScrollBarVisibility" 
             Value="Auto"></Setter>
            <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" 
             Value="Auto"></Setter>
        </Style>
    </TabControl.Resources>
</TabControl>  

现在我解释包含添加和删除 TabItems 逻辑的代码。

后台代码

在代码隐藏文件中,定义两个数据成员。一个是 _tabItems,它是一个 TabItem 列表,用于保存选项卡,另一个是 _tabAdd,它是一个指向最后一个 TabItem 的引用,用于动态添加新选项卡。

private List<TabItem> _tabItems;
private TabItem _tabAdd;    

在构造函数中初始化以上数据成员,并将最后一个选项卡的标题设置为文本“**+**”或您喜欢的任何图像。使用函数 AddItemItem() 添加第一个 TabItem,然后将 TabControl 绑定到 List 并选择第一个 TabItem

public MainWindow() 
{ 
    try
    {
        InitializeComponent();

        // initialize tabItem array
        _tabItems = new List<TabItem>();

        // add a tabItem with + in header 
        TabItem tabAdd = new TabItem();
        tabAdd.Header = "+";

        _tabItems.Add(tabAdd);

        // add first tab
        this.AddTabItem();

        // bind tab control
        tabDynamic.DataContext = _tabItems;

        tabDynamic.SelectedIndex = 0;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}  

在上面使用的函数 AddTabItem() 中,向列表 _tabItems 添加一个新的 TabItem,并返回该 TabItem 的实例。将 HeaderTemplate 设置为我们上面定义的那个。给 TabItem 一个唯一的名称,例如“TabXX”,其中 XX 是一个计数器,使名称唯一。您也可以使用相同的唯一字符串作为 Header 文本。设置 HeaderName 属性非常重要,因为在上面 XAML 中定义的 DataTemplate 中,我们将 TextBlock.TextButton.CommandParameter 绑定到这些属性。接下来,添加您想要在 TabItem 中拥有的控件,并将 TabItem 添加到列表 _tabItems 中。在这里,我添加了一个简单的 TextBox 控件。另请注意,新选项卡插入在用于添加新选项卡的最后一个选项卡之前。

private TabItem AddTabItem()
{
    int count = _tabItems.Count;

    // create new tab item
    TabItem tab = new TabItem();
    tab.Header = string.Format("Tab {0}", count);
    tab.Name = string.Format("tab{0}", count);
    tab.HeaderTemplate = tabDynamic.FindResource("TabHeader") as DataTemplate;
 
    // add controls to tab item, this case I added just a text box
    TextBox txt = new TextBox();
    txt.Name = "txt";
    tab.Content = txt;

    // insert tab item right before the last (+) tab item
    _tabItems.Insert(count - 1, tab);
    return tab; 
} 

SelectionChanged 事件处理程序的实现中,检查选定的 TabItem 是否是最后一个,然后使用 AddTabItem() 函数添加一个新的 TabItem,重新绑定 TabControl 并选择新添加的 TabItem。对于其他选项卡,您可以为此事件添加您的代码(如果有)。

private void tabDynamic_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    TabItem tab = tabDynamic.SelectedItem as TabItem;

    if (tab != null && tab.Header != null)
    {
        if (tab.Header.Equals(_addTabHeader))
        {
            // clear tab control binding
            tabDynamic.DataContext = null;

            // add new tab
            TabItem newTab = this.AddTabItem();

            // bind tab control
            tabDynamic.DataContext = _tabItems;

            // select newly added tab item
            tabDynamic.SelectedItem = newTab;
        }
        else
        {
            // your code here...
        }
    }
}

最后,实现添加到 DataTemplate 的删除按钮的 Click 事件处理程序。在这里,使用 CommandParameter 识别要删除的选项卡,后者又给出 TabItem.Name。您可以使用 linq 在 TabControl.Items 集合中轻松找到此选项卡。验证此选项卡不是列表中剩下的唯一选项卡,然后在确认后,从列表 _tabItems 中删除该选项卡,并重新绑定 TabControl。如果删除了一个活动选项卡,则在重新绑定后选择第一个选项卡,否则选择删除之前选择的选项卡。

private void btnDelete_Click(object sender, RoutedEventArgs e)
{
    string tabName = (sender as Button).CommandParameter.ToString();

    var item = tabDynamic.Items.Cast<tabitem>().Where
               (i => i.Name.Equals(tabName)).SingleOrDefault();

    TabItem tab = item as TabItem;

    if (tab != null)
    {
        if (_tabItems.Count < 3)
        {
            MessageBox.Show("Cannot remove last tab.");
        }
        else if (MessageBox.Show(string.Format
        ("Are you sure you want to remove the tab '{0}'?", tab.Header.ToString()),
            "Remove Tab", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
        {
            // get selected tab
            TabItem selectedTab = tabDynamic.SelectedItem as TabItem;

            // clear tab control binding
            tabDynamic.DataContext = null;

            _tabItems.Remove(tab);

            // bind tab control
            tabDynamic.DataContext = _tabItems;

            // select previously selected tab. if that is removed then select first tab
            if (selectedTab == null || selectedTab.Equals(tab))
            {
                selectedTab = _tabItems[0];
            }
            tabDynamic.SelectedItem = selectedTab;
        }
    }
}

历史

  • 2012年11月18日:初始版本
© . All rights reserved.