简体   繁体   中英

WPF-Custom TabItem with Delete Button Binding Issue

Hi i' m trying to create a custom TabItem with delete Button,i want to bind my viewmodel command to my custom Dependency Property 'DeleteCommandProperty' . Can someone tell me what i'm doing wrong?

my Custom TabControl :

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

my Custom 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); }
        }
    }

when i bind the DeleteCommand directly like this my Command in my ViewModel is executed

 <customControls:MyTabControl>
            <customControls:MyTabItem Header="Test" DeleteCommand="{Binding DeleteStudiengangCommand}" Template="{DynamicResource MyTabItemControlTemplate}"/>
        </customControls:MyTabControl>

bu when try to bind the deleteCommand via style like this but it doesn't work :

 <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

I suspect your DataContext contains your current item from MyList (whatever it is), so the style setter cannot access the MyDeleteCommand property as you planned. Look at the visual studio debugger log, whether a binding exception is logged there.


Example that evolved from a working code until it happened to reveal a possible problem explanation.

It seems you have some difficulties in reducing your example, so the only thing I can offer you based on the information you provide is a small working example with the Style and TemplateBinding approach which you can use as a base to identify your real problem. Edit: The explanation at the end might actually be the answer to your question.

Note you may have to change the namespaces to match with your project setup.

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

Summary of the example: You see three different tabs, each with its unique build and appearance:

  1. The tab item is created from the GetContainerForItemOverride method, the style setter for the Header property specifies the text that appears within the header. The MyDeleteCommand binding does not work!
  2. The tab item is provided as MyTabItem with header and content value. The style setter for the Header property does not apply, because the property is explicitely set. The style setter for the DeleteCommand property binds to the MyDeleteCommand property from MyContext .
  3. The tab item is provided as TabItem and because there is no override of MyTabControl.IsItemItsOwnContainerOverride , this is accepted as a control item of MyTabControl . The whole MyTabItem template does not apply.

The debugging output inside visual studio gives a hint about the underlying problem for the first tab item:

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')

The reason is, that the current tab item content becomes the new local DataContext in this scenario unlike the second tab item, where the item itself is accepted as the container.

A solution could be to ensure usage of the propper context on the command binding:

Supposed you give some suitable parent element a name x:Name="_this" , then you can access the parent DataContext .

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM