ToolTip on TabItem: Show on header, but not on content - wpf

Tooltips on a TabControl's TabItems are not only spawn on the TabItem's header, but also on any TabItem content which doesn't explicitly set its own ToolTip.
Here is an example, which reproduces the problem:
<Window x:Class="TestToolTipsOnTabControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow">
<Grid>
<TabControl>
<TabItem Header="Tab1"
ToolTip="Tooltip of tab1">
<StackPanel>
<TextBlock Text="Content of tab1 with its own tooltip"
ToolTip="Tooltip on content of tab1"/>
<TextBlock Text="more content of tab1" />
</StackPanel>
</TabItem>
<TabItem Header="Tab2"
ToolTipService.ToolTip="Tooltip of tab2">
<StackPanel>
<TextBlock Text="Content of tab2 with its own tooltip"
ToolTipService.ToolTip="Tooltip on content of tab2"/>
<TextBlock Text="more content of tab2" />
</StackPanel>
</TabItem>
<TabItem Header="Tab3">
<StackPanel>
<TextBlock Text="Content of tab3" />
<TextBlock Text="more content of tab3" />
</StackPanel>
</TabItem>
</TabControl>
</Grid>
</Window>
Moving the mouse pointer over the "more content of tab1" text will display the ToolTip which I'd like to only show up on the TabItem header.
Is there any way to for the ToolTip to show up only on the TabItem header, but nowhere else?

Is there any way to for the ToolTip to show up only on the TabItem header, but nowhere else?
You should only apply Tooltip to the Header not the whole TabItem so change it to:
<TabItem>
<TabItem.Header>
<TextBlock Text="Tab1"
ToolTip="Tooltip of tab1"/>
</TabItem.Header>
<StackPanel>
<TextBlock Text="Content of tab1 with its own tooltip"
ToolTip="Tooltip on content of tab1"/>
<TextBlock Text="more content of tab1" />
</StackPanel>
</TabItem>

Add the below piece of code to the textblocks
ToolTip="", ToolTipSevice.ShowDuration="0"

This works for me when adding tabItems dynamically:
TabItem nt = new TabItem
string _newTabItemText = "xxxx" // Dynamic header text
string _newTabItemTooltip = "xxxx Tooltip" // Dynamic tooltip text
string _newTabItemName = "xxxx" // tabItem name to reference the tab item in code ie XAML x:name = "xxxx"
{
Header = new TextBlock() { Text = _newTabItemText, ToolTip = _newTabItemTooltip },
Name = _newTabItemName,
Width = 108
};

As an alternative, I can offer is this: created a Style for ToolTip with zero Width and Height:
<Style x:Key="NullToolTip" TargetType="{x:Type ToolTip}">
<Setter Property="Width" Value="0" />
<Setter Property="Height" Value="0" />
<Setter Property="Content" Value="{x:Null}" />
</Style>
When created ToolTip with this Style and placed in Resources:
<ToolTip x:Key="NoToolTip" Style="{StaticResource NullToolTip}" />
And assign to control that want to turn off the ToolTip:
<TabItem Header="Tab1" ToolTip="Tooltip of tab1">
<StackPanel>
<TextBlock Text="Content of tab1 with its own tooltip" ToolTip="Tooltip on content of tab1"/>
<TextBlock Text="more content of tab1" ToolTipService.ToolTip="{StaticResource NoToolTip}" />
</StackPanel>
</TabItem>
Note: Similar problem appears when you use the ToolTip for TreeViewItem. His children inherit parent ToolTip.

Just in case someone is running into this issue when creating the tabs dynamically, the following code did the magic:
TabItem tabItem = new TabItem();
var stackPanel = new StackPanel();
var stackPanelToolTip = new System.Windows.Controls.ToolTip();
stackPanelToolTip.Content = "ToolTip content";
stackPanel.ToolTip = (stackPanelToolTip);
tabItem.Header = stackPanel;
So the key here was to add the tooltip to the tab header, but not to the tab element (adding it to the header means that you will have the tooltip visible only when mouse is hover the tab).

Related

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 : dynamic view/content

