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

时间:2015-02-27 13:03:04

标签: c# wpf tabcontrol

我试图实施这个问题:Closing TabItem by clicking middle button

但是,CloseCommandExecuted()的e.Source属性返回一个TabControl对象。这不能用于确定单击了哪个TabItem。 e.OriginalSource返回在datatemplate内部定义的Grid。最后,从原始源向上的父母后面永远不会导致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;
}

主窗口代码

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

3 个答案:

答案 0 :(得分:2)

根据您添加到问题中的代码,您可以执行以下操作:

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

它为您提供了它所绑定的TabItemTabContent对象。

答案 1 :(得分:0)

我只是将这个样本放在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);
           }
      }
 }

答案 2 :(得分:0)

我认为MVVM这样做的方法是为每个标签添加一个按钮,当单击它时,会触发一个命令来关闭该标签或其他任何标签。例如,这是Chrome关闭标签时的工作方式。这不是您要求的“中间点击”解决方案,但可以通过一些额外的工作进行修改以实现这种方式。

无论如何,您可以向TabContent类添加命令属性:

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的类:我确定你已经看过或使用过类似的类型。执行命令时会触发一个事件。创建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>

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

从您的角度来看,唯一的问题是按钮只会执行命令以响应单击左键。要使其与中键单击相反,您需要使用其他内容替换button元素(例如,Border)并使用附加行为来处理鼠标按下事件并相应地执行操作。