TabItem ControlTemplate element Visibility - wpf

I have defined a custom ControlTemplate for TabItem where I am having besides the ContentPresenter a Button (to hide the tab when clicked).
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Border>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentPresenter ContentSource="Header"/>
<Button Grid.Column="1" Content="x" Visibility="Visible"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I want the Button to be Visible only for certain TabItems and Collapsed for others. Collapsed state should be the default and I don't understand how to set it to Visible for certain tabs.
If the tab items are like
<TabItem Header="Normal tab">
<Grid />
</TabItem>
<TabItem Header="Closable tab">
<Grid/>
</TabItem>
and I want to have the "Closable tab" to be showing this Button but the "Normal tab" not showing this Button, how can this be done ?

You can use TabItem's Tag property to identify its type, and watch this type from inside Button with RelativeSource binding.
<TabControl>
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Border BorderThickness="1" BorderBrush="Green" Margin="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentPresenter ContentSource="Header"/>
<Button Grid.Column="1" Content="x">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TabItem}, Path=Tag}" Value="Closable">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem Header="Normal tab">
<Grid />
</TabItem>
<TabItem Tag="Closable" Header="Closable tab">
<Grid />
</TabItem>
</TabControl>
I edited Border attributes a little bit just to visual separate tabs, they are not necessary for this example to work.

Related

WPF Datagrid HeaderColumn Content Alignment

The ContentTemplate code I use for the Datagrid Header is included in the relevant style file as follows:
<Style TargetType="{x:Type DataGrid}">
...
<Style.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding}" Margin="10 5" Grid.Column="0" />
<Button x:Name="btnFilter" Content="" FontFamily="{StaticResource FontAwesome}" FontSize="16" HorizontalAlignment="Right" Grid.Column="1" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
...
</Style>
The resulting image from this code is as follows:
But I want to align the arrow icons to the right side as in the picture below. How can I do that?
Set the HorizontalContentAlignment property of the DataGridColumnHeaders to Stretch:
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="ContentTemplate">
...
</Setter>
</Style>

How can I add a separator between tabs in a tabcontrol?

I'm styling my tabs so that it just displays the header text but I want to add a separator between them. Right now the tabs look like:
Content1 Content2
Is there a way I can add a Separator between the tabs so that it looks like:
Content1 | Content2
I can already style a vertical separator like that but I can't figure out how to place it in between the tabs. I don't want to use a tab border to fake a divider and I would like to stay away from using
<TabItem Header="|" IsEnabled="False" /> to fake it too. Is there an elegant solution for this?
This is what I have so far:
<TabControl Background="Transparent" BorderThickness="0">
<TabItem Header="Content1">
<!--content1-->
</TabItem>
<TabItem Header="Content2">
<!--content2-->
</TabItem>
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border Margin="2,0">
<ContentPresenter ContentSource="Header" Margin="10,2" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="White" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter Property="Foreground" Value="Gray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
Thanks in advance!
I ended up just going with this:
<TabItem IsEnabled="False" IsHitTestVisible="False">
<TabItem.Header>
<Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" Height="10" Background="Black" />
</TabItem.Header>
</TabItem>
For anyone who came here too to see the possible answer (like me).
I did it this way:
In your Resources define three styles
Style 1 (with both separators):
<Style TargetType="TabItem" x:Key="TabItemWithBothSeparators">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Border BorderBrush="DarkGray" BorderThickness="1,0">
<Grid Name="Panel">
<ContentPresenter x:Name="ContentSite" ContentSource="Header" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Style 2 (with left separator):
<Style TargetType="TabItem" x:Key="TabItemWithLeftSeparator">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Border BorderBrush="DarkGray" BorderThickness="1,0,0,0">
<Grid Name="Panel">
<ContentPresenter x:Name="ContentSite" ContentSource="Header" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Style 3 (without separators):
<Style TargetType="TabItem" x:Key="TabItemWithoutSeparators">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Grid Name="Panel">
<ContentPresenter x:Name="ContentSite" ContentSource="Header" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then set the styles this way (code example below):
On odd TabItems count (1,3,5,7,...) set style to TabItemWithoutSeparators
On even TabItems count (2,4,6,8,...) set style to TabItemWithBothSeparators
When your last TabItem count is even, set its style to TabItemWithLeftSeparator otherwise set to TabItemWithoutSeparators
Example with even TabItems count
<TabItem Header="Item 1" Style="{StaticResource TabItemWithoutSeparators}">
<!--content 1-->
</TabItem>
<TabItem Header="Item 2" Style="{StaticResource TabItemWithBothSeparators}">
<!--content 2-->
</TabItem>
<TabItem Header="Item 3" Style="{StaticResource TabItemWithoutSeparators}">
<!--content 3-->
</TabItem>
<TabItem Header="Item 4" Style="{StaticResource TabItemWithLeftSeparator}">
<!--content 4-->
</TabItem>
Example with odd TabItems count
<TabItem Header="Item 1" Style="{StaticResource TabItemWithoutSeparators}">
<!--content 1-->
</TabItem>
<TabItem Header="Item 2" Style="{StaticResource TabItemWithBothSeparators}">
<!--content 2-->
</TabItem>
<TabItem Header="Item 3" Style="{StaticResource TabItemWithoutSeparators}">
<!--content 3-->
</TabItem>
Hope, this helps! Fore sure there is a more elegant way to get this separators...

