How do I bind a TabControl to a collection of ViewModels?
This isn't MVVM. You should not be creating UI elements in your view model.
You should be binding the ItemsSource of the Tab to your ObservableCollection, and that should hold models with information about the tabs that should be created.
Here are the VM and the model which represents a tab page:
public sealed class ViewModel
{
public ObservableCollection<TabItem> Tabs {get;set;}
public ViewModel()
{
Tabs = new ObservableCollection<TabItem>();
Tabs.Add(new TabItem { Header = "One", Content = "One's content" });
Tabs.Add(new TabItem { Header = "Two", Content = "Two's content" });
}
}
public sealed class TabItem
{
public string Header { get; set; }
public string Content { get; set; }
}
And here is how the bindings look in the window:
<Window x:Class="WpfApplication12.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">
<Window.DataContext>
<ViewModel
xmlns="clr-namespace:WpfApplication12" />
</Window.DataContext>
<TabControl
ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock
Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<TextBlock
Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Window>
(Note, if you want different stuff in different tabs, use DataTemplates
. Either each tab's view model should be its own class, or create a custom DataTemplateSelector
to pick the correct template.)
A UserControl inside the data template:
<TabControl
ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock
Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<MyUserControl xmlns="clr-namespace:WpfApplication12" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Binding collection of viewmodels to WPF MVVM TabControl
How can I bind a collection of ViewModels to populate the headers and content of a tab control without depending on the TabItem object?
Try this:
View:
<TabControl ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
View Model:
public class TabViewModel
{
public TabViewModel()
{
Items = new ObservableCollection<Item>()
{
new Item { Header = "a", Content = "..." },
new Item { Header = "b", Content = "..." },
new Item { Header = "c", Content = "..." },
};
}
public ObservableCollection<Item> Items { get; set; }
}
Item
is a POCO class.
WPF MVVM: Binding a different ViewModel to each TabItem?
You can indeed add the view models for your tabs to a main view model. You can then bind to the child view models in the XAML for your tabs.
Say that you have three viewmodels: MainViewModel
, Tab1ViewModel
, and Tab2ViewModel
. On your MainViewModel
you keep a collection of your tab viewmodels:
class MainViewModel
{
ObservableCollection<object> _children;
public MainViewModel()
{
_children = new ObservableCollection<object>();
_children.Add(new Tab1ViewModel());
_children.Add(new Tab2ViewModel());
}
public ObservableCollection<object> Children { get { return _children; } }
}
After setting the DataContext
of your main window to your MainViewModel
you can bind the DataContext
of your tabs by referencing the Children
property:
<TabControl>
<TabItem DataContext="{Binding Children[0]}" x:Name="Tab1" Header="Tab1" >
<!-- Tab content -->
</TabItem>
<TabItem DataContext="{Binding Children[1]}" x:Name="Tab2" Header="Tab2" >
<!-- Tab content -->
</TabItem>
</TabControl>
Binding TabControl ItemsSource to Collection of ViewModels
I ended up using a ContentControl
with a TabControl
data template (like the original tutorial project). Here is the xaml code I ended up with. I did not change the code-behind from the original sample project to make this work. The ContentControl
is in my MainWindow.xaml and the other two pieces of code were in a ResourceDictionary
.
<!-- Workspaces Tab Control -->
<ContentControl Grid.Row="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Content="{Binding Path=Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}"/>
<!-- Workspaces Template -->
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl Style="{StaticResource ClosableTabControl}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource WorkspaceTabItemTemplate}"
Margin="1"/>
</DataTemplate>
<!-- Workspace Tab Item Template -->
<DataTemplate x:Key="WorkspaceTabItemTemplate">
<Grid Width="Auto" Height="Auto">
<ContentPresenter ContentSource="Header" Margin="3"
Content="{Binding Path=DisplayName}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ContentPresenter.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource Foreground}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
</DataTemplate>
How to bind a TabControl with a collection of TabItems
You would want to bind the ItemSource for the TabControl to you collection to generate the tabs. Something like this:
<TabControl ItemsSource="{Binding TableAreaCollection}">
<TabControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding TableCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="canvas1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:TableViewModel}">
<Button Uid="{Binding TableName}" ContentStringFormat="{Binding TableGuestCount}" Style="{StaticResource ResourceKey=BtnTableEmpty}" Width="84" Height="90" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=TablePosX}" />
<Setter Property="Canvas.Top" Value="{Binding Path=TablePosY}" />
<Setter Property="Tag" Value="{Binding Path=TableID}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
Tabcontrol MVVM light viewmodel binding
Don't use static
fields in your MainViewModel
, it's a bad design decision and makes your code not testable.
Instead, use the powerful data templating mechanism of WPF.
class MainViewModel : INotifyPropertyChanged
{
// Note: this is just a sample.
// You might want to inject the instances via DI
public IEnumerable<INotifyPropertyChanged> TabItems { get; } =
new[] { new PricesViewModel(), new LicensesViewModel() };
}
Use the data templates for your view models:
<TabControl ItemsSource="{Binding TabItems}" DisplayMemberPath="PropertyNameForTabHeader">
<TabControl.Resources>
<DataTemplate DataType="{x:Type viewModels:PricesViewModel}">
<local:PricesControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:LicensesViewModel}">
<local:LicenseControl/>
</DataTemplate>
</TabControl.Resources>
</TabControl>
The DisplayMemberPath
property specifies the name of the tab item's view model property to use as the tab's header.
With this approach, your design is dynamic and scalable.
How to bind WPF TabControl ContentTemplate to an observable collection of different ViewModels
Ok, i will modify the sample code in the answer you linked:
<Window.Resources>
<DataTemplate x:Key="templateForTheHeader" DataType="{x:Type vm:BaseViewModel}">
<TextBlock Text="{Binding CommonPropertyToDisplayInTheHeader}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ViewModel1}">
<TextBlock Text="{Binding PropertyInVM1}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ViewModel2}">
<TextBlock Text="{Binding PropertyInVM2}"/>
</DataTemplate>
</Window.Resources>
...
<TabControl ItemsSource="{Binding YourCollection}"
ItemTemplate="{StaticResource templateForTheHeader}">
</TabControl>
The header displays some property in the base VM class.
And the important thing, i removed the x:key
of the other DataTemplates, this will make it being applied to every instance of the defined DataType
found in the Window
(ContentTemplate
in the TabControl
not needed).
YourCollection
is a mix of objects, each of it will get its template applied based on its type if a DataTemplate
with matching DataType
exists. Simple uh?
Binding TabControl to collection of ViewModels
Header is specified by ItemTemplate
, body is specified by ContentTemplate
.
e.g.
<TabControl ItemsSource="{Binding DpData}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Occupation}" />
<TextBlock Text="{Binding Status}" />
<TextBlock Text="{Binding IsActive}" />
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
If you have trouble with bindings and datatemplates read the overviews first:
Data Binding Overview
Data Templating Overview
c# WPF MVVM TabControl with Multiple ViewModels and changing tabs
So I solved this with an EventAggregator.
public static class IntAggregator
{
public static void Transmit(int data)
{
if (OnDataTransmitted != null)
{
OnDataTransmitted(data);
}
}
public static Action<int> OnDataTransmitted;
}
First ViewModel sends data.
public class ModifyUsersViewModel : INotifyPropertyChanged
{
private void change_tab(int data)
{
IntAggregator.Transmit(data);
}
}
Second ViewModel receives data and then does something with it.
public class MainWindowViewModel : INotifyPropertyChanged
{
private int _Tab_SelectedIndex = 0;
public int Tab_SelectedIndex
{
get
{
return _Tab_SelectedIndex;
}
set
{
_Tab_SelectedIndex = value;
OnPropertyChanged(new PropertyChangedEventArgs("Tab_SelectedIndex"));
}
}
public MainWindowViewModel()
{
IntAggregator.OnDataTransmitted += OnDataReceived;
}
private void OnDataReceived(int data)
{
Tab_SelectedIndex = data;
}
}
Related Topics
How to Get the Unix Timestamp in C#
Cannot Delete Directory with Directory.Delete(Path, True)
Why Doesn't .Net/C# Optimize for Tail-Call Recursion
Checking for Directory and File Write Permissions in .Net
System.Missingmethodexception: Method Not Found
How to Get Windows Display Settings
Resolving Instances with ASP.NET Core Di from Within Configureservices
Using Regex to Balance Match Parenthesis
Detect Windows Version in .Net
How to Use Reflection to Inspect the Code in a Method
Using String Format to Show Decimal Up to 2 Places or Simple Integer
Create Folder and File on Current User Profile, from Admin Profile
Launching an Application (.Exe) from C#
Selectsinglenode Returns Null When Tag Contains Xmlnamespace
Difference Between Invariantculture and Ordinal String Comparison