在 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
)并使用附加行为来处理鼠标按下事件并采取相应行动。
我正在尝试解决这个问题: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
)并使用附加行为来处理鼠标按下事件并采取相应行动。