Docked control tabs not rendering correclty

I'm working on customizing a user control containing a tab control child element. The user control docks to the side of the main window. I want the tab items to orient based on the docking position, eg. left docked tab items rotate 90 degrees to lay along the side. I have the tabs aligning correctly by binding to the attached property DockPanel.Dock. The problem is that when the tabs are rendered they are disconnected by a line from the tab control. I can manually set the (DockPanel.Dock) property for the outer user control object and the tabs docked along that direction draw correctly but the other tabs are separated by a line.
Here is the TabDockControl XML:
<UserControl x:Class="AnimationMotionCaptureStudio.TabDockControl"
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" x:Name="Root" DockPanel.Dock="Left">
<Grid>
<TabControl Height="Auto" HorizontalAlignment="Stretch" Name="tabControl1" VerticalAlignment="Stretch" Width="Auto" TabStripPlacement="{Binding Path=(DockPanel.Dock),ElementName=Root}">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Padding" Value="2" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<ContentPresenter Content="{TemplateBinding Content}" />
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(DockPanel.Dock), ElementName=Root}" Value="Left">
<Setter Property="Padding" Value="2" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<ContentPresenter Content="{TemplateBinding Content}">
<ContentPresenter.LayoutTransform>
<RotateTransform Angle="90" />
</ContentPresenter.LayoutTransform>
</ContentPresenter>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(DockPanel.Dock), ElementName=Root}" Value="Right">
<Setter Property="Padding" Value="5" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<ContentPresenter Content="{TemplateBinding Content}">
<ContentPresenter.LayoutTransform>
<RotateTransform Angle="-90" />
</ContentPresenter.LayoutTransform>
</ContentPresenter>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Resources>
<TabItem Header="{Binding Path=(DockPanel.Dock), ElementName=Root}" Name="tabItem1">
<Grid>
<Border BorderBrush="Silver" BorderThickness="1" Height="Auto" HorizontalAlignment="Stretch" Name="border1" VerticalAlignment="Stretch" Width="Auto" />
</Grid>
</TabItem>
</TabControl>
</Grid>
The code behind file has no additional code added beyond the constructor generated by VS2010.
And the MainWindow XML usage of the control:
<DockPanel Grid.Row="1" Height="Auto" HorizontalAlignment="Stretch" Name="MainDockPanel" VerticalAlignment="Stretch" Width="Auto" >
<DockPanel.Children>
<StatusBar Height="23" Name="statusBar1" Width="Auto" DockPanel.Dock="Bottom" Background="#FF00476D" />
<AMCS:TabDockControl DockPanel.Dock="Left">
</AMCS:TabDockControl>
<AMCS:TabDockControl DockPanel.Dock="Right">
</AMCS:TabDockControl>
<AMCS:TabDockControl DockPanel.Dock="Bottom">
</AMCS:TabDockControl>
<Border BorderBrush="Silver" BorderThickness="2" Height="Auto" Name="border1" Width="Auto" DockPanel.Dock="Top" Background="#FF898888">
<Image Height="Auto" HorizontalAlignment="Stretch" Name="D3DImage" Stretch="Fill" VerticalAlignment="Stretch" Width="Auto" MouseLeftButtonDown="D3DImage_MouseLeftButtonDown" MouseLeftButtonUp="D3DImage_MouseLeftButtonUp" MouseMove="D3DImage_MouseMove" Grid.RowSpan="1" Grid.Row="1" MouseWheel="D3DImage_MouseWheel">
<Image.Source>
<i:D3DImage x:Name="D3DImageSource"/>
</Image.Source>
</Image>
</Border>
</DockPanel.Children>
</DockPanel>
Any suggestions for how to set this up to render the tabs correctly would be greatly appreciated.

WPF ListBox items with template become invisible after grouping

