如何创建选项卡控件的可关闭选项卡项(自定义控件)?



我的英语水平很差,因为我的母语不是英语。 希望你能理解。

我想创建具有可关闭功能的选项卡控件。(可关闭选项卡控件(

ClosesableTabControl 必须具有在单击关闭按钮时关闭选项卡项的功能。 另外,我想自动删除与关闭的选项卡项相关的项目来源。

因此,我想在外部项目中使用 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>

如您所见,它不需要连接关闭命令即可删除外部项目中的文档。 此外,它不需要重写要绑定的项模板。(它将使用标头绑定功能解决( 我认为上面的自定义控件为外部项目提供了便利。

我试图像上面的控件一样创建,但我遇到了如下问题。

1.它不能删除元素 项目源 的 可关闭标签控件.(关闭选项卡项时需要(

2. 我不知道如何实现标头绑定功能。

我应该怎么做才能解决上述问题? 我希望你的帮助。

感谢您的阅读。

这个快速而简单的示例扩展了TabControl,并且还覆盖了默认StyleTabControl。新Style必须放在"/Themes/Generic.xaml">文件中。Style将覆盖默认TabItemControlTemplate并向其添加关闭按钮。

Button.Command绑定到ClosableTabControl的路由命令CloseTabRoutedCommand
调用后,ClosableTabControl检查Items集合是通过数据绑定还是 XAML 填充的。

如果TabItem是通过ItemsSource(绑定(创建的,则将执行ICommand属性ClosableTabControl.RemoveItemCommand,以便让视图模型从集合中删除该项。这是为了避免直接从ItemsSource中获取项,这会破坏此属性上的绑定。传递给命令委托的参数是请求删除的选项卡项的数据模型(在类型DocumentViewModel的情况下(。
如果TabItem是通过 XAML 创建的,则无需从视图模型中执行命令即可直接删除该项。

主视图模型.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));
...
}

可关闭选项卡控件.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);
}
}
}
}

通用.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。这是一种更直观、更灵活的行为(例如,最近关闭的重新开放(。因为当用户将其从视图中删除时,并不意味着也必须将其从视图模型中删除。如果视图模型决定真正删除数据模型,则只需将其从绑定源集合中删除即可。这也将消除对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>

最新更新