views:

239

answers:

3

What is the proper way of adding a '+' button tab at the end of all the tab items in the tab strip of a tab control.

  1. It should work correctly with multiple tab header rows.
  2. It should be at the end of all tab items
  3. Tab cycling should work correctly (Alt+Tab) ie the + tab should be skipped.
  4. I shouldn't have to modify the source collection I am binding to. ie the control should be reuseable.
  5. Solution should work with MVVM

alt text

alt text

EDIT: To be more precise, the button should appear exactly as an additional last tab and not as a separate button somewhere on the right of all tab strip rows.

I am just looking for the general approach to doing this.

Google throws many examples of this, but if you dig a little deep none of them satisfy all the above four points.

Any help greatly appreciated.

N

+1  A: 

OK try this

Define the ControlTemplate of the TabControl like this:

 <!-- Sets the look of the Tabcontrol. -->
<Style x:Key="TabControlStyle" TargetType="{x:Type TabControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabControl}">
                <Grid>
                    <!-- Upperrow holds the tabs themselves and lower the content of the tab -->
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>

The upper row in the grid would be the TabPanel but you would put that into a StackPanel with a button following the TabPanel, style the button to look like a tab

Now the button would create a new TabItem (your custom created one perhaps) and add it to the ObservableCollection of Tabs you have as the Itemssource for your TabControl.

1) I think I know what you mean but not sure though, update me on this one

2 & 3) It should always appear at the end, and it's not a tab so hopefully not part of tab cycling

4) Well your TabControl should use a ObservableCollection of TabItems as Itemssource to be notified when a new one is added/removed

Hope this helps.

EDIT: Adding some code

The NewTabButton usercontrol .cs file

public partial class NewTabButton : TabItem
{
    public NewTabButton()
    {
        InitializeComponent();

        Header = "+";
    }
}

and the main window:

public partial class Window1 : Window
{
    public ObservableCollection<TabItem> Tabs { get; set; }

    public Window1()
    {
        InitializeComponent();

        Tabs = new ObservableCollection<TabItem>();

        for (int i = 0; i < 20; i++)
        {
            TabItem tab = new TabItem();
            tab.Header = "TabNumber" + i.ToString();
            Tabs.Add(tab);
        }

        Tabs.Add(new NewTabButton());

        theTabs.ItemsSource = Tabs;
    }
}

Now we would need to find a way to let it always appear bottme right and also add the event and style for it ( the plus sing is there as a placeholder ).

I'll take a better look tomorrow.

Ingó Vals
Thanks Ingo, I just added an image to make the question more clear. What you suggest wont work when there are multiple rows of tab items I think.
NVM
You could try to create a UserControls that inherits TabItem and create a event on that one that when it is selected it creates a new TabItem and puts it into the collection infront itself. That should work perfectly. If I have time I might add code later on.
Ingó Vals
If you are binding through ItemsSource you cannot add an additional TabItem in XAML. If you are talking about using a CompositeCollection I think it will only work with static TabItems and you cannot bind the items
NVM
You can add to a ObservableCollection<TabItem> that is the itemssource, have done that and works fine. The problem I found with this solution is that the last Tab ends in the line above when there are multiple lines, I'll add code and we can work from there.
Ingó Vals
Aah, sorry I took MVVM as a given. With what you suggest I need to take UI (TabItem) into my VM which is not acceptable.
NVM
Well when you add the new tab you are going to need to fetch new Data from your VM right? How do control what data goes into tabs and what not?
Ingó Vals
Through a datatemplate.
NVM
More precisely, the + button should fire a "AddNew" event, which will be wired to the VM as a "AddNewCommand" which will add an item to the bound observablecollection of items and the datatemplate will take care of the rest. Thats pretty standard MVVM stuff I think.
NVM
Hmm then the only solution (short of waiting until WPF adds this feature ) I see is to somehow edit the TabPanel to feature a button among the tabs. Problem is TabPanel isn't a control I think.
Ingó Vals
Actually IEditableCollectionView looks like a good bet. It has a NewItemPlaceHolder whose purpose is exactly to do a Add Neww thing. eg DataGrid Add new row works using this. Unfortunately I am relatively new to WPF to judge whether this is the way to go or not and so posted the question hoping people more knowledgeable will point me in the right direction lest I dig myself into a hole.
NVM
And also I am happy to build a derived tabcontrol/panel etc if that is indeed the right approach.
NVM
IEditableCollectionView is just a wrapper you would wrap ObservableCollection in for example. It's nice as you can create a new item and cancel it without ever having to connect to the database. But I can't see it help you here. I guess a derived TabPanel would be the right approach I just don't know how hard it will be. I tried just creating a NewTabPanel.cs class that inherits TabPanel and used it in my TabControl ControlTemplate and that works fine. Now we just have to find out how to edit it correctly.
Ingó Vals
Ingo, I really appreciate your help with this.http://msdn.microsoft.com/en-us/library/system.windows.data.collectionview.newitemplaceholder.aspxIf the collectionview add a NewItemPlaceholder item automatically, I can simply add a datatemplate containing a + button for that item. Rest of the items will use the tabcontrol's default template. It sounds almost perfect!! Any thoughts?
NVM
BTW the NewItemPlaceHolder takes care of everything, its can not be the current item(so I am assuming it wont cause trouble with the tab cycle), it can be at the last item, it doesn't get involved in filtering/sorting etc....
NVM
Yeah sorry somehow missed your earlier mention of this NewItemPlaceholder, It sounds like the thing you need, good find. Let me know how it works out. As I already am implementing IEditable in my App I might add it as well :)
Ingó Vals
Thanks Ingo, will update the thread with how this goes...
NVM
NP I think I've learned more from you then you from me :)
Ingó Vals
A: 

