Everything's easier with data binding and MVVM. Harder at first, but ultimately lots easier.
Make two classes, Item
and ItemCollection
, that both implement INotifyPropertyChanged
. Item
should expose a string Text
property, while ItemCollection
should expose an ObservableCollection<Item>
Items
property and an Item
SelectedItem
property.
Make the ItemCollection
class's constructor populate Items
with test data and set SelectedItem
.
This seems like a lot to do before you actually get around to implementing your tab control, but trust me, you'll like the result. Your XAML for the TabControl will look something like this:
<TabControl
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<TabControl.DataContext>
<local:ItemsCollection/>
</TabControl.DataContext>
<TabControl.Resources>
<DataTemplate DataType="{x:Type local:Item}">
<TextBlock Background="AliceBlue" Text="{Binding Text}"/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Style.Setters>
<Setter Property="Header" Value="{Binding Text}"/>
</Style.Setters>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
Let's understand what this does. It creates a TabControl
. It creates an ItemsCollection
object and sets it as the TabControl
's DataContext
. You've bound ItemSource
to Items
, so the TabControl
will create a TabItem
for each item. It will apply the ItemContainerStyle
to each TabItem
, which sets its Header
property to the Item
's Text
property.
When the control renders the content of a tab, it finds the item it's rendering, searches through the resources to find a DataTemplate
whose DataType
matches the item, and uses that template. Since we've defined one in TabControl.Resources
, you get a nice blue background and the Text
property again.
This seems like a lot to go through. But now you don't have to write any code that manipulates the UI; you just write code that manipulates your ItemsCollection
, and the UI pretty much takes care of itself.
Now let's take care of adding new tabs. What we're going to do is add a new item to the control that, when it becomes selected, adds a new item to the Items
collection.
Create a new class, called, oh, ControlItem
. Have it derive from Item
. Modify your ItemsCollection
constructor so that the last item it adds is a ControlItem
, not an Item
. And have it set the Text
property on that item to "+".
Add this method to ItemsCollection
:
public Item AddItem()
{
Item newItem = new Item {Text = "New item"};
Items.Insert(Items.Count-1, newItem);
return newItem;
}
Now add to your window's code-behind, and as the SelectionChanged
event handler for your TabControl
:
void TabControl_SelectionChanged(object sender, RoutedEventArgs e)
{
TabControl tc = (TabControl) sender;
if (tc.SelectedItem is ControlItem)
{
ItemsCollection ic = (ItemsCollection) tc.DataContext;
tc.SelectedItem = ic.AddItem();
}
}
You can implement similar logic to remove an item from the list, but you'll need to introduce another variable to ItemsCollection
to track the previously selected item, so that you can know which item to delete.
Another thing you can do: implement a Background
property in Item
and add a setter to the ItemContainerStyle
that binds the TabItem
's Background
property to it. Then you can overload that property in ControlItem
, so that your add and delete tabs look different.
You can also implement different subclasses for your control items, and have them expose a method that you call in the SelectionChanged
event handler. That way, the event handler doesn't have to know anything about what the control item being clicked is doing. In fact, if you make the method part of Item
, and have it do nothing unless it's overriden, the window doesn't even need to know that Item
has subclasses.
That's the philosophy behind MVVM in a nutshell: Bind the view to objects about which it knows almost nothing. Let the view-model objects control what happens, so that the view doesn't have to.