Search code examples
c#wpfmvvmlistboxtabitem

Add Tab Dynamically Into A tabControl based on Selected item from ListBox


I am working on MVVM sample app. I have 2 projects inside my solution in VSC# 2010. In one project (main project) I have a mainwindow.xaml which has two grids, one located on right side and other on the left. Left side grid has a listBox which has items with the help of observablecollection<>. E.g. Voltage, I2C etc are the items. I have also set the selecteditem property for the listbox which gives me the selecteditem.

My main query is the grid towards the right side which has a TABCONTROL with one item("CONNECT") added by default. What I need is to add the tabitem (e.g.Voltage tab) inside the tabcontrol when I select the "Voltage" item from listBox.

TabControl and ListBox inside my grid:

<Grid Grid.Column="0" Name="BoardTabSelect" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
            <ListBox Name="ButtonPanel" Style="{DynamicResource styleBanner}" ItemsSource="{Binding BoardTabs, Mode=TwoWay}" SelectedItem="{Binding SelectedTab, Mode=TwoWay}" >
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Name="BoardtabChanger" Margin="53,27,0,0" Text="{Binding TabOperation}" />
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>         

My tabcontrol:

                <Grid Grid.Row="0" >
                    <TabControl Name="ConnectTab" Style="{DynamicResource styleBackground}">
                        <tablocal:CloseableTabItem Header="Connect" x:Name="ConnectMain" MouseDoubleClick="TabItem_MouseDoubleClick"> 
                            <DockPanel>
                                <ListView  Name="listView" Height="460" Margin="0,-77,0,0" ItemsSource="{Binding Products}" SelectedItem="{Binding SelectedProduct, Mode=TwoWay}">
                                    <ListView.View>
                                        <GridView>
                                            <GridViewColumn Width="300" Header="Name" DisplayMemberBinding="{Binding Name}" />
                                            <GridViewColumn Width="283" Header="Connection Status" DisplayMemberBinding="{Binding Connection_Status}" />
                                        </GridView>
                                    </ListView.View>
                                </ListView>

                                <Button Content="Connect" Height="23" HorizontalAlignment="Stretch" Margin="-920,500,0,0" Name="ConnectBtnGrid" VerticalAlignment="Stretch" Width="100" Command="{Binding Path=ConnectCommand}" />
                                <Button Content="Update MSP430" Height="23" HorizontalAlignment="Stretch" Margin="-560,500,0,0" Name="UpdateMSPBtn" VerticalAlignment="Stretch" Width="100" />
                                <Button Content="Disconnect" Height="23" HorizontalAlignment="Stretch" Margin="-220,500,0,0" Name="DisconnectBtn" VerticalAlignment="Stretch" Width="100" Command="{Binding Path=DisconnectCommand}" />
                            </DockPanel>
                       </tablocal:CloseableTabItem>
                    </TabControl>
                </Grid>                 

My ViewModel class is here:

