Search code examples
c#wpfxamltabcontrolcontenttemplate

Running the TabControl.ContentTemplate within the TabControl


I'm trying to find a way to add content to a tab page using the TabControl without creating new tabs. I have a ViewModel that holds the values for the tab header and tab content. Currently, when the 'Add Tab' button is clicked, it will add a new tab with the correct heading, however the tab content will have the data missing. I understand why my work doesn't work, which is why I would like to find out if it is possible to separate these two processes. I am new to WPF and would appreciate any help.

XAML:

<TabControl ItemsSource="{Binding}" Grid.Column="1" Grid.Row="1" Grid.RowSpan="5">
    <TabControl.ItemTemplate>
         <DataTemplate DataType="local:MyTab">
              <TextBlock Text="{Binding Header}"/>
         </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
         <DataTemplate DataType="local:MyTab">
               <StackPanel>
                   <TextBlock Text="First Name:" />
                   <TextBlock Binding="{Binding FirstName}" Margin="0,0,0,10"/>
                   <TextBlock Text="Second Name:" />
                   <TextBlock Binding="{Binding SecondName}" Margin="0,0,0,10"/>
                   <TextBlock Text="ID Number:" />
                   <TextBlock Binding="{Binding Id}" Margin="0,0,0,10"/>
                   <TextBlock Text="Age:" />
                   <TextBlock Binding="{Binding Age}" Margin="0,0,0,10"/>
                   <TextBlock Text="Gender:" />
                   <TextBlock Binding="{Binding Gender}" Margin="0,0,0,10"/>
                   <TextBlock Text="Address:" />
                   <TextBlock Binding="{Binding Address}" Margin="0,0,0,10"/>
              </StackPanel>
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

C#:

 public partial class MainWindow : Window
    {
        ObservableCollection<MyTab> tabs = new ObservableCollection<MyTab>();

        string firstName;
        string secondName;
        string id;
        int age;
        string gender;
        string address;

        public MainWindow()
        {
            InitializeComponent();

        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            firstName = firstNameTxtBox.Text;
            secondName = surnameTxtBox.Text;

            var tab = new MyTab() { Header = firstName + " " + secondName };
            tabs.Add(tab);

            DataContext = tabs;

            firstNameTxtBox.Clear();
            surnameTxtBox.Clear();
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            id = idTxtBox.Text;
            age = Convert.ToInt32(ageTxtBox.Text);
            gender = genderTxtBox.Text;
            address = addressTxtBox.Text;

            var tab = new MyTab();
            tab.Data.Add(new MyTabData() { FirstName = firstName, SecondName = secondName, Id = id, Age = age, Gender = gender, Address = address });
            tabs.Add(tab);

            DataContext = tabs;

            idTxtBox.Clear();
            ageTxtBox.Clear();
            genderTxtBox.Clear();
            addressTxtBox.Clear();
        }
    }

Solution

  • As I understood, MyTab is your class, that will look like that :

    public class MyTab : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        public void NotifyPropertyChanged(string propName)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        }
        private bool NotifyPropertyChanged<T>(ref T variable, T valeur, [CallerMemberName] string nomPropriete = null)
        {
            if (object.Equals(variable, valeur)) return false;
    
            variable = valeur;
            NotifyPropertyChanged(nomPropriete);
            return true;
        }
        private string name = "";
        public string Name
        {
            get { return this.name; }
            set
            {
                if (value != null && this.name != value)
                {
                    this.name = value;
                    this.NotifyPropertyChanged("Name");
                }
            }
        }
        private string surname = "";
        public string Surname
        {
            get { return this.surname; }
            set
            {
                if (value != null && this.surname != value)
                {
                    this.surname = value;
                    this.NotifyPropertyChanged("Surname");
                }
            }
        }
        // firsName, Id and so on...    
        public MyTab()
        {
        }
    }
    

    First of all, your class must be INotifyPropertyChanged so the binding of TextBoxes will work.

    Then your MainWindows : Window, INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;
    
    public void NotifyPropertyChanged(string propName)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    }
    private ObservableCollection<MyTab> listMyTab { get; set; } = new ObservableCollection<MyTab>();
    public ObservableCollection<MyTab> ListMyTab { get { return this.listMyTab; } set { this.listMyTab = value; this.NotifyPropertyChanged("ListMyTab"); } }
    public MainWindow()
        {
            InitializeComponent();
            this.DataContext=this;
        }
    

    You must set the dataContext (if you ant to do it properly (MVVM), you may set the context in a different file named ViewModelMainWindows.cs for example.

    The ViewModel part :

    private ObservableCollection<MyTab> listMyTab { get; set; } = new ObservableCollection<MyTab>();
    public ObservableCollection<MyTab> ListMyTab { get { return this.listMyTab; } set { this.listMyTab = value; this.NotifyPropertyChanged("ListMyTab"); } }
    

    Then xaml look like that :

    <TabControl ItemsSource="{Binding ListMyTab}" Grid.Column="1" Grid.Row="1" Grid.RowSpan="5">
        <TabControl.ItemTemplate>
             <DataTemplate>
                  <StackPanel Orientation="Horizontal">
                            <Grid>
                                <Label Grid.Column="1" Content="{Binding FirstName}" Margin="3" />
                            </Grid>
    
                        </StackPanel>
             </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
             <DataTemplate DataType="local:MyTab">
                   <StackPanel>
                       <Label Content="First Name:" />
                       <TextBlock Binding="{Binding FirstName}" Margin="0,0,0,10"/>
                       <Label Content="Second Name:" />
                       <TextBlock Binding="{Binding SecondName}" Margin="0,0,0,10"/>
                       <Label Content="ID Number:" />
                       <TextBlock Binding="{Binding Id}" Margin="0,0,0,10"/>
                       <Label Content="Age:" />
                       <TextBlock Binding="{Binding Age}" Margin="0,0,0,10"/>
                       <Label Content="Gender:" />
                       <TextBlock Binding="{Binding Gender}" Margin="0,0,0,10"/>
                       <Label Content="Address:" />
                       <TextBlock Binding="{Binding Address}" Margin="0,0,0,10"/>
                  </StackPanel>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
    

    TabControl's content is linked to ListMyTab (one TabItem for each object MyTab). Then the content of each TabItem is binded to each item. So when you will edit FirstName in the TextBlock (I would use TextBox instead), the header will be automaticaly updated (this is Binding power).

    If you want to add a new item, then add somewhere a button, with that :

    private void Button_Add_Click(object sender, RoutedEventArgs e)
    {
        this.ListMyTab.Add(new MyTab());//add default values if necessary
    }
    

    It will add an item in observable collection, then a new tab will appear.

    Also for your tab appearance, I'd advise you to have a look at that :

    <Grid.ColumnDefinitions>
    <ColumnDefinition Width="auto"/>
    <ColumnDefinition Width="auto"/>
    <ColumnDefinition Width="*"/>
    <ColumnDefinition Width="auto"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"/>
        <RowDefinition Height="auto"/>
    </Grid.RowDefinitions>