0
votes

I have a tabbed section and I am trying to wire up the commands for closing and opening new tabs dynamically. The problem is I cant understand how to bind the command from within my tabItem template(which has a button). Here is the code:

(UserControl containing the tabbed section, simplified..):

<UserControl.DataContext>
    <vm:InicioViewModel />
</UserControl.DataContext>
<UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="../Visual Resources/TabResource.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</UserControl.Resources>        

    <ContentPresenter HorizontalAlignment="Stretch" Grid.Column="1">
        <ContentPresenter.Content>
            <TabControl Name="tc">
                <TabControl.DataContext>
                    <vm:WorkSpaceViewModel/>
                </TabControl.DataContext>  
                <TabControl ItemsSource="{Binding Items}"/>
            </TabControl>

        </ContentPresenter.Content>
    </ContentPresenter>
</Grid>

(Here is the resource Dictionary for the tabItem):

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabItem}">
                <Border x:Name="TaBorder" Width="auto" Height="auto"
                        BorderBrush="LightGray"
                        BorderThickness="0.5,0.5,0.5,0"
                        CornerRadius="3,3,0,0"
                        Background="WhiteSmoke">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="20" />
                        </Grid.ColumnDefinitions>
                        <TextBlock Margin="2" Grid.Column="0" Text="{TemplateBinding Header}" />
                        <Button x:Name="CloseButton" Grid.Column="1" Width="11" Height="11"
                                VerticalAlignment="Center"
                                HorizontalAlignment="Center"
                                Background="Transparent"
                                DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=DataContext}"
                                Command="{Binding CloseWorkSpaceCommand}"
                                (Donkt know what yo put in command to           reference my viewmodel Icommand)
                                BorderThickness="0"
                                >
                            <!-- ETC -->                

(Here my viewModel):

class InicioViewModel : ViewModelBase
{
    private WorkSpaceViewModel _workSpaceVm;

    public InicioViewModel()
    {

    }

    public WorkSpaceViewModel WorkSpaceVm
    {
        get { return _workSpaceVm; }
        set { _workSpaceVm = value; }
    }
}

(WorkSpaceViewModel..):

public class WorkSpaceViewModel
{
    private ObservableCollection<IWorkSpaceItemVm> _items;        

    private RelayCommand _closeWorkSpaceCommand;        

    public WorkSpaceViewModel()
    {
        _items = new ObservableCollection<IWorkSpaceItemVm>();
    }

    public ObservableCollection<IWorkSpaceItemVm> Items
    {
        get { return _items; }
        set { _items = value; }
    }      

    public ICommand CloseWorkSpaceCommand
    {
        get
        {
            return _closeWorkSpaceCommand ?? (_closeWorkSpaceCommand = new RelayCommand(
                param => CloseWorkSpace_Execute(param),
                param => CloseWorkSpace_CanExecute(param)
                ));
        }
    }       

    private void CloseWorkSpace_Execute(object parm)
    {
        MessageBox.Show("asdasdasd");
    }

    private bool CloseWorkSpace_CanExecute(object parm)
    {
        return true;
    }
}

As you can note I only have a MessageBox showing in CloseWorkSpace_Execute for test purposes.

1) How can I reference the Icommand in my viewmodel from within my tabItem style template, or, if is there a better way with same results will be welcome.

2) Why when I run the app one empty tab is created, I have my observable collection list empty

EDIT: The RelayCommand is working OK in another part of the program, thats not the problem, The tabItem gets rendered OK with triggers workin and all, I still cant get my head into how to bind the command from my viewmodel with the Templated tabItem I made.

EDIT 2: The command is working now, apparently the Command is not recognized in the resource dictionary and is marked as: "Cant resolve property 'CloseWorkSpaceCommand' in DataContext of type object", but setting the DataContext of the button to: DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=DataContext}" gets the work done when running the app(visual studio still apologizing about DataContext type, dunno what it means). There is still one tab created by default, why? And is there a way to correct the code smell with DataContext type?

1
At first glance, it seems your difficulty may be related to the placement of the template in a separate resources dictionary, making it less convenient to access whatever it is you want to bind to. One possible solution that comes to mind is also declare an appropriate Command in the same XAML file and bind to that, and then set up a binding (e.g. CommandBinding) elsewhere to respond to that command when executed. But there's too much code here to know for sure what part of the ambiguous question is giving you trouble, never mind spend time trying to fix.Peter Duniho
Please edit so that only a good, minimal, complete code example that reliably reproduces the problem is included.Peter Duniho
Hey Peter, I edited it, I dont get what you are saying, You say the problem is the reference with the resource dictionary declaration?, so , what is the relative resource for? is there another way I can achieve this? I want to bind a command to the button inside the style template, no matter if I have to declared inside template, inline in my user or my tabcontrol resources, but I cant understand how to reference the button I created within my tabItem templateLucho Mansilla
"You say the problem is the reference with the resource dictionary declaration" -- no, I say that without a good code example, I can't say for sure what the problem is. Please break the question down into the individual, specific pieces you can't solve yourself, and ask a single question for each piece, being sure to include a good, minimal, complete code example that clearly illustrates the question and reliably reproduces whatever problem is described.Peter Duniho
I am starting to think that I am in the wrong path with this, if I have to create another mock command and then bind again.. Any ideas?Lucho Mansilla

