Representation of tab(tree)item
Let us define some class that is used as model for our nested controls. It is basically a tree, where children propagate change of their IsSelected
property to their parents:
public class TabItemModel : INotifyPropertyChanged
{
private TabItemModel m_parent;
private Boolean m_IsSelected;
public TabItemModel(String name) : this(name, null)
{
}
public TabItemModel(String name, IEnumerable<TabItemModel> children)
{
this.Name = name;
this.Children = new ObservableCollection<TabItemModel>(children ?? Enumerable.Empty<TabItemModel>());
foreach (var child in this.Children)
{
child.m_parent = this;
}
}
public String Name
{
get;
set;
}
public ObservableCollection<TabItemModel> Children
{
get;
private set;
}
public Boolean IsSelected
{
get
{
return this.m_IsSelected;
}
set
{
if (value == this.m_IsSelected)
return;
if (this.m_parent != null)
this.m_parent.IsSelected = value;
this.m_IsSelected = value;
this.OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName]String propertyName = null)
{
var propChangedDelegate = this.PropertyChanged;
if (propChangedDelegate == null)
return;
propChangedDelegate(this,
new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Main ViewModel
We will need some main viewmodel that will:
- Contain such TabItemModels
- Provide the selected tabitemmodel property to interoperate with selectedItem in treeview
Code:
public class TabsViewModel
{
public TabsViewModel()
{
this.Items = GetItems();
}
private TabItemModel _SelectedItem;
public TabItemModel SelectedItem
{
get
{
return this._SelectedItem;
}
set
{
if (value == this._SelectedItem)
return;
if (value != null)
{
if (this._SelectedItem != null)
this._SelectedItem.IsSelected = false;
value.IsSelected = true;
}
this._SelectedItem = value;
}
}
public ObservableCollection<TabItemModel> Items
{
get;
private set;
}
private ObservableCollection<TabItemModel> GetItems()
{
return new ObservableCollection<TabItemModel>()
{
new TabItemModel("Tab 1",
new TabItemModel[]
{
new TabItemModel("Tab 1 - SubTab 1"),
new TabItemModel("Tab 1 - SubTab 2")
}),
new TabItemModel("Tab 2",
new TabItemModel[]
{
new TabItemModel("Tab 2 - SubTab 1"),
new TabItemModel("Tab 2 - SubTab 2")
}),
new TabItemModel("Tab 3",
new TabItemModel[]
{
new TabItemModel("Tab 3 - SubTab 1"),
new TabItemModel("Tab 3 - SubTab 2")
})
};
}
}
Code-behind
public MainWindow()
{
InitializeComponent();
this.DataContext = new TabsViewModel();
}
XAML
It is the least pretty thing. I was unable to both:
- Create normal hierarchical nested TabControls.
- Connect their IsSelected with bound items IsSelected in datatemplates.
So, for now I can only propose the following XAML with hard-coded tabItems:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TreeView Grid.Row="0" Grid.Column="0"
ItemsSource="{Binding Items}">
<blend:Interaction.Behaviors>
<view:BindableSelectedItemBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
</blend:Interaction.Behaviors>
<TreeView.Resources>
<DataTemplate x:Key="tabItemTemplateLeaf">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="tabItemTemplate"
ItemTemplate="{StaticResource tabItemTemplateLeaf}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ItemTemplate>
<StaticResource ResourceKey="tabItemTemplate"/>
</TreeView.ItemTemplate>
</TreeView>
<TabControl Grid.Row="0" Grid.Column="1">
<TabItem DataContext="{Binding Items[0]}" Header="{Binding Name}" IsSelected="{Binding IsSelected}">
<TabControl>
<TabItem DataContext="{Binding Children[0]}" Header="{Binding Name}" IsSelected="{Binding IsSelected}"/>
<TabItem DataContext="{Binding Children[1]}" Header="{Binding Name}" IsSelected="{Binding IsSelected}"/>
</TabControl>
</TabItem>
<TabItem DataContext="{Binding Items[1]}" Header="{Binding Name}" IsSelected="{Binding IsSelected}">
<TabControl>
<TabItem DataContext="{Binding Children[0]}" Header="{Binding Name}" IsSelected="{Binding IsSelected}"/>
<TabItem DataContext="{Binding Children[1]}" Header="{Binding Name}" IsSelected="{Binding IsSelected}"/>
</TabControl>
</TabItem>
<TabItem DataContext="{Binding Items[2]}" Header="{Binding Name}" IsSelected="{Binding IsSelected}">
<TabControl>
<TabItem DataContext="{Binding Children[0]}" Header="{Binding Name}" IsSelected="{Binding IsSelected}"/>
<TabItem DataContext="{Binding Children[1]}" Header="{Binding Name}" IsSelected="{Binding IsSelected}"/>
</TabControl>
</TabItem>
</TabControl>
</Grid>
It uses blend Behaviour class from this SO answer to allow binding to TreeView's SelectedItem property (you will need to add System.Windows.Interactions.dll
to your project and xmlns:blend="http://schemas.microsoft.com/expression/2010/interactivity"
to your XAML)
P.S: I will try to solve problems described in XAML section, but for now it is the best I can propose.