具有删除按钮绑定问题的 WPF 自定义 TabItem
WPF-Custom TabItem with Delete Button Binding Issue
您好,我正在尝试创建一个带有删除按钮的自定义 TabItem,我想将我的视图模型命令绑定到我的自定义依赖项 属性 'DeleteCommandProperty'。有人可以告诉我我做错了什么吗?
我的自定义选项卡控件:
/// <summary>
/// TabControl withCustom TabItem
/// </summary>
public class MyTabControl:TabControl
{
/// <summary>
/// TabItem override
/// </summary>
/// <returns></returns>
protected override DependencyObject GetContainerForItemOverride()
{
return new MyTabItem();
}
}
我的自定义 TabItem class :
/// <summary>
/// Custom TabItem
/// </summary>
public class MyTabItem:TabItem
{
/// <summary>
/// Delete Command
/// </summary>
public static DependencyProperty DeleteCommandProperty = DependencyProperty.Register(
"DeleteCommand",typeof(ICommand),typeof(MyTabItem));
/// <summary>
/// Delete
/// </summary>
public ICommand DeleteCommand
{
get { return (ICommand)GetValue(DeleteCommandProperty); }
set { SetValue(DeleteCommandProperty, value); }
}
}
当我像这样直接绑定 DeleteCommand 时,我的 ViewModel 中的命令被执行
<customControls:MyTabControl>
<customControls:MyTabItem Header="Test" DeleteCommand="{Binding DeleteStudiengangCommand}" Template="{DynamicResource MyTabItemControlTemplate}"/>
</customControls:MyTabControl>
bu 当尝试通过这样的样式绑定 deleteCommand 但它不起作用时:
<Style TargetType="customControls:MyTabItem">
<Setter Property="Template" Value="{DynamicResource MyTabItemControlTemplate}"/>
<Setter Property="DeleteCommand" Value="{Binding MyDeleteCommand}"/>
</Style>
<customControls:MyTabControl ItemsSource="{Binding MyList}" SelectedItem="{Binding SelectedItem}" SelectedIndex="0">
<customControls:MyTabControl.ContentTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Value}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</customControls:MyTabControl.ContentTemplate>
</customControls:MyTabControl>
TL;DR
我怀疑您的 DataContext
包含您当前来自 MyList
的项目(无论它是什么),因此样式 setter 无法访问 MyDeleteCommand
属性正如你计划的那样。查看 visual studio 调试器日志,是否记录了绑定异常。
从工作代码演变而来的示例,直到它碰巧揭示了可能的问题解释。
看来您在减少示例方面遇到了一些困难,因此根据您提供的信息,我唯一能为您提供的是一个使用 Style
和 TemplateBinding
方法的小型工作示例您可以以此为基础来确定您的真正问题。 编辑:最后的解释可能就是你问题的答案。
请注意,您可能需要更改名称空间以匹配您的项目设置。
MainWindow.xaml
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded">
<Window.Resources>
<!-- Header template -->
<ControlTemplate x:Key="MyTabItemControlTemplate" TargetType="{x:Type local:MyTabItem}">
<!-- Some text and the command button with template binding -->
<StackPanel Orientation="Horizontal" Background="Aquamarine" Margin="3">
<ContentPresenter Content="{TemplateBinding Header}" VerticalAlignment="Center" Margin="2"/>
<Button Content="Delete" Command="{TemplateBinding DeleteCommand}" Margin="2"/>
</StackPanel>
</ControlTemplate>
<!-- Setting the control template and assigning the command implementation -->
<Style TargetType="{x:Type local:MyTabItem}">
<Setter Property="Template" Value="{DynamicResource MyTabItemControlTemplate}"/>
<Setter Property="DeleteCommand" Value="{Binding MyDeleteCommand}"/>
<Setter Property="Header" Value="Default Header Text"/>
</Style>
</Window.Resources>
<Grid>
<local:MyTabControl ItemsSource="{Binding MyTabItemList}"/>
</Grid>
</Window>
MainWindow.xaml.cs
/// <summary>
/// TabControl withCustom TabItem
/// </summary>
public class MyTabControl : TabControl
{
/// <summary>
/// TabItem override
/// </summary>
protected override DependencyObject GetContainerForItemOverride()
{
return new MyTabItem();
}
}
public class MyTabItem : TabItem
{
/// <summary>
/// Delete Command
/// </summary>
public static DependencyProperty DeleteCommandProperty = DependencyProperty.Register(
"DeleteCommand", typeof(ICommand), typeof(MyTabItem));
/// <summary>
/// Delete
/// </summary>
public ICommand DeleteCommand
{
get { return (ICommand)GetValue(DeleteCommandProperty); }
set { SetValue(DeleteCommandProperty, value); }
}
}
public class MyCommand : ICommand
{
public void Execute(object parameter)
{
MessageBox.Show("Hello WPF", "Message");
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged { add { } remove { } }
}
public class MyContext
{
public ICommand MyDeleteCommand { get; set; }
public List<object> MyTabItemList { get; set; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var list = new List<object>();
list.Add(new TextBlock() { Text = "Test 1" });
list.Add(new MyTabItem() { Content = "Test Content 2", Header = "Test Header 2" });
list.Add(new TabItem() { Content = "Test Content 3", Header = "Test Header 3" });
this.DataContext = new MyContext()
{
MyTabItemList = list,
MyDeleteCommand = new MyCommand()
};
}
}
示例总结:
您会看到三个不同的选项卡,每个选项卡都有其独特的构造和外观:
- 选项卡项是通过
GetContainerForItemOverride
方法创建的,Header
属性 的样式 setter 指定 header 中出现的文本. MyDeleteCommand
绑定无效!
- 选项卡项以
MyTabItem
形式提供,具有 header 和内容值。 Header
属性 的样式 setter 不适用,因为 属性 是明确设置的。 DeleteCommand
属性 的样式 setter 绑定到 MyContext
. 的 MyDeleteCommand
属性
- 选项卡项作为
TabItem
提供,并且由于没有覆盖 MyTabControl.IsItemItsOwnContainerOverride
,这被接受为 MyTabControl
的控制项。整个 MyTabItem
模板不适用。
visual studio 中的调试输出给出了有关第一个选项卡项的潜在问题的提示:
System.Windows.Data Error: 40 : BindingExpression path error: 'MyDeleteCommand' property not found on 'object' ''TextBlock' (Name='')'. BindingExpression:Path=MyDeleteCommand; DataItem='TextBlock' (Name=''); target element is 'MyTabItem' (Name=''); target property is 'DeleteCommand' (type 'ICommand')
原因是,在这种情况下,当前选项卡项内容成为新的本地 DataContext
,这与第二个选项卡项不同,在第二个选项卡项中,项目本身被接受为容器。
一个解决方案可能是确保在命令绑定上使用正确的上下文:
假设你给一些合适的parent元素起一个名字x:Name="_this"
,那么你就可以访问parentDataContext
.
<Setter Property="DeleteCommand" Value="{Binding DataContext.MyDeleteCommand,ElementName=_this}"/>
您好,我正在尝试创建一个带有删除按钮的自定义 TabItem,我想将我的视图模型命令绑定到我的自定义依赖项 属性 'DeleteCommandProperty'。有人可以告诉我我做错了什么吗?
我的自定义选项卡控件:
/// <summary>
/// TabControl withCustom TabItem
/// </summary>
public class MyTabControl:TabControl
{
/// <summary>
/// TabItem override
/// </summary>
/// <returns></returns>
protected override DependencyObject GetContainerForItemOverride()
{
return new MyTabItem();
}
}
我的自定义 TabItem class :
/// <summary>
/// Custom TabItem
/// </summary>
public class MyTabItem:TabItem
{
/// <summary>
/// Delete Command
/// </summary>
public static DependencyProperty DeleteCommandProperty = DependencyProperty.Register(
"DeleteCommand",typeof(ICommand),typeof(MyTabItem));
/// <summary>
/// Delete
/// </summary>
public ICommand DeleteCommand
{
get { return (ICommand)GetValue(DeleteCommandProperty); }
set { SetValue(DeleteCommandProperty, value); }
}
}
当我像这样直接绑定 DeleteCommand 时,我的 ViewModel 中的命令被执行
<customControls:MyTabControl>
<customControls:MyTabItem Header="Test" DeleteCommand="{Binding DeleteStudiengangCommand}" Template="{DynamicResource MyTabItemControlTemplate}"/>
</customControls:MyTabControl>
bu 当尝试通过这样的样式绑定 deleteCommand 但它不起作用时:
<Style TargetType="customControls:MyTabItem">
<Setter Property="Template" Value="{DynamicResource MyTabItemControlTemplate}"/>
<Setter Property="DeleteCommand" Value="{Binding MyDeleteCommand}"/>
</Style>
<customControls:MyTabControl ItemsSource="{Binding MyList}" SelectedItem="{Binding SelectedItem}" SelectedIndex="0">
<customControls:MyTabControl.ContentTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Value}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</customControls:MyTabControl.ContentTemplate>
</customControls:MyTabControl>
TL;DR
我怀疑您的 DataContext
包含您当前来自 MyList
的项目(无论它是什么),因此样式 setter 无法访问 MyDeleteCommand
属性正如你计划的那样。查看 visual studio 调试器日志,是否记录了绑定异常。
从工作代码演变而来的示例,直到它碰巧揭示了可能的问题解释。
看来您在减少示例方面遇到了一些困难,因此根据您提供的信息,我唯一能为您提供的是一个使用 Style
和 TemplateBinding
方法的小型工作示例您可以以此为基础来确定您的真正问题。 编辑:最后的解释可能就是你问题的答案。
请注意,您可能需要更改名称空间以匹配您的项目设置。
MainWindow.xaml
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded">
<Window.Resources>
<!-- Header template -->
<ControlTemplate x:Key="MyTabItemControlTemplate" TargetType="{x:Type local:MyTabItem}">
<!-- Some text and the command button with template binding -->
<StackPanel Orientation="Horizontal" Background="Aquamarine" Margin="3">
<ContentPresenter Content="{TemplateBinding Header}" VerticalAlignment="Center" Margin="2"/>
<Button Content="Delete" Command="{TemplateBinding DeleteCommand}" Margin="2"/>
</StackPanel>
</ControlTemplate>
<!-- Setting the control template and assigning the command implementation -->
<Style TargetType="{x:Type local:MyTabItem}">
<Setter Property="Template" Value="{DynamicResource MyTabItemControlTemplate}"/>
<Setter Property="DeleteCommand" Value="{Binding MyDeleteCommand}"/>
<Setter Property="Header" Value="Default Header Text"/>
</Style>
</Window.Resources>
<Grid>
<local:MyTabControl ItemsSource="{Binding MyTabItemList}"/>
</Grid>
</Window>
MainWindow.xaml.cs
/// <summary>
/// TabControl withCustom TabItem
/// </summary>
public class MyTabControl : TabControl
{
/// <summary>
/// TabItem override
/// </summary>
protected override DependencyObject GetContainerForItemOverride()
{
return new MyTabItem();
}
}
public class MyTabItem : TabItem
{
/// <summary>
/// Delete Command
/// </summary>
public static DependencyProperty DeleteCommandProperty = DependencyProperty.Register(
"DeleteCommand", typeof(ICommand), typeof(MyTabItem));
/// <summary>
/// Delete
/// </summary>
public ICommand DeleteCommand
{
get { return (ICommand)GetValue(DeleteCommandProperty); }
set { SetValue(DeleteCommandProperty, value); }
}
}
public class MyCommand : ICommand
{
public void Execute(object parameter)
{
MessageBox.Show("Hello WPF", "Message");
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged { add { } remove { } }
}
public class MyContext
{
public ICommand MyDeleteCommand { get; set; }
public List<object> MyTabItemList { get; set; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var list = new List<object>();
list.Add(new TextBlock() { Text = "Test 1" });
list.Add(new MyTabItem() { Content = "Test Content 2", Header = "Test Header 2" });
list.Add(new TabItem() { Content = "Test Content 3", Header = "Test Header 3" });
this.DataContext = new MyContext()
{
MyTabItemList = list,
MyDeleteCommand = new MyCommand()
};
}
}
示例总结: 您会看到三个不同的选项卡,每个选项卡都有其独特的构造和外观:
- 选项卡项是通过
GetContainerForItemOverride
方法创建的,Header
属性 的样式 setter 指定 header 中出现的文本.MyDeleteCommand
绑定无效! - 选项卡项以
MyTabItem
形式提供,具有 header 和内容值。Header
属性 的样式 setter 不适用,因为 属性 是明确设置的。DeleteCommand
属性 的样式 setter 绑定到MyContext
. 的 - 选项卡项作为
TabItem
提供,并且由于没有覆盖MyTabControl.IsItemItsOwnContainerOverride
,这被接受为MyTabControl
的控制项。整个MyTabItem
模板不适用。
MyDeleteCommand
属性
visual studio 中的调试输出给出了有关第一个选项卡项的潜在问题的提示:
System.Windows.Data Error: 40 : BindingExpression path error: 'MyDeleteCommand' property not found on 'object' ''TextBlock' (Name='')'. BindingExpression:Path=MyDeleteCommand; DataItem='TextBlock' (Name=''); target element is 'MyTabItem' (Name=''); target property is 'DeleteCommand' (type 'ICommand')
原因是,在这种情况下,当前选项卡项内容成为新的本地 DataContext
,这与第二个选项卡项不同,在第二个选项卡项中,项目本身被接受为容器。
一个解决方案可能是确保在命令绑定上使用正确的上下文:
假设你给一些合适的parent元素起一个名字x:Name="_this"
,那么你就可以访问parentDataContext
.
<Setter Property="DeleteCommand" Value="{Binding DataContext.MyDeleteCommand,ElementName=_this}"/>