3

I have the a tabcontrol in my MainWindow. The default/first tab of the tabcontrol is the home user control. In the home page I have a button that can add further tabs.

MainWindow.xaml:

<Window.Resources>
    <DataTemplate x:Key="ClosableTabItemTemplate"> 
        <Button Content="X" Cursor="Hand" DockPanel.Dock="Right" Focusable="False"
                FontFamily="Courier" FontSize="9" FontWeight="Bold"  Margin="0,1,0,0" Padding="0" 
                VerticalContentAlignment="Bottom" Width="16" Height="16"/>            
    </DataTemplate>
</Window.Resources>

<Grid>
    <TabControl Name="tabMain" ItemsSource="{Binding TabItems,UpdateSourceTrigger=PropertyChanged}" />   
</Grid>

In my view model I have the add functionality where a new tab has been added. I need the close button for all these newly added tabs.

public MainViewModel()
    {
        try
        {
            Home Item2 = new Home();                           
            TabItems.Add(new TabItem() { Header = "Home", Content = Item2 });                             
        }
        catch(Exception ex)
        {
            MessageBox.Show("Exception "+ex);
        }

//Function to add new tabs.
public void AddNewTabs()
{
            ChildWindow childContent = new ChildWindow();
            TabItem item = new TabItem() { Header = "New Tab", Content = childContent};                
            item.MouseDoubleClick += new MouseButtonEventHandler(tab_MouseDoubleClick);
            TabItems.Add(item);
}

Right now new tabs are being added but without the close button. I have tried giving

item.HeaderTemplate = FindResource("ClosableTabItemTemplate") as DataTemplate; 

But it shows error.

Any help would be appreciated.

Thanks in advance.

Charan Ghate
  • 1,384
  • 15
  • 32
Ani_1317
  • 151
  • 1
  • 3
  • 12
  • I implemented similar functionality in an extended TabControl. It should fit your situation nicely. If you do not want the close button to appear in your "home" tab you would just have to rewrite the HeaderTemplateSelector. http://stackoverflow.com/questions/3468866/tabcontrol-with-add-new-tab-button/27805238#27805238 – Derrick Moeller Feb 03 '15 at 13:08

2 Answers2

10

You could have a look at Dragablz which does this and more.

enter image description here

Disclaimer: this is my library, but it's open source, so enjoy.

James Willock
  • 1,999
  • 14
  • 17
6

You view model must not interact directly with the view in order to respect the Mvvm pattern, meaning that you need to use commands instead of Events, don't use any view related control in your View model logic ..

here a cleaner way to achieve what you're looking for :

First In the view use the TabControl ContentTemplate and ItemTemplate Instead :

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
    <Button Content="Add new tab" Command="{Binding AddNewTabCommand}"></Button>
    <TabControl  Grid.Row="1" Name="TabMain" ItemsSource="{Binding TabItems,UpdateSourceTrigger=PropertyChanged}" >
        <TabControl.ItemTemplate>
            <DataTemplate >
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Header}"/>
                    <Button Content="X" Cursor="Hand" DockPanel.Dock="Right" Focusable="False"
            FontFamily="Courier" FontSize="9" FontWeight="Bold"  Margin="0,1,0,0" Padding="0" 
            VerticalContentAlignment="Bottom" Width="16" Height="16" Command="{Binding DataContext.CloseTabCommand,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" CommandParameter="{Binding ElementName=TabMain,Path=SelectedItem}"/>
                </StackPanel>
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Content}" VerticalAlignment="Center" HorizontalAlignment="Center"></TextBlock>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
</Grid>

Second, In the ViewModel Create a class TabItem that will hold a tab Content and header (customize it as needed), you may want to implement the INotifyPropertyChanged interface if the class reflect any changes to the view,

Third, define the commands to add and romove a TabItem from TabItems ObservableCollection,

here the viewModel Code :

  public class TabItem
{
    public String Header { get; set; }
    public String Content { get; set; }
}
public class MainViewModel : INotifyPropertyChanged
{
    private ObservableCollection<TabItem> _tabItems;
    public ObservableCollection<TabItem> TabItems
    {
        get
        {
            return _tabItems;
        }

        set
        {
            if (_tabItems == value)
            {
                return;
            }

            _tabItems = value;
            OnPropertyChanged();
        }
    }
    private RelayCommand _addNewTabCommand;
    public RelayCommand AddNewTabCommand
    {
        get
        {
            return _addNewTabCommand
                ?? (_addNewTabCommand = new RelayCommand(
                () =>
                {
                    TabItems.Add(new TabItem()
                    {
                        Header = "NewTab",
                        Content = "NewContent"
                    });
                }));
        }
    }
    private RelayCommand<TabItem> _closeTabCommand;
    public RelayCommand<TabItem> CloseTabCommand
    {
        get
        {
            return _closeTabCommand
                ?? (_closeTabCommand = new RelayCommand<TabItem>(
                (t) =>
                {
                    TabItems.Remove(t);
                }));
        }
    }

    public MainViewModel()
    {
        TabItems = new ObservableCollection<TabItem>()
       {
            new TabItem()
           {
               Header = "Home",
               Content = "Home Content"
           },
           new TabItem()
           {
               Header = "Header1",
               Content = "Content1"
           }
       };

    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

the output :

enter image description here

Ps: My MainWindow View DataContext is set to MainWindowViewModel, and tha's why i am using AncestorType to find the CloseTabCommand

SamTh3D3v
  • 9,854
  • 3
  • 31
  • 47
  • Thanks a lot. But my default tab is home . I have used Relay Commands for adding new tab though I have not mentioned it in my question(Sorry abt that.) Well another point is I do not want the home tab to have a Close(X) button. I want only the tabs that are added from the home page to have a close button. – Ani_1317 Feb 04 '15 at 06:26
  • Again my content is a user control page.. So for each tab each user control page appears according to the selection made. – Ani_1317 Feb 04 '15 at 06:50
  • Regarding the Close button on the Home Tab, Use a Converter to set the Close Button Visibility, ...and in the HomeButtonConverter return Visible or Collapsed based on whether Header=="Home" or Not – SamTh3D3v Feb 04 '15 at 08:27
  • And for the second part you could use a Trigger to select which content (DataTemplate) to display based on Header for example ..check this for example http://stackoverflow.com/questions/10190887/different-views-data-template-based-on-member-variable – SamTh3D3v Feb 04 '15 at 08:33