Reputation: 15016
Just when I thought I was getting better at this, TabControl is now giving me problems. I have read relevant posts here on StackOverflow, but have been unable to get my simple demo application to work the way I want it to.
To keep things focused, I'll start with a single question about something I don't understand.
I have a TabControl whose TabItems each host the same UserControl. When I set the TabControl.ContentTemplate's DataTemplate to my UserControl, a rendering of that control appears, but it looks like it's the same control for each tab. Or perhaps it's not tied to any of the tabs at all.
MainWindow.xaml
<Window x:Class="TabControlMvvm.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:localviews="clr-namespace:TabControlMvvm.Views"
Title="MainWindow" Height="350" Width="525">
<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="{Binding Selected}">
<TabControl.ContentTemplate>
<DataTemplate>
<localviews:PersonMainPanel />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Window>
Code-behind just sets the ViewModel as its DataContext:
namespace TabControlMvvm {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow()
{
InitializeComponent();
DataContext = new TabControlMvvm.ViewModels.MainViewModel();
}
}
}
The TabItem's Content should be another UserControl, PersonMainPanel.xaml:
<UserControl x:Class="TabControlMvvm.Views.PersonMainPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:localviews="clr-namespace:TabControlMvvm.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Border BorderBrush="Red" BorderThickness="2">
<TabControl TabStripPlacement="Bottom">
<TabItem Header="Tab 1">
<localviews:MyTabItem />
</TabItem>
<TabItem Header="Tab 2">
<TextBlock Text="This was left blank intentionally" />
</TabItem>
<TabItem Header="Tab 3">
<TextBlock Text="This was also left blank intentionally" />
</TabItem>
</TabControl>
</Border>
</UserControl>
Code-behind:
namespace TabControlMvvm.Views {
/// <summary>
/// Interaction logic for PersonMainPanel.xaml
/// </summary>
public partial class PersonMainPanel : UserControl {
public PersonMainPanel()
{
InitializeComponent();
}
}
}
And the MainViewModel:
namespace TabControlMvvm.ViewModels {
public class MainViewModel : ViewModelBase {
public ICollectionView Tabs { get; set; }
public int Selected { get; set; }
public class Person
{
public string Name { get; set; }
}
public class DummyController {
public List<Person> Persons { get; private set; }
public DummyController()
{
Persons = new List<Person> {
new Person { Name = "Larry" },
new Person { Name = "Darryl" },
new Person { Name = "Other brother Darryl" }
};
}
}
public DummyController Controller { get; private set; }
public RelayCommand HelloCommand { get; set; }
public MainViewModel()
{
Controller = new DummyController();
/*
IEnumerable<TabItem> tabs = Enumerable.Range( 1, _controller.Persons.Count())
.Select( x => new TabItem { Header = String.Format( "Person {0}", x),
Content = new PersonMainPanel() });
*/
IEnumerable<TabItem> tabs = Enumerable.Range( 1, Controller.Persons.Count())
.Select( x => new TabItem { Header = String.Format( "Person {0}", x)});
Tabs = CollectionViewSource.GetDefaultView( tabs.ToList());
Tabs.MoveCurrentToFirst();
InitializeCommands();
}
private void InitializeCommands()
{
HelloCommand = new RelayCommand( () => { MessageBox.Show( String.Format( "Hello, Person {0} named {1}!",
Selected, Controller.Persons[Selected].Name)); });
}
}
}
PersonMainPanel hosts another TabControl, where Tab 1's Content is MyTabItem.xaml:
<UserControl x:Class="TabControlMvvm.Views.MyTabItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:" />
<TextBox Text="{Binding Name}" Width="100" />
</StackPanel>
<Button Command="{Binding HelloCommand}" Content="Say Hello" />
</StackPanel>
</UserControl>
Code-behind:
namespace TabControlMvvm.Views {
/// <summary>
/// Interaction logic for MyTabItem.xaml
/// </summary>
public partial class MyTabItem : UserControl {
public MyTabItem()
{
InitializeComponent();
}
}
}
Which looks like this at runtime:
Issues I have so far:
Here's the sample solution: http://www.filedropper.com/tabcontrolmvvm
Upvotes: 3
Views: 7736
Reputation: 1049
Here is solution:
In MainWindow modify your TabControl template, to bind Header from your Model:
<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="{Binding Selected}">
<TabControl.ContentTemplate>
<DataTemplate>
<localviews:PersonMainPanel />
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
In MyTabItem.xaml, set UpdateTrigger, because default one 'OnLostFocus' can sometimes not save your data:
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Width="100" />
In MainViewModel modify creating of your tabs, so it will have Name property too:
IEnumerable<TabItem> tabs = Enumerable.Range( 1, Controller.Persons.Count())
.Select( x => new TabItem { Header = String.Format("Person {0}", x), Name = Controller.Persons[x-1].Name });
Also, the most important, create own TabItem class to contain some bounded data:
public class TabItem
{
public string Name { set; get; }
public string Header { set; get; }
}
Upvotes: 3