I'm a bit beginner in WPF, so I ask this..
Let's say I have a window, and inside the window I want to have something like container, could be just border or maybe panel (in winform terms). The content of container is binded to the selected option (e.g:button). So, for instance, when user selects OPTION 1, the container shows chart; when user selects OPTION 2, the container shows listview filled with data; when user selects OPTION 3, the container shows another things, and so on.
What is the best/nicest (or easiest maybe) approach to do this? I'm thinking about using user control for the content of the container, but don't know if this is nice solution neither the performance for using user control to show little bit complex things and maybe some calculations. Any other idea guys?
To elaborate on #Sheridan's answer, here is a simple TabControl XAML that does what you need:
<TabControl TabStripPlacement="Left">
<TabItem Header="Option 1">
<Grid Background="DarkGray">
<TextBlock Foreground="AliceBlue" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="20" Text="View 1"/>
</Grid>
</TabItem>
<TabItem Header="Option 2">
<Grid Background="DarkBlue">
<TextBlock Foreground="AliceBlue" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="20" Text="View 2"/>
</Grid>
</TabItem>
<TabItem Header="Option 3">
<Grid Background="DarkSlateBlue">
<TextBlock Foreground="AliceBlue" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="20" Text="View 3"/>
</Grid>
</TabItem>
</TabControl>
Result:
You can customize it a little bit by adding this simple Style To your Window.Resources:
<Window.Resources>
<Style TargetType="TabItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<RadioButton Content="{TemplateBinding Header}" Margin="2"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
Which then results in:
The "WPF Mentality" makes you think the UI controls in terms of their functionality, not their appearance, this is a TabControl =)
I solved this with a ContentControl
MainWindow:
(Define the views you wish to visualize as resources)
<Window.Resources>
<DataTemplate DataType="{x:Type viewModels:SystemPCViewModel}">
<controls:SystemPCControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:ChipPCViewModel}">
<controls:ChipPCControl/>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding CurrentView}"/>
</Grid>
ViewModel: (can't get much simpler)
public ViewModelBase CurrentView
{
get { return currentView; }
set { Set(() => CurrentView, ref currentView, value); }
}
And there you go, you can change your views by setting the view model for the controls you defined in your MainWindow
private void OnCommandExecuted()
{
CurrentView = someViewModel;
}
private void OnAnotherCommandExecuted()
{
CurrentView = anotherViewModel;
}
HTH!
What you are describing sounds pretty close to a standard TabControl, but with a ControlTemplate that puts the tabs on the left side instead of above the content panel. Using this method would mean having a UserControl in each TabItem, eg. multiple controls. You can find out more about the TabControl from the TabControl Class page at MSDN.

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.

How to bind the name of the active TabItem to a Label in WPF?

actual the Labels shows the namespace of the control and not the name (header) of the active tabitem.
..
<Label Content="{x:Type TabControl}" />
</Grid>
<TabControl>
<TabItem Header="Header1" />
<TabItem Header="Header2" />
..
I hope this is what you want -
<TabControl x:Name="MyControl">
<TabItem Header="tab1" />
<TabItem Header="tab2" />
<TabItem Header="tab3" />
</TabControl>
<!-- ... -->
<Label Content="{Binding ElementName=MyControl, Path=SelectedItem.Header}"/>
You obviously have no clue about what you are doing, read this: Data Binding Overview
The x:Type markup extension has nothing to do with binding, it just returns the type of a given class.
One way to bind to the selected item:
<Label Content="{Binding ElementName=tc, Path=SelectedItem.Header}"/>
<TabControl Name="tc" ...>
<!-- Items -->
</TabControl>
(Note: The SelectedItem normally (- when using ItemsSource -) does not represent the selected control but the data behind the selected item)

WP7 - Scrolling ListBox in external ScrollViewer

I have the following page layout in my application:
<Grid x:Name="ContentPanel"
Grid.Row="1">
<ScrollViewer x:Name="ScrollViewer1"
MaxHeight="600"
VerticalAlignment="Top"
HorizontalAlignment="Stretch">
<StackPanel x:Name="StackPanel1" >
<TextBlock x:Name="TextBlock1" />
<toolkit:ListPicker x:Name="ListPicker1" />
<TextBlock x:Name="TextBlock2" />
<TextBox x:Name="TextBlock3" />
<TextBlock x:Name="TextBlock4" />
<StackPanel x:Name="StackPanel2" >
<TextBlock x:Name="TextBlock5" />
<Image x:Name="Image1"/>
</StackPanel>
<ListBox x:Name="ListBox1">
<!--Customize the ListBox template to remove the built-in ScrollViewer-->
<ListBox.Template>
<ControlTemplate>
<ItemsPresenter />
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemTemplate>
<DataTemplate>
<!-- .... -->
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</StackPanel>
</ScrollViewer>
</Grid>
I added an external ScrollViewer instead of the using the ListBox's because without it the stuff above the ListBox was taking too much room and not leaving enough space to view the ListBox contents.
Now, the problem is if I add an item to the end of the ListBox the ScrollIntoView method does not work. So I need to use the ScrollViewer's ScrollToVerticalOffset method.
I'm adding the new item to an ObservableCollection that is bound to the ListBox when the user clicks a button on the application bar. How can I calculate the value to be passed to ScrollViewer.ScrollToVerticalOffset?
Thanks for your help!
You can find the container that the ListBox has generated to host your element. Once you have this container, you can find its position relative to the scrollviewer:
var newItem = // the item you just added to your listbox
// find the ListBox container
listBox.UpdateLayout()
var element = listBox.ItemContainerGenerator.ContainerFromItem(newItem) as FrameworkElement;
// find its position in the scroll viewer
var transform = element.TransformToVisual(ScrollViewer);
var elementLocation = transform.Transform(new Point(0, 0));
double newVerticalOffset = elementLocation.Y + ScrollViewer.VerticalOffset;
// scroll into view
ScrollViewer.ScrollToVerticalOffset(newVerticalOffset);
Hope that helps

Resources