I have ObservableCollection with items which I want to display in a ListBox.
Also I write a template for ListboxItem for correct display of my collection.
On this stage everything works fine.
in .cs
Sensors = new ObservableCollection<Sensor>();
...
lstBox.ItemsSource = Sensors;
in .xaml
...
<DataTemplate x:Key="SensorTileTemplate">
<Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" Grid.Row="0" Grid.ColumnSpan="3"></TextBlock>
<Image Source="{Binding ImageModel.ImgSource}" Style="{StaticResource ImageGlowStyle}" Height="72" Grid.Row="1" Grid.Column="0"></Image>
<StackPanel Grid.Row="1" Grid.Column="1" Margin="5">
<TextBlock Text="IP:"></TextBlock>
<TextBlock Text="Port:"></TextBlock>
<TextBlock Text="Command port:"></TextBlock>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="2" Margin="5">
<TextBlock Text="{Binding DeviceAddress}"></TextBlock>
<TextBlock Text="{Binding DeviceDataPort}"></TextBlock>
<TextBlock Text="{Binding DeviceControlPort}"></TextBlock>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
<Style x:Key="ContainerStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="ListBoxItem.Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
...
<ListBox Name="lstBox" Focusable="False"
SelectionChanged="lstBox_SelectionChanged"
HorizontalContentAlignment="Stretch"
ItemTemplate="{StaticResource SensorTileTemplate}"
ItemContainerStyle="{StaticResource ContainerStyle}">
</ListBox>
The problem appears when I need to group certain items using expander as a group container.
in .cs
...
ICollectionView view = CollectionViewSource.GetDefaultView(Sensors);
view.GroupDescriptions.Add(new PropertyGroupDescription("GroupNumber"));
lstBox.ItemsSource = view;
...
in .xaml
<!--Same Template and Style-->
...
...
<Style x:Key="GroupContainerStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Group #" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...
<ListBox Name="lstBox" Focusable="False"
SelectionChanged="lstBox_SelectionChanged"
HorizontalContentAlignment="Stretch"
ItemTemplate="{StaticResource SensorTileTemplate}"
ItemContainerStyle="{StaticResource ContainerStyle}">
<ListBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupContainerStyle}" />
</ListBox.GroupStyle>
</ListBox>
This code works and groups items but items become invisible.
So without grouping items display correctly but with grouping expanders show nothing in it.
I think there is something about ItemsPresenter in Expander but can't figure out what.
The problem was in one third party theme I use in my app. That theme has a ListBox template like:
<Style TargetType="{x:Type ListBox}">
...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Grid>
<Border x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2" Background="{DynamicResource ControlBackgroundBrush}" />
<ScrollViewer Margin="1" Style="{DynamicResource NuclearScrollViewer}" Focusable="false" Background="{x:Null}">
<StackPanel Margin="1,1,1,1" IsItemsHost="true" />
</ScrollViewer>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" Value="{DynamicResource DisabledBackgroundBrush}" TargetName="Border" />
<Setter Property="BorderBrush" Value="{DynamicResource DisabledBorderBrush}" TargetName="Border" />
</Trigger>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So I use an ItemsPresenter instead of the StackPanel in that template and everything works now.

WPF toggle buttons with custom template not accepting click in correct area

I'm a relative beginner in the WPF space so I'm sure this will be the first of many questions!
I have a series of toggle buttons all with a custom template designed to show an image with a transparent background that then becomes highlighted when the user toggles the button.
I wanted to add padding around the content so that the highlighted area would extent around the content.
This is working, but the user still has to click in the inner area to activate the button, which is not what I want.
I'm assuming it's because I'm using the Margin property of the ContentPresenter to bind to the Padding of the button and this is classed as outside of the content, but not sure of the best way to fix this.
It does work when de-selecting the button though.
Below is some XAML showing the problem which should be able to be copied and pasted straight into XamlPad.
<Page.Resources>
<Style x:Key="ValidationToggleButton" TargetType="ToggleButton">
<Setter Property="Padding" Value="5" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate >
<Grid Name="MainGrid">
<Viewbox>
<ContentPresenter Margin="{TemplateBinding Padding}"
Content="{TemplateBinding Property=Button.Content}" />
</Viewbox>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter TargetName="MainGrid" Property="Background" Value="#88FFFF55" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<Grid>
<GroupBox Grid.Column="0" Header="Validation" BorderBrush="#55BBE6" Margin="2" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ToggleButton Grid.Column="0" Style="{StaticResource ValidationToggleButton}">
CLICK
</ToggleButton>
</Grid>
</GroupBox>
</Grid>
Anyone have any idea how I might correct this?
Welcome to WPF world :). That happens because of the... background brush. If you don't set it it's null. And that means it's not visible for hit testing mechanism. Quick fix to this set Background="Transparent" to the MainGrid. But more appropriate way to set it via styles:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style x:Key="ValidationToggleButton" TargetType="ToggleButton">
<Setter Property="Padding" Value="5" />
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate >
<Grid Name="MainGrid" Background="{TemplateBinding Background}">
<Viewbox>
<ContentPresenter Margin="{TemplateBinding Padding}"
Content="{TemplateBinding Property=Button.Content}" />
</Viewbox>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter TargetName="MainGrid" Property="Background" Value="#88FFFF55" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<Grid>
<GroupBox Grid.Column="0" Header="Validation" BorderBrush="#55BBE6" Margin="2" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ToggleButton Grid.Column="0" Style="{StaticResource ValidationToggleButton}">
CLICK
</ToggleButton>
</Grid>
</GroupBox>
</Grid>
</Page>

Resources