我的英语水平很差,因为我的母语不是英语。 希望你能理解。
我想创建具有可关闭功能的选项卡控件。(可关闭选项卡控件(
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
,并且还覆盖了默认Style
TabControl
。新Style
必须放在"/Themes/Generic.xaml">文件中。Style
将覆盖默认TabItem
ControlTemplate
并向其添加关闭按钮。
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>