1 Answers

1
votes

It seems like you want to add a close button on every tab like the ones we see in browsers. It's seems pretty complicated to do that. But let me try to break it down for you.

First let's start by stating the roadblocks that prevents us from doing that:

  1. The TabItem does not have a command property where you can bind your CloseWorkSpaceCommand.
  2. The tab item does not have a close button. That's the reason you created a Template. But still you can't do a template binding to a command property since the TabItem does not have such command property.
  3. How will you be able wire up the button's command to the viewmodel's CloseWorkSpaceCommand property?

Now let's try to resolve each problem one by one.

  1. To resolve this, we need to create a custom control for the TabItem that has a command property.

    public class ClosableTabItem : TabItem
    {
        public static readonly DependencyProperty CloseCommandProperty = DependencyProperty.Register("CloseCommand", typeof(ICommand), typeof(ClosableTabItem), new PropertyMetadata(null));        
        public ICommand CloseCommand
        {
          get { return (ICommand)GetValue(CloseCommandProperty); }
          set { SetValue(CloseCommandProperty, value); }
        }
    }  
    

    Since we have a custom tab item, we also need a custom TabControl because we need to overide the GetContainerForItemOverride() method.

    public class ClosableTabControl : TabControl { protected override DependencyObject GetContainerForItemOverride() { return new ClosableTabItem(); } }

    This resolves problem #1.

  2. Like what you did we need to have a ControlTemplate so we could place a close button on each tabs.

    <ControlTemplate TargetType="{x:Type local:ClosableTabItem}"> <Border x:Name="TaBorder" Width="auto" Height="auto" Background="LightGray" CornerRadius="4,4,0,0" Margin="0,2,3,0"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="20" /> </Grid.ColumnDefinitions> <TextBlock Margin="2" Grid.Column="0" Text="{TemplateBinding Header}" /> <Button x:Name="CloseButton" Grid.Column="1" Width="11" Height="11" VerticalAlignment="Center" HorizontalAlignment="Center" Command="{TemplateBinding CloseCommand}" BorderThickness="0" Content="X" Background="Red" FontSize="8"> </Button> </Grid> </Border> </ControlTemplate>

  3. To bind the viewmodel.CloseWorkSpaceCommand to the tab item we do this in the ItemContainerStyle's setter.

    <local:ClosableTabControl.ItemContainerStyle> <Style TargetType="local:ClosableTabItem" BasedOn="{StaticResource {x:Type TabItem}}"> <Setter Property="CloseCommand" Value="{Binding DataContext.CloseWorkSpaceCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" /> ...

    You'll notice that I'm using relative source to the DataContext of the Window.

    Now to bind the CloseCommand Property of the ClosableTabItem to the Command property of the Button inside the template, we now do the template binding inside the control template's button.

    You can also see this in answer #2 code sample.

    Command="{TemplateBinding CloseCommand}"

Here's the complete xaml code:

<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApplication1" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <local:MainViewModel /> </Window.DataContext> <Grid> <local:ClosableTabControl ItemsSource="{Binding Items}"> <local:ClosableTabControl.ItemContainerStyle> <Style TargetType="local:ClosableTabItem" BasedOn="{StaticResource {x:Type TabItem}}"> <Setter Property="CloseCommand" Value="{Binding DataContext.CloseWorkSpaceCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:ClosableTabItem}"> <Border x:Name="TaBorder" Width="auto" Height="auto" Background="LightGray" CornerRadius="4,4,0,0" Margin="0,2,3,0"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="20" /> </Grid.ColumnDefinitions> <TextBlock Margin="2" Grid.Column="0" Text="{TemplateBinding Header}" /> <Button x:Name="CloseButton" Grid.Column="1" Width="11" Height="11" VerticalAlignment="Center" HorizontalAlignment="Center" Command="{TemplateBinding CloseCommand}" BorderThickness="0" Content="X" Background="Red" FontSize="8"> </Button> </Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </local:ClosableTabControl.ItemContainerStyle> <TabControl.Items> </TabControl.Items> </local:ClosableTabControl> </Grid> </Window>

enter image description here