【问题标题】:WPF-Custom TabItem with Delete Button Binding Issue带有删除按钮绑定问题的 WPF 自定义 TabItem
【发布时间】:2016-08-21 14:22:42
【问题描述】:

您好,我正在尝试使用删除按钮创建自定义 TabItem,我想将我的 viewmodel 命令绑定到我的自定义依赖属性“DeleteCommandProperty”。谁能告诉我我做错了什么?

我的自定义 TabControl:

    /// <summary>
    /// TabControl withCustom TabItem
    /// </summary>
    public class MyTabControl:TabControl
    {
        /// <summary>
        /// TabItem override
        /// </summary>
        /// <returns></returns>
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new MyTabItem();
        }
    }

我的自定义 TabItem 类:

    /// <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>

当尝试通过这样的样式绑定 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>

【问题讨论】:

  • 考虑将其减少为MVCE。例如,不要尝试以你的风格绑定DeleteCommand,除非它对于显示你的问题是必不可少的......相反,在你的构造函数中分配一个静态命令
  • 我看到你以某种方式编辑了你的问题,但它远非最小(你使用了许多与你的问题无关的模板),远非 verifiable(我用你的基于样式的命令设置器构建了一个小例子,它工作得很好),远非完整customControls:MyTabControl 的代码在哪里?另外,请不要强迫我使用 Blend)并且您并没有真正解释与预期行为相比观察到的行为是什么,因此也没有给出 示例
  • 我添加了我的 TabControl 类并尝试解释什么不起作用希望有人能提供帮助

标签: c# wpf mvvm dependency-properties tabitem


【解决方案1】:

TL;DR

我怀疑您的 DataContext 包含来自 MyList 的当前项目(无论它是什么),因此样式设置器无法按您的计划访问 MyDeleteCommand 属性。查看 Visual Studio 调试器日志,那里是否记录了绑定异常。


从工作代码演变而来的示例,直到它碰巧揭示了可能的问题解释。

您似乎在减少示例方面遇到了一些困难,因此根据您提供的信息,我唯一可以为您提供的是一个使用 StyleTemplateBinding 方法的小型工作示例,您可以将其用作基础找出你真正的问题。 编辑:最后的解释可能实际上是您问题的答案。

请注意,您可能必须更改命名空间以匹配您的项目设置。

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()
        };
    }
}

示例总结: 您会看到三个不同的选项卡,每个选项卡都有其独特的构建和外观:

  1. 标签项是从GetContainerForItemOverride 方法创建的,Header 属性的样式设置器指定出现在标题中的文本。 MyDeleteCommand 绑定不起作用!
  2. 标签项以MyTabItem 的形式提供,带有标题和内容值。 Header 属性的样式设置器不适用,因为该属性是明确设置的。 DeleteCommand 属性的样式设置器从 MyContext 绑定到 MyDeleteCommand 属性。
  3. 选项卡项作为TabItem 提供,并且因为没有覆盖MyTabControl.IsItemItsOwnContainerOverride,所以将其作为MyTabControl 的控制项接受。整个MyTabItem 模板不适用。

Visual Studio 中的调试输出提示了第一个选项卡项的潜在问题:

System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”“TextBlock”(名称=“”)上找不到“MyDeleteCommand”属性。绑定表达式:路径=我的删除命令;数据项='文本块'(名称='');目标元素是'MyTabItem'(名称='');目标属性是“DeleteCommand”(输入“ICommand”)

原因是,在这种情况下,当前选项卡项内容变成了新的本地DataContext,这与第二个选项卡项不同,其中项本身被接受为容器。

一种解决方案可能是确保在命令绑定上使用propper上下文:

假设你给一些合适的父元素命名x:Name="_this",那么你可以访问父元素DataContext

<Setter Property="DeleteCommand" Value="{Binding DataContext.MyDeleteCommand,ElementName=_this}"/>

【讨论】:

  • 感谢您指定 DataContext,就像您说的那样解决了问题。
猜你喜欢
  • 2013-04-06
  • 1970-01-01
  • 1970-01-01
  • 2013-07-01
  • 2017-02-07
  • 1970-01-01
  • 2018-04-21
  • 2013-08-04
  • 2014-06-19
相关资源
最近更新 更多