jwillmer
jwillmer

Reputation: 3790

How to close a TabItem in MVVM Light

In my view i'm adding dynamically custom TabItems (TextseiteTabItem). With the property DataContext i gave each TabItem a Model to work with (fill in values). Now i added a close-command to the custom TabItems but it wont work. I cant manage to send the close-command to the viewmodel. Above is my attempt..

My custom TabItem:

<sdk:TabItem x:Class="PortfolioCreator.TextseiteTabItem" 
           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"
           mc:Ignorable="d"
           xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
           xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
           xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">

    <sdk:TabItem.Header>
        <StackPanel Orientation="Horizontal">
            <sdk:Label Content="{Binding Seitennummer, StringFormat='Seite {0}', Mode=TwoWay}"/>
            <Button Content="X"
                    Command="{Binding CloseTabCommand, Mode=TwoWay}"
                    DataContext="{Binding ElementName=TemplateTabControl}"
                    CommandParameter="{Binding SelectedItem, ElementName=TemplateTabControl}" />   
        </StackPanel>
    </sdk:TabItem.Header>

    <sdk:TabItem.Content>
        <Grid x:Name="LayoutRoot">
            ...
        </Grid>
    </sdk:TabItem.Content>
</sdk:TabItem>

In my View:

...
<sdk:TabControl toolkit:DockPanel.Dock="Bottom" ItemsSource="{Binding Tabs}" x:Name="TemplateTabControl"/>
...

In my ViewModel:

public class PortfolioViewModel : ViewModelBase
{
    public ObservableCollection<TabItem> Tabs { get; set; }

    public RelayCommand<TabItem> CloseTabCommand
    {
        get;
        private set;
    }

    public PortfolioViewModel()
    {
        CloseTabCommand = new RelayCommand<TabItem>(tab =>
        {
            //never reached
        },
        tab =>
        {
            //never reached
        });

        Tabs = new ObservableCollection<TabItem>();

        AddTextseite();
        AddTextseite();          
    }

    void AddTextseite()
    {
        TabItem item = new TextseiteTabItem();
        item.DataContext = new TextSeiteModel();

        Tabs.Add(item);
    }

}

Upvotes: 1

Views: 955

Answers (4)

jwillmer
jwillmer

Reputation: 3790

This is my workaround for this problem. I admit it is not a good solution and breaks the mvvm pattern but as @herzmeister says other approaches are too elaborate for my project right now. (But it won't be the final solution ;-) )

TabItemViewModel:

public delegate void CloseTabItemHandler();

public class TextseiteTabItemViewModel : ViewModelBase
{
    public event CloseTabItemHandler CloseTabItem;
    public RelayCommand CloseTabCommand {get; set;}

    public TextseiteTabItemViewModel()
    {
        CloseTabCommand = new RelayCommand(() =>
        {
            if (CloseTabItem == null) return;
            CloseTabItem();
        });
    }
}

TabItemView:

<sdk:TabItem x:Class="PortfolioCreator.TextseiteTabItemView" 
        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"
        mc:Ignorable="d"
        xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
        xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
        xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">

    <sdk:TabItem.Header>
        <StackPanel Orientation="Horizontal">
            <Button Content="X" Command="{Binding CloseTabCommand, Mode=TwoWay}" />  
        </StackPanel>
    </sdk:TabItem.Header>

    <sdk:TabItem.Content>
        <Grid x:Name="LayoutRoot">
            ...
        </Grid>
    </sdk:TabItem.Content>
</sdk:TabItem>

Parent ViewModel:

public class PortfolioViewModel : ViewModelBase
{
    public ObservableCollection<TabItem> Tabs { get; set; }

    public PortfolioViewModel()
    {
        Tabs = new ObservableCollection<TabItem>();

        AddTextseite();
        AddTextseite();
    }

    void AddTextseite()
    {
        var viewmodel = new TextseiteTabItemViewModel();

        TabItem item = new TextseiteTabItemView();
        item.DataContext = viewmodel;

        viewmodel.CloseTabItem += new CloseTabItemHandler(() => 
        { 
            Tabs.Remove(item);
        });

        Tabs.Add(item);
    }
}

Upvotes: 0

Rafal
Rafal

Reputation: 12619

You are attempting to use MVVM but the strange thing I see is collection of ui elements (Tabs) in your view model. The correct way would be to create ViewModel that describes Tab item and move the command there. Then it will bind. To remove tab from Tabs you should expose event in your Tab view model and attach to it form PortfolioViewModel.

Of course my change will cause that your TextseiteTabItem will not show in TablControl. But it can be easily fixed with TabControl.ItemTemplate and TabControl.ContentTemplate.

Upvotes: 1

blindmeis
blindmeis

Reputation: 22445

here you find a demo application with closeable tabs for wpf, maybe it works for your silverlight version also.

Upvotes: 0

herzmeister
herzmeister

Reputation: 11287

First, your CloseTabCommand does nothing in your current code snippet: //never reached. The execute handler should read something like tab.Visibility = Visibility.Collapsed or myTabControl.Items.Remove(myTabItem).

Second, as @Rafal pointed out, using UI elements in the ViewModel is not the correct way to implement MVVM. If you want closable tab items, the correct way would be to derive a generic CloseableTabItem control or write a ClosableTabItemBehavior on the UI layer with a settable ICommand CloseCommand that can be bound to the corresponding ICommand instance on the ViewModel. Admittedly this approach might be too elaborate for your project though.

Upvotes: 1

Related Questions