Here's the same question from msdn wpf forums:

http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/da30ade6-a8ae-4f0b-823b-86c7b2306fd4

Mario C
Unfortunately examples like the one in the link are the exactly why I posted the question here, trying to be clear what are my requisites. I've added another image in my question that shows what the link sample does and is not what I am after.
NVM
+1  A: 

An almost complete solution using IEditableCollectionView:

enter code here
    ObservableCollection<ItemVM> _items;
    public ObservableCollection<ItemVM> Items
    {
        get
        {
            if (_items == null)
            {
                _items = new ObservableCollection<ItemVM>();
                var itemsView = (IEditableCollectionView)CollectionViewSource.GetDefaultView(_items);
                itemsView.NewItemPlaceholderPosition = NewItemPlaceholderPosition.AtEnd;
            }

            return _items;
        }
    }

<DataTemplate x:Key="newTabButtonContentTemplate">
    <Grid/>
</DataTemplate>

<DataTemplate x:Key="newTabButtonHeaderTemplate">
    <Button Content="+"
        Command="{Binding ElementName=parentUserControl, Path=DataContext.NewCommand}"/>
</DataTemplate>

<DataTemplate x:Key="itemContentTemplate">
    <Grid/>
</DataTemplate>

<DataTemplate x:Key="itemHeaderTemplate">
    <TextBlock Text="TabItem_test"/>
</DataTemplate>

<vw:ItemHeaderTemplateSelector x:Key="headerTemplateSelector"
                           NewButtonHeaderTemplate="{StaticResource newTabButtonHeaderTemplate}"
                           ItemHeaderTemplate="{StaticResource itemHeaderTemplate}"/>

<vw:ItemContentTemplateSelector x:Key="contentTemplateSelector"
                            NewButtonContentTemplate="{StaticResource     newTabButtonContentTemplate}"
                            ItemContentTemplate="{StaticResource itemContentTemplate}"/>

<TabControl ItemsSource="{Binding Items}"
        ItemTemplateSelector="{StaticResource headerTemplateSelector}"
        ContentTemplateSelector="{StaticResource contentTemplateSelector}"/>


public class ItemHeaderTemplateSelector : DataTemplateSelector
{
    public DataTemplate ItemHeaderTemplate { get; set; }
    public DataTemplate NewButtonHeaderTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item == CollectionView.NewItemPlaceholder)
        {
            return NewButtonHeaderTemplate;
        }
        else
        {
            return ItemHeaderTemplate;
        }
    }
}



public class ItemContentTemplateSelector : DataTemplateSelector
{
    public DataTemplate ItemContentTemplate { get; set; }
    public DataTemplate NewButtonContentTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item == CollectionView.NewItemPlaceholder)
        {
            return NewButtonContentTemplate;
        }
        else
        {
            return ItemContentTemplate;
        }
    }
}

enter code here

Its almost complete because the tab cycle doesnt skip the '+' tab, and will show empty content (which is not exactly great but I can live with it until a better solution come around... ).

NVM
You should accept this as your right answer so people will find it when looking for answer to the same question. Great Job btw.
Ingó Vals
I was hoping someone would suggest a complete solution. But I guess I've waited long enough....
NVM