我的英语能力很差,因为我不会说英语。希望您能理解。
我想创建具有可关闭功能的选项卡控件。 (ClosableTabControl)
ClosableTabControl必须具有单击关闭按钮时关闭选项卡项目的功能。另外,我想自动删除与关闭的选项卡项目相关的ItemsSource。
因此,我要在外部项目中使用ClosableTabControl,如下所示。
class MainViewModel
{
public ObservableCollection<DocumentViewModel> Documents {get;}
...
}
class DocumentViewModel
{
public string Title {get;}
public object Content {get;}
}
<Window DataContext="MainViewModel">
<ClosableTabControl ItemsSource="Documents"
HeaderBinding="{Binding Title}"/>
</Window>
如您所见,它不需要连接关闭命令即可删除外部项目中的文档。同样,它不需要重写ItemTemplate即可绑定。 (它将解决使用HeaderBinding功能)我认为上述自定义控件为外部项目带来了便利。
我试图像上面的控件一样创建,但是遇到了下面的问题。
1。它无法删除ClosableTabControl的ItemsSource元素。 (关闭选项卡项时需要)
2。我不知道如何实现HeaderBinding功能。
我应如何解决上述问题?希望您的帮助。
感谢您阅读。
此快速简单的示例扩展了TabControl
,并且还覆盖了Style
的默认TabControl
。新的Style
必须放置在“ / Themes / Generic.xaml”文件中。 Style
会覆盖默认的TabItem
ControlTemplate
,并向其添加一个关闭按钮。
Button.Command
绑定到CloseTabRoutedCommand
的路由命令ClosableTabControl
。调用后,ClosableTabControl
检查是否通过数据绑定或例如通过数据绑定来填充Items
集合。 XAML。
如果通过TabItem
(绑定)创建ItemsSource
,则将执行ICommand
属性ClosableTabControl.RemoveItemCommand
,以使视图模型从集合中删除该项目。这是为了避免直接来自ItemsSource
的项目,这会破坏此属性上的绑定。传递给命令委托的参数是需要删除的选项卡项目的数据模型(在您的情况下为DocumentViewModel
类型)。如果TabItem
是通过XAML创建的,则无需视图模型中的命令即可直接删除该项目。
MainViewModel.cs
class MainViewModel
{
public ObservableCollection<DocumentViewModel> Documents {get;}
// The remove command which is bound to the ClosableTabControl RemoveItemCommand property.
// In case the TabControl.ItemsSource is data bound,
// this command will be invoked to remove the tab item
public ICommand RemoveTabItemCommand => new AsyncRelayCommand<DocumentViewModel>(item => this.Documents.Remove(item));
...
}
ClosableTabControl.cs
public class ClosableTabControl : TabControl
{
public static readonly RoutedUICommand CloseTabRoutedCommand = new RoutedUICommand(
"Close TabItem and remove item from ItemsSource",
nameof(ClosableTabControl.CloseTabRoutedCommand),
typeof(ClosableTabControl));
// Bind this property to a ICommand implementation of the view model
public static readonly DependencyProperty RemoveItemCommandProperty = DependencyProperty.Register(
"RemoveItemCommand",
typeof(ICommand),
typeof(ClosableTabControl),
new PropertyMetadata(default(ICommand)));
public ICommand RemoveItemCommand
{
get => (ICommand) GetValue(ClosableTabControl.RemoveItemCommandProperty);
set => SetValue(ClosableTabControl.RemoveItemCommandProperty, value);
}
static ClosableTabControl()
{
// Override the default style.
// The new Style must be located in the "/Themes/Generic.xaml" ResourceDictionary
DefaultStyleKeyProperty.OverrideMetadata(typeof(ClosableTabControl), new FrameworkPropertyMetadata(typeof(ClosableTabControl)));
}
public ClosableTabControl()
{
this.CommandBindings.Add(
new CommandBinding(ClosableTabControl.CloseTabRoutedCommand, ExecuteRemoveTab, CanExecuteRemoveTab));
}
private void CanExecuteRemoveTab(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = e.OriginalSource is FrameworkElement frameworkElement
&& this.Items.Contains(frameworkElement.DataContext)
|| this.Items.Contains(e.Source);
}
private void ExecuteRemoveTab(object sender, ExecutedRoutedEventArgs e)
{
if (this.ItemsSource == null)
{
object tabItemToRemove = e.Source;
this.Items.Remove(tabItemToRemove);
}
else
{
object tabItemToRemove = (e.OriginalSource as FrameworkElement).DataContext;
if (this.RemoveItemCommand?.CanExecute(tabItemToRemove) ?? false)
{
this.RemoveItemCommand.Execute(tabItemToRemove);
}
}
}
}
Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="{x:Type ClosableTabControl}"
BasedOn="{StaticResource {x:Type TabControl}}">
<Setter Property="Background"
Value="{x:Static SystemColors.ControlBrush}" />
<!-- Add a close button to the tab header -->
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="BorderThickness"
Value="1,1,1,0" />
<Setter Property="Margin"
Value="0,2,0,0" />
<Setter Property="BorderBrush" Value="DimGray" />
<Setter Property="Background" Value="LightGray" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<StackPanel Orientation="Horizontal">
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="12,2,12,2"
RecognizesAccessKey="True" />
<Button Content="X"
Command="{x:Static local:ClosableTabControl.CloseTabRoutedCommand}"
Height="16"
Width="16"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="4" />
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background"
Value="{x:Static SystemColors.ControlBrush}" />
<Setter Property="Panel.ZIndex"
Value="100" />
<Setter Property="Margin"
Value="0,0,0,-1" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
<!-- Provide a default DataTemplate for the tab header
This will only work if the data item has a property Title -->
<Setter Property="ItemTemplate">
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</Setter.Value>
</Setter>
<!-- Provide a default DataTemplate for the tab content
This will only work if the data item has a property Content -->
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ContentPresenter Content="{Binding Content}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
用法示例
<Window>
<Window.DataContext>
<MainViewModel" x:Name="MainViewModel" />
</Window.DataContext>
<ClosableTabControl ItemsSource="{Binding Documents}"
RemoveItemCommand="{Binding RemoveTabItemCommand}" />
</Window>
而不是从ItemsSource
中删除项目(这实际上是您的要求),我建议切换TabItem.Visibility
并将其设置为Visibility.Collapsed
以从视图中删除TabItem
。这是一种更直观,更灵活的行为(例如,重新打开最近关闭的行为)。因为当用户将其从视图中删除时,这并不意味着也必须从视图模型中将其删除。如果视图模型决定really删除数据模型,则只需将其从绑定源集合中删除。这也将消除对ClosableTabControl.RemoveItemCommand
属性的需要,因为Visibility
可以/必须在ClosableTabControl
内部处理。
所以ClosableTabControl.ExecuteRemoveTab
方法将变成:
private void ExecuteRemoveTab(object sender, ExecutedRoutedEventArgs e)
{
object tabItemToRemove = this.ItemsSource == null
? e.Source
: (e.OriginalSource as FrameworkElement).DataContext;
// Select the next tab after the removed tab
int lastItemIndex = this.Items.Count - 1;
int nextItemIndex = this.Items.IndexOf(tabItemToRemove) + 1;
this.SelectedIndex = Math.Min(lastItemIndex, nextItemIndex);
(this.ItemContainerGenerator.ContainerFromItem(tabItemToRemove) as UIElement).Visibility = Visibility.Collapsed;
}
用法示例
<Window>
<Window.DataContext>
<MainViewModel" x:Name="MainViewModel" />
</Window.DataContext>
<ClosableTabControl ItemsSource="{Binding Documents}" />
</Window>