在 WPF 中单击(但未选中)TabItem

Getting clicked (but not selected) TabItem in WPF

我正在尝试解决这个问题:Closing TabItem by clicking middle button

但是,e.Source 属性 的 CloseCommandExecuted() returns 一个 TabControl 对象。这不能用于确定单击了哪个 TabItem。 e.OriginalSource returns 在数据模板内部定义的网格。最后,从原始源向上跟随父级永远不会导致 TabItem 对象。

如何获取用户点击的TabItem?

编辑:在我的例子中,我通过 ItemsSource 绑定对象。

//Xaml for the Window
<Window.Resources>
    <DataTemplate x:Key="closableTabTemplate">
        <Border x:Name="testBorder">
            <Grid>
                <Grid.InputBindings>
                    <MouseBinding Command="ApplicationCommands.Close" Gesture="MiddleClick" />
                </Grid.InputBindings>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid>
                    <TextBlock Text="{Binding Headertext}"></TextBlock>
                </Grid>
            </Grid>
        </Border>
    </DataTemplate>
</Window.Resources>
<Window.CommandBindings>
    <CommandBinding Command="ApplicationCommands.Close" Executed="CloseCommandExecuted" CanExecute="CloseCommandCanExecute" />
</Window.CommandBindings>
<Grid>
    <TabControl x:Name="MainTabControl" ItemTemplate="{StaticResource closableTabTemplate}" Margin="10">
        <TabControl.ItemContainerStyle>
            <Style TargetType="TabItem">
                <!--<Setter Property="Header" Value="{Binding ModelName}"/>-->
                <Setter Property="Content" Value="{Binding Content}"/>
            </Style>
        </TabControl.ItemContainerStyle>
    </TabControl>
</Grid>

我绑定的对象。这是针对此问题的示例

public class TabContent
{
    public string Headertext { get; set; }
    public FrameworkElement Content = null;
}

主要window代码

public partial class MainWindow
{
    public ObservableCollection<TabContent> MyCollection = new ObservableCollection<TabContent>();

    public MainWindow()
    {
        MyCollection.Add(new TabContent { Headertext = "item1" });
        MyCollection.Add(new TabContent { Headertext = "item2" });
        MyCollection.Add(new TabContent { Headertext = "item3" });

        InitializeComponent();

        MainTabControl.ItemsSource = MyCollection;
        MainTabControl.SelectedIndex = 0;
    }

    private void CloseCommandExecuted(object sender, ExecutedRoutedEventArgs e)
    {
        //Need some way to access the tab item or item bound to tab item

        //if (tabitem != null)
        //{
        //    //MainTabControl.Items.Remove(tabitem);
        //}
    }

    private void CloseCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
    }
}

我只是把这个样本放在一起,在xaml,一个空的TabControl

<TabControl x:Name="MyTabControl">            
</TabControl>

假设您在运行时加载 TabItem 对象,我将其添加到 Window

的构造函数中
public MainWindow()
{
    InitializeComponent();

    // Add some sample tabs to the tab control
    for (int i = 0; i < 5; i++)
    {
        TabItem ti = new TabItem() { Header = String.Format("Tab {0}", i + 1) };
        ti.PreviewMouseDown += ti_PreviewMouseDown;
        MyTabControl.Items.Add(ti);
    }
}

如果你想在 xaml

中定义你的选项卡
<TabControl x:Name="MyTabControl">
    <TabItem Header="Tab 1" PreviewMouseDown="ti_PreviewMouseDown"></TabItem>
    <TabItem Header="Tab 2" PreviewMouseDown="ti_PreviewMouseDown"></TabItem>
    <TabItem Header="Tab 3" PreviewMouseDown="ti_PreviewMouseDown"></TabItem>
    <TabItem Header="Tab 4" PreviewMouseDown="ti_PreviewMouseDown"></TabItem>
    <TabItem Header="Tab 5" PreviewMouseDown="ti_PreviewMouseDown"></TabItem>
</TabControl>

然后在事件处理程序中

 void ti_PreviewMouseDown(object sender, MouseButtonEventArgs e)
 {
      TabItem clickedTabItem = sender as TabItem;
      if (clickedTabItem != null)
      {               
           if (e.ChangedButton == MouseButton.Middle && e.ButtonState == MouseButtonState.Pressed)
           {
                // Do whatever you want to do with clickedTabItem here, I'm removing it from the TabControl
                MyTabControl.Items.Remove(clickedTabItem);
           }
      }
 }

根据您添加到问题中的代码,您可以这样做:

private void CloseCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{           
    DependencyObject dep = (DependencyObject)e.OriginalSource;
    // Traverse the visual tree looking for TabItem
    while ((dep != null) && !(dep is TabItem))
        dep = VisualTreeHelper.GetParent(dep);

    if (dep == null)
    {
        // Didn't find TabItem
        return;
    }

    TabItem tabitem = dep as TabItem;
    if (tabitem != null)
    {
        TabContent content = tabitem.Header as TabContent;
        if(content !=null)
            MyCollection.Remove(content);               
    }
}

这为您提供了 TabItem 和它所绑定的 TabContent 对象。

我认为 MVVM 的做法是向每个选项卡添加一个按钮,单击该按钮时会触发关闭该选项卡或其他内容的命令。例如,这就是 Chrome 在关闭标签时的工作方式。这不是您要求的 "middle-click" 解决方案,但可以通过一些额外的工作进行修改以按此方式工作。

无论如何,你会添加一个命令 属性 到你的 TabContent class:

public class TabContent
{
    ...
    public RelayCommand CloseTabCommand { get; private set; }

    public TabContent()
    {
        CloseTabCommand = new RelayCommand(OnTabClosing);
    }

    public event EventHandler TabClosing;

    void OnTabClosing()
    {
        var handler = TabClosing;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }
}

这里我使用的是 RelayCommand,它只是实现 ICommand 的 class:我相信您已经看到或使用过类似的类型。当命令被执行时,它会触发一个事件。当您创建 TabItem 个对象时,您需要注册以处理此事件:

public MainWindow()
{
    MyCollection.Add(new TabContent { Headertext = "item1" });
    MyCollection.Add(new TabContent { Headertext = "item2" });
    MyCollection.Add(new TabContent { Headertext = "item3" });

    ...

    foreach (TabContent tab in MyCollection)
    {
        tab.TabClosing += OnTabClosing;
    }
}

然后,在事件处理程序中,sender 参数将引用触发事件的 TabContent 对象:

void OnTabClosing(object sender, EventArgs e)
{
    MyCollection.Remove(sender as TabContent);
}

在这里,我通过从集合中删除该选项卡来响应该事件。

在XAML方面,您只需修改数据模板以添加一个按钮控件,该按钮控件将通过绑定调用自定义命令:

<DataTemplate x:Key="closableTabTemplate">
    <Button Command="{Binding CloseTabCommand}">
        <TextBlock Text="{Binding Headertext}"></TextBlock>
    </Button>
</DataTemplate>

显然,如果您想要的话,您可以更改按钮的模板以移除边框并使其看起来不像按钮。

从您的角度来看,唯一的问题是该按钮只会在单击左键时执行命令。要使其与中键单击一起工作,您需要将按钮元素替换为其他元素(例如,Border)并使用附加行为来处理鼠标按下事件并采取相应行动。