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:
- When I enter Person 1's Name and then click the Person 2 tab, Person 1's Name is still visible, hence my assumption that the controls are not databound properly. I understand that ItemsControls do not pass their DataContext down to their children, but I am not sure how to fix this without associating the View in code-behind.
- I would have expected to get databinding errors in the Output window because of the missing DataContext, but I don't get any errors. I assume the DataContext is null, but wouldn't this still result in a binding error?
- How can I use Snoop effectively to debug problems like this?
Here's the sample solution: http://www.filedropper.com/tabcontrolmvvm