1
votes

I have TabControl that has already define some TabItems on XAML. I need to create new TabItems and add to it.

If I use ItemSource I get an exception Items collection must be empty before using ItemsSource.

The solution I have found so far is to create those TabItems I have already defined on XAML but programmatically on the ViewModel, so I can created the others I really need, but doesn't seems to be a good solution.

Other solution would be to add the TabControl as a property and use the Code-Behind to bind it to the ViewModel, which I would like to avoid.

So, I'm just wondering if there is a way to do this only with XAML and MVVM.

Edit:

ItemSource attempt, which is working.

XAML:

<TabControl Grid.Row="1"
        Grid.Column="0"
        VerticalAlignment="Stretch"
        BorderThickness="0.5"
        BorderBrush="Black"
        ItemsSource="{Binding Model.TabItems, Mode=TwoWay}">
    <!--<TabControl.Items>
    </TabControl.Items>-->
</TabControl>

Model

public ObservableCollection<TabItem> TabItems {get;set;}

VM

TabItem tabItem = new TabItem { Content = new DetailedViewModel((MyObject)inCommandParameter) };
Model.TabItems.Add(tabItem);
1
The only way I can think with using XAML is that you set all your TabItems but you set their Visibility to false, and then when you need them you show them. And if you are using ItemSource, well then I suggest you to check this.arcticwhite
Yep, with ItemSource its working, but I have some fix TabItem's and others that I have to create dynamically, it makes sense for me to create on XAML the fixed ones, and the others in the ViewModel, but seems that I will have to do "everything or nothing" style. Thanks for your answer!Nekeniehl
Can you show us the XAML for the ItemsSource attempt?XAMlMAX
Sure @XAMlMAX, but like I said, that is working fine. The problem is when I mix both, definition of TabItems on XAML and in the VMNekeniehl
Have you heard of CompositeCollection and CollectionViewSource?XAMlMAX

1 Answers

2
votes

What you are doing here is NOT MvvM. Idea behind it is to keep parts of the app separate, i.e. Model should NOT return any UI elements. If you want to use this with any other UI framework for example WinForms then it will fail and will require additional work.
What you need is something like this, bear in mind that this is an example and you will need to modify this to comply with your requirements.
Model class:

namespace Model
{
    public class Profile
    {
        public string Name { get; set; }

        public static int I { get; set; } = 2;
    }
}  

After this you will need the ViewModel:

namespace VM
{
    public class MainViewModel : BaseViewModel
    {
        public MainViewModel()
        {
            ProfilesCollection = new List<Profile>();
            for (int i = 0; i < 100; i++)
            {
                ProfilesCollection.Add(new Profile() {Name = $"Name {i}"});
            }
        }

        private List<Profile> profilesCollection;   

        public List<Profile> ProfilesCollection
        {
            get { return profilesCollection; }
            set { profilesCollection = value; OnPropertyChanged(); }
        }
    }
}  

Now we have base to work with. After that I assume you know how to add the relevant references in your xaml, but this might be seen by other people so I will include it anyway.
Here is a complete MainWindow.xaml:

<Window x:Class="SO_app.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="clr-namespace:VM;assembly=VM"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
    xmlns:converter="clr-namespace:SO_app.Converters"
    xmlns:validation="clr-namespace:SO_app.Validation"
    xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
    xmlns:local="clr-namespace:SO_app"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns:model="clr-namespace:Model;assembly=Model"//reference to my model
    mc:Ignorable="d"
    Title="MainWindow" Height="452.762" Width="525" Closing="Window_Closing">
<!-- d:DataContext="{d:DesignInstance Type=vm:MainViewModel, IsDesignTimeCreatable=True}" -->
<Window.Resources>
    <CollectionViewSource Source="{Binding ProfilesCollection}" x:Key="profiles"/> // this corresponds to our collection in VM
</Window.Resources>
<Window.DataContext>
    <vm:MainViewModel/>//Data Context of the Window
</Window.DataContext>

<Window.Background>
    <VisualBrush>
        <VisualBrush.Visual>
            <Rectangle Width="50" Height="50" Fill="ForestGreen"></Rectangle>
        </VisualBrush.Visual>
    </VisualBrush>
</Window.Background>
<TabControl>
    <TabControl.Resources>
        <DataTemplate DataType="{x:Type model:Profile}">//this data template will be used by the TabControl
            <Grid>
                <TextBlock Text="{Binding Name}"/>
            </Grid>
        </DataTemplate>
    </TabControl.Resources>
    <TabControl.ItemsSource>
        <CompositeCollection>
            <TabItem Header="First Item"/>
            <TabItem Header="SecondItem"/>
            <CollectionContainer Collection="{Binding Source={StaticResource profiles}}"/>
        </CompositeCollection>
    </TabControl.ItemsSource>
</TabControl>


If you want to add more items then just use Command which would be implemented in VM and just add profile to it and enjoy the show.