public List<Product> m_Products;
    public ObservableCollection<Product> m_BoardTabs;        

    public ProductViewModel()
    {           
        m_Products = new List<Product>()
        {                
            new Product() {Name = "Bavaria", Connection_Status = "Disconnected"},
            new Product() {Name = "Redhook", Connection_Status = "Disconnected"},                
        };

        m_BoardTabs = new ObservableCollection<Product>()
        {
            new Product() {TabOperation = "Connect"}              
        };
    }                      

    public List<Product> Products
    {
        get
        {
            return m_Products;
        }

        set
        {
            m_Products = value;
        }
    }

    public ObservableCollection<Product> BoardTabs
    {
        get
        {
            return m_BoardTabs;
        }

        set
        {
            m_BoardTabs = value;
        }
    }

    /// <summary>
    /// get:
    /// set:   
    /// </summary>
    private Product m_SelectedItem;
    public Product SelectedProduct
    {
        get
        {
            return m_SelectedItem;
        }

        set
        {
            m_SelectedItem = value;                
            NotifyPropertyChanged("SelectedProduct");
        }
    }

    private Product m_SelectedTab;
    public Product SelectedTab
    {
        get
        {
            return m_SelectedTab;
        }

        set
        {
            m_SelectedTab = value;
            NotifyPropertyChanged("SelectedTab");
            onTabChanged();
        }
    }        

    void onTabChanged()
    {
        if (SelectedTab.TabOperation == "Voltage")
        {
            //Add tab here
        }
    }

    public Product value { get; set; }

    /// <summary>
    /// get:
    /// set:   
    /// </summary>
    private ICommand mUpdater;
    public ICommand ConnectCommand
    {
        get
        {
            if (mUpdater == null)
                mUpdater = new DelegateCommand(new Action(SaveExecuted), new Func<bool>(SaveCanExecute));

            return mUpdater;
        }
        set
        {
            mUpdater = value;                
        }
    }

    public bool SaveCanExecute()
    {
        return true;
    }     

    public void SaveExecuted()
    {
        if (SelectedProduct.Connection_Status == "Disconnected" && SelectedProduct.Name == "Bavaria")
        {
            SelectedProduct.Connection_Status = "Connected";
            m_BoardTabs.Add(new Product() { TabOperation = "I2C" });
            m_BoardTabs.Add(new Product() { TabOperation = "Voltage" });
            m_BoardTabs.Add(new Product() { TabOperation = "Clock" });
            m_BoardTabs.Add(new Product() { TabOperation = "Codec" });
            m_BoardTabs.Add(new Product() { TabOperation = "EEPROM" });                             
        }

        else if (SelectedProduct.Connection_Status == "Disconnected" && SelectedProduct.Name == "Redhook")
        {
            SelectedProduct.Connection_Status = "Connected";

            m_BoardTabs.Add(new Product() { TabOperation = "I2C" });
            m_BoardTabs.Add(new Product() { TabOperation = "Voltage" });
            m_BoardTabs.Add(new Product() { TabOperation = "Clock" });
            m_BoardTabs.Add(new Product() { TabOperation = "Codec" });
            m_BoardTabs.Add(new Product() { TabOperation = "EEPROM" });
            m_BoardTabs.Add(new Product() { TabOperation = "PCM Route" });
            m_BoardTabs.Add(new Product() { TabOperation = "PCM Route #" });
            m_BoardTabs.Add(new Product() { TabOperation = "PCM Gen" });
            m_BoardTabs.Add(new Product() { TabOperation = "SD Card" });
            m_BoardTabs.Add(new Product() { TabOperation = "FPGA" });
            m_BoardTabs.Add(new Product() { TabOperation = "PCMPDM" });
            m_BoardTabs.Add(new Product() { TabOperation = "Data Gen" });           
        }
    }        

Mind you the tabitem "VOLTAGE" which I want to add in my control is part of another project which I mentioned in the beginning. It has its own view, viewmodel and model class. The following is the view in.xaml file which I want to add:

<Grid Name="VoltageTab" Height="572" Width="590" DataContext="{StaticResource VoltageViewModel}" >
    // Some UI componments which shud be placed inside VOLTAGE Tab on selecting Voltage from ListBox.
</Grid>

As you can see above, I want the above view to be placed inside the VoltageTab which should get created once user clicks Voltage item from ListBox.


Solution

  • The data template for the tabcontrol should not contain a tabitem. The tabcontrol is generating a tabitem for each element in the bound Collection. To enable databinding to the tabitem header set the itemcontainerstyle property of the tabcontrol. Here is a small sample:

    The View:

    <Window x:Class="WpfLab.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.5*"/>
            <ColumnDefinition Width="0.5*"/>
        </Grid.ColumnDefinitions>
        <TabControl ItemsSource="{Binding ProductTabs}">
            <TabControl.ItemContainerStyle>
                <Style TargetType="TabItem">
                    <Setter Property="Header" Value="{Binding Header}"/>
                </Style>
            </TabControl.ItemContainerStyle>
        </TabControl>
        <ListBox Grid.Column="1" ItemsSource="{Binding Products}" SelectedItem="{Binding SelectedProduct}"/>
    </Grid>
    

    The ViewModel

    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Windows;
    
    namespace WpfLab
    {
        public partial class MainWindow : Window
        {
            Product selectProduct;
    
            public MainWindow()
            {
                InitializeComponent();
    
                Products = new ObservableCollection<Product>();
                ProductTabs = new ObservableCollection<Product>();
    
                var products = Enumerable.Range(0, 10).Select(i => new Product { Header = "Product " + i });
                foreach (var p in products)
                    Products.Add(p);
    
                DataContext = this;
            }
    
            public Product SelectedProduct 
            {
                get { return this.selectProduct; }
    
                set
                {
                    UpdateTabs(this.selectProduct, value);
                    this.selectProduct = value;
                }
            }
    
            public ObservableCollection<Product> Products { get; private set; }
    
            public ObservableCollection<Product> ProductTabs { get; private set; }
    
            void UpdateTabs(Product old, Product @new)
            {
                if (ProductTabs.Any(p => p == old))
                    ProductTabs.Remove(old);
    
                ProductTabs.Add(@new);
            }
        }
    
        public class Product
        {
            public string Header { get; set; }
    
            public override string ToString()
            {
                return Header;
            }
        }
    }