1

I am building a settings window that has a TreeView on the left and a TabControl on the right - the layout is based off of the Microsoft Visual Studio Options dialog.

The TabControl has 3 TabItems, so 3 tabs. Each of the TabItems contains a TabControl with 2 of its own TabItems. I plan to hide the TabControl tabs later, but going back to the TreeView, it will look like this:

  • Option group 1
    • Sub option group 1
    • Sub option group 2
  • Option group 2
    • Sub option group 1
    • Sub option group 2
  • Option group 3
    • Sub option group 1
    • Sub option group 2

In my XAML, everything is visually OK. I'm trying to figure out the codebehind for this.

I would like to have it so clicking the item in the TreeView will cause the corresponding TabItem to become active. How can I acquire the Treeview's selected item/node and cause all of the TabControls to react?

I am questioning if this is best way of doing this. I would have to maintain the TreeView items and the TabControl TabItems...

ikathegreat
  • 2,311
  • 9
  • 49
  • 80

1 Answers1

0

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:

  1. Contain such TabItemModels
  2. 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:

  1. Create normal hierarchical nested TabControls.
  2. 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.

Community
  • 1
  • 1
Eugene Podskal
  • 10,270
  • 5
  • 31
  • 53