WPF Tabcontrol not showing TabItem content until selection changes - wpf

I have the following Tabcontrol definition:
<TabControl Grid.Row="0" SelectedItem="{Binding SelectedTab}" >
<TabItem Header="Foo" IsSelected="True" >
One
</TabItem>
<TabItem Header="Bar">
Two
</TabItem>
<TabItem Header="Smeh">
Three
</TabItem>
</TabControl>
I would expect the "Foo" tab to be selected and the 'One' text to be visible. This is not the behaviour I'm seeing. It appears all tabs are unselected and until I click on a different tab (clicking on the one that is supposedly already selected does nothing), then the tab looks selected and shows content.
Does anyone know why the TabControl works like this and how to fix it to work correctly?

By default there is no selected item in the TabControl. If you want to synchronize some model with the selected item, try
<TabControl Grid.Row="0" SelectedItem="{Binding SelectedTab, Mode=OneWayToSource}" >

Related

Best Way to Replace UserControl Loaded in TabControl TabItem

I currently have a TabControl loaded in MainWindow.xaml, which has three tabs. Let's name them Tab1, Tab2 and Tab3.
The individual tabs have views (usercontrols) supplied and they all work as expected. Code as follows. I am using MahApps Metro TabControl here.
<Controls:MetroAnimatedSingleRowTabControl Grid.Row="0" Grid.ColumnSpan="4" x:Name="MainTabControl">
<TabItem Header="Tab1">
<view:Tab1View DataContext="{Binding}" />
</TabItem>
<TabItem Header="Tab2">
<view:Tab2View DataContext="{Binding}" />
</TabItem>
<TabItem Header="Tab3">
<view:Tab3View DataContext="{Binding}" />
</TabItem>
</Controls:MetroAnimatedSingleRowTabControl>
What I would like to do now is to switch the view of Tab3 (which is Tab3View.xaml) to another view (let's call it subTab3View.xaml) when I click on a button on Tab3View.xaml. This will basically switch the Tab3 content from Tab3View.xaml to subTab3View.xaml.
Could anyone kindly suggest me a way to achieve this?
You can have a content control in your tab, and then change the content on button click or whatever event you want.
<TabItem Header="Tab3">
<ContentControl x:Name="contentControl"/>
</TabItem>
private void ButtonClick(object sender, RoutedEventArgs e)
{
this.contentControl.Content = new Tab3View();
}

TabControl: all TabItems collapsed, but content of 1st TabItem still visible

I've got a rather strange behavior on a TabControl, whose TabItems are all collapsed: The content of the first TabItem is still visible (but the header is not).
The TabControl and its TabItems are setup like this:
<TabControl>
<TabItem Header="Data 1"
Visibility="{Binding Path=DataTable1.HasRows,
Converter={StaticResource BoolToVisibility}}">
<UI:ShowData DataContext="{Binding Path=DataTable1}"/>
</TabItem>
<TabItem Header="Data 2"
Visibility="{Binding Path=DataTable2.HasRows,
Converter={StaticResource BoolToVisibility}}">
<UI:ShowData DataContext="{Binding Path=DataTable2}"/>
</TabItem>
</TabControl>
If none of the data tables contains any rows, none of the TabItems should be shown. (I known that I could hide the whole TabControl in that case, but that's not the point here.)
Actually the content of the tab item "Header 1" will be displayed despite the TabItem being collapsed! The TabItem's header itself is collapsed, the TabItems border which contains its content and the content itself are displayed.
Edit/Add:
This can easily be reproduced using this code (note using Collapsed or Hidden does not make any difference:
<TabControl>
<TabItem Header="Test 1" Visibility="Hidden">
<Label>Test1</Label>
</TabItem>
<TabItem Header="Test 2" Visibility="Hidden">
<Label>Test2</Label>
</TabItem>
</TabControl>
So what's wrong here? Any help/hints are appreciated!
Ok, so you've found a real problem here... I looked around online and found several posts that relate to this. Some say that this is a bug, while others say that it is the designed behaviour. don't know which, although it certainly seems to be more of a bug than a feature.
Either way, you want to know how to deal with the problem. .. there are several solutions. One is just to set the TabItem.Content to null whenever you want to hide the tab and another is another involves adding an empty TabItem and selecting that item before hiding (so that it's empty content is shown).
You can attach a handler to the TabItem.IsVisibleChanged Event to be notified when the Visibility property has been changed:
public void TabItemIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// Hide TabItem.Content here
}
Here are some links to the relevant posts:
Bug in TabControl/TabItem`s content visibility?
WPF TabControl - Select different tab when TabItem Visibility changes
Is there a workaround for this tabcontrol/tabitem bug
Another solution that I prefer over the ones suggested: Bind the visibility of the TabItem and its content to the same property (using the BooleanToVisibilityConverter).
Here's a simple example:
<UserControl.Resources >
<BooleanToVisibilityConverter x:Key="boolToVis"/>
</UserControl.Resources>
<Grid>
<TabControl>
<TabItem Header="TabItem 1" Visibility="{Binding Item1Visibility, Converter={StaticResource boolToVis}}">
<Label Content="Content 1" Visibility="{Binding Item1Visibility, Converter={StaticResource boolToVis}}"/>
</TabItem>
<TabItem Header="TabItem 2" Visibility="{Binding Item1Visibility, Converter={StaticResource boolToVis}}">
<Label Content="Content 2" Visibility="{Binding Item1Visibility, Converter={StaticResource boolToVis}}"/>
</TabItem>
</TabControl>
</Grid>
Could be a WPF bug, anyway bypass by binding the content visibility to the tab item visibility.
<TabControl>
<TabItem x:Name="_test1Tab" Header="Test 1" Visibility="Hidden">
<Label Visibility="{Binding ElementName=_test1Tab, Path=Visibility}">Test1</Label>
</TabItem>
<TabItem x:Name="_test2Tab" Header="Test 2" Visibility="Hidden">
<Label Visibility="{Binding ElementName=_test1Tab, Path=Visibility}">Test2</Label>
</TabItem>
</TabControl>
My solution to this was to put the TabItem I wanted to hide in another position. The problem happens only if you want to collapse only the first TabItem.

WPF: How to set background of TabItem?

How to set the background of TabItem? I tried the following code:
<TabControl>
<TabItem Header="Test" Background="Blue" Foreground="Red" />
</TabControl>
Foreground works, but Background does not work.
Any ideas? Thanks
What is happening is that in the case of a single tab, it is always selected, and so you are only seeing the selection style of the tab item.
For example, take a look at the following TabControl:
<TabControl>
<TabItem Header="Tab A" Background="Blue" Foreground="Red">
<Grid />
</TabItem>
<TabItem Header="Tab B" Background="Green" Foreground="Navy" >
<Grid />
</TabItem>
<TabItem Header="Tab C" Background="LightBlue">
<Grid />
</TabItem>
</TabControl>
Tab A will not display its Blue background until you select a different tab. If you truly want the Background to remain the same regardless of whether it is selected or not, you will need to override the control template of the TabItem.
See the question TabItem Background color changes when tabitem selected or hover over for an example of how to do this.

Binding a TextBlock's text to TabControl item in WPF

I have a WPF window with a tab control, and I'm defining the TabItems in the XAML file, like:
<TabControl>
<TabItem Name="tab1" Tag="Transactions"/>
<TabItem Name="tab2" Tag="Promotions" />
...
</TabControl>
Elsewhere on the screen I have a textblock which I want to use to display the Tag value of the selected tab. It works when the screen is initially loaded, and whenever the "transactions" tab is selected, but when a different tab is selected, it's blank. Why is that, and how can I make it display the tag of any selected tab? Here is the TextBlock:
<TextBlock Text="{Binding ElementName=tabControl1, Path=SelectedItem.Tag}"/>
This works as expected for me. (You did set the name of the TabControl, right?)
Note that if the TabControl is populated via ItemsSource unlike your example code the SelectedItem will not contain the TabItem but the data-object from which the TabItem is created, so the binding path SelectedItem.Tag does not work.
Code used:
<!-- Both controls enclosed in a Stackpanel -->
<TabControl Name="tabControl1">
<TabItem Name="tab1" Tag="Transactions"/>
<TabItem Name="tab2" Tag="Promotions" />
</TabControl>
<TextBlock Text="{Binding ElementName=tabControl1, Path=SelectedItem.Tag}"/>
The tab headers are obviously going to be empty but they are selectable.

Drop-down TabControl

I've been trying to create a custom skin/template for a TabControl in WPF.
I want the tabs to be displayed in a ComboBox. When you select the item from the ComboBox, I want the content area of the tab control to display the TabItem contents.
Here's an image showing what I'm looking for:
I could do this using some sort of master-detail setup with data objects and templates, but the problem is I want to set up the controls using the TabControl XAML format, like this:
<TabControl Style="{DynamicResource ComboTabControlStyle}">
<TabItem Header="TabItem1">
<TextBlock Text="TabItem1 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
<TabItem Header="TabItem2">
<TextBlock Text="TabItem2 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
<TabItem Header="TabItem3">
<TextBlock Text="TabItem3 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
</TabControl>
Any thoughts or suggestions?
It is very easy to change the layout of the tab items using a different Panel, but a ComboBox is an ItemsControl, not a Panel.
I tried putting the ComboBox in the TabControl template and binding the ItemsSource of the ComboBox to the TabControl.Items property, but it didn't seem to work correctly.
I also tried creating a custom panel that only shows one "selected" item at a time and shows all of the items in a drop-down when you click on it (basically a "ComboBox" panel). I ran into trouble because visuals can only be in one place in the visual tree. So putting the children of the panel into a popup caused an exception to be thrown.
Anybody have any other ideas?
Thanks for the help!
It's surprisingly difficult to do what you're trying to do. This comes very close:
<DockPanel>
<ComboBox x:Name="ItemSelector" DockPanel.Dock="Top">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type TabItem}">
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<TabItem Header="TabItem1">
<TextBlock Text="TabItem1 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
<TabItem Header="TabItem2">
<TextBlock Text="TabItem2 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
<TabItem Header="TabItem3">
<TextBlock Text="TabItem3 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
</ComboBox>
<ContentPresenter Content="{Binding SelectedItem.Content, ElementName=ItemSelector}" DockPanel.Dock="Top"/>
<TextBlock/>
</DockPanel>
Where it breaks down, oddly, is displaying the selected item in the ComboBox: the ItemTemplate is ignored when rendering the item, so the selection box contains a TabItem. To fix this, you have to subclass ComboBox and implement a read/write SelectionBoxItemTemplate dependency property, because, for some reason that I'm sure is not as stupid as it seems to me at this moment, that property's read-only.
Create a new class, inherit from panel, put a combo box inside, do a lot of parent binding, and use it. it'll do the trick.
You must use custom class to write the tabs as they are regular tab items.
I found a solution.
I created a custom Control class (MasterDetailControl).
This class has two template parts:
[TemplatePart(Name = "PART_MasterSelector", Type = typeof(Selector))]
[TemplatePart(Name = "PART_DetailPresenter", Type = typeof(ContentPresenter))]
The control has an items dependency property:
public IList Items { ... }
I added a helper class:
[ContentProperty("Detail")]
public class MasterDetail
{
public object Master { get; set; }
public object Detail { get; set; }
}
Items that are placed in the Items DP are processed by the MasterDetailControl. If they are of type MasterDetail, the master is added to the selector items list. For other child item types, a new MasterDetail object is created with the object assigned to the master and detail fields. A separate list maintains all of the generated MasterDetail objects with indexes that correspond to those in the Selector control.
When the SelectionChanged event fires on the Selector object, I set the ContentPresenter's Content property to the Detail field of the item corresponding to the selected master object.
If anyone wants more details, feel free to comment.
In the end, I can now use this control with a simple control template specifying any selector object (ListBox, ComboBox, etc) and a ContentPresenter.

Resources