How to get TabPanel ActualWidth from TabControl - wpf

I have a tabcontrol and tab strip placement is on left. So it is a simple navigation menu type tab control.
There is a TabPanel inside TabControl's control template.
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid x:Name="Grid" ClipToBounds="true" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnDefinition0"/>
<ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDefinition0" Height="Auto"/>
<RowDefinition x:Name="RowDefinition1" Height="*"/>
</Grid.RowDefinitions>
<Border x:Name="ContentPanel" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="0" KeyboardNavigation.DirectionalNavigation="Contained" Grid.Row="1" KeyboardNavigation.TabIndex="2" KeyboardNavigation.TabNavigation="Local" Padding="{TemplateBinding Padding}" Margin="0,-1,0,0">
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
<ScrollViewer x:Name="scroll" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto" Template="{StaticResource ScrollViewerTab}">
<TabPanel x:Name="HeaderPanel" IsItemsHost="true" Margin="0,2,0,0" KeyboardNavigation.TabIndex="1" Panel.ZIndex="1" VerticalAlignment="Bottom" />
</ScrollViewer>
</Grid>
</ControlTemplate>
I need TabPanel's (tab headers) ActualWidth outside tabcontrol for alignment purposes.
I tried following but it did not work.
<TextBlock Text="{Binding Path=Actualwidth, ElementName=HeaderPanel}" />
<TabControl />
I also tried to bind the ActualWidth of header panel to Tag property of TabControl. Even that did not work.
<TabControl Tag="{Binding Path=ActualWidth, ElementName=HeaderPanel}" />
TabControls.Items collection gives the list of ViewModel objects. So I cannot get TabItem instances.

I ended up using visual tree helper in the codebehind file
var tabPanel = VisualTreeHelpers.FindChild<TabPanel>(TabControl, "MenuPanel");

Related

XAML: How to bind TabControl nested element to Attached Property of TabItem

I have been searching for a "pure" XAML solution for this problem but just cannot find it.
My goal would be to only create an Attached Property in code behind but the rest should be XAML only without creating a Custom Control or User Control. But I'm not sure whether this is possible at all and if, how to make the connection between a nested element inside the TabControl template and an Attached Property set in a TabItem
I'd have a boilerplate Attached Property of string with [AttachedPropertyBrowsableForType(typeof(TabItem))] and/or [AttachedPropertyBrowsableForType(typeof(TabControl))] inside my MainWindow class and the following XAML
<Window x:Class="AP_Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:AP_Test"
mc:Ignorable="d"
Title="MainWindow" Height="400" Width="800">
<Window.Resources>
<Style TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid Name="templateRoot" ClipToBounds="true" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local">
<Grid.ColumnDefinitions>
<ColumnDefinition Name="ColumnDefinition0"/>
<ColumnDefinition Name="ColumnDefinition1" Width="0"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Name="RowDefinition0" Height="Auto"/>
<RowDefinition Name="RowDefinition1" Height="*"/>
</Grid.RowDefinitions>
<TabPanel Name="headerPanel"
Background="Transparent"
Grid.Column="0"
IsItemsHost="true"
Margin="2,2,2,0"
Grid.Row="0"
KeyboardNavigation.TabIndex="1"
Panel.ZIndex="1"/>
<Border Name="contentPanel"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Grid.Column="0"
KeyboardNavigation.DirectionalNavigation="Contained"
Grid.Row="1"
KeyboardNavigation.TabIndex="2"
KeyboardNavigation.TabNavigation="Local">
<DockPanel Background="White">
<Grid Name="TabControlHeader" DockPanel.Dock="Top" Height="65">
<Label x:Name="SelectedItemTitle" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="24" Content="How to bind to AP ItemTitle?"/>
</Grid>
<Grid Name="Detail" Margin="8,0,8,8">
<Border BorderThickness="3,3,0,0" BorderBrush="DarkGray" CornerRadius="3"/>
<Border BorderThickness="2,2,1,1" BorderBrush="LightGray" CornerRadius="3"/>
<Border BorderThickness="1,1,1,1" BorderBrush="White" CornerRadius="3" Margin="3,3,-1,-1" Padding="5">
<Viewbox>
<ContentPresenter Name="PART_SelectedContentHost"
ContentSource="SelectedContent"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Viewbox>
</Border>
</Grid>
</DockPanel>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<TabControl x:Name="TabCtl">
<TabItem Header="Tab1" local:MainWindow.ItemTitle="Tab1 Title" />
<TabItem Header="Tab2" local:MainWindow.ItemTitle="Tab2 Title" />
<TabItem Header="Tab3" local:MainWindow.ItemTitle="Tab3 Title" />
</TabControl>
</Window>
I'd like the respective title entries to be displayed in the TabControl's SelectedItemTitle label.
Any hints appreciated, even a definitive "That's not possible" would be good to know, so I can stop trying 😁
The property (sub-)path for an attached property needs to be enclosed in parentheses:
Content="{Binding Path=SelectedItem.(local:MainWindow.ItemTitle),
RelativeSource={RelativeSource AncestorType=TabControl}}"
See PropertyPath for Objects in Data Binding for details.
An attached property is not even required. You could as well use the TabItem's Tag property like
<TabItem Header="Tab1" Tag="Tab1 Title"/>
with
Content="{Binding Path=SelectedItem.Tag,
RelativeSource={RelativeSource AncestorType=TabControl}}"

How do I remove the Mouse Over Effect on WPF Ribbon Group?

I'm working on this Ribbon Control in WPF: System.Windows.Controls.Ribbon.Ribbon
I've made the background orange and I've changed the style slightly. It looks like this:
When I move the cursor over a group it looks like this:
I want to remove the white mouse over / hover effect, but I don't know which Style or Template I should look at. I've tried all these:
Ribbon
RibbonTab
RibbonTabHeader
RibbonButton
RibbonGroup
Is it possible? How do I do it?
So it is inside the style of the RibbonGroup:
<!--Ribbon Group - Style-->
<Style TargetType="RibbonGroup">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Name="ribbonGroupControlTemplate" TargetType="RibbonGroup" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Border Background="{TemplateBinding Panel.Background}" Name="GroupBorder" Margin="1,2,0,0">
<Grid Name="MainGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" MinHeight="16" />
</Grid.RowDefinitions>
<Border BorderThickness="1,1,1,1" CornerRadius="2,2,2,2" BorderBrush="{TemplateBinding RibbonControlService.MouseOverBorderBrush}" Background="{TemplateBinding RibbonControlService.MouseOverBackground}" Name="PART_HotBackground" Opacity="0" SnapsToDevicePixels="True" Grid.RowSpan="3" />
<Border Background="{TemplateBinding Border.BorderBrush}" Name="SeparatorBorder" Width="1" Height="75" VerticalAlignment="Center" SnapsToDevicePixels="True" Grid.Column="1" Grid.RowSpan="3" />
<Border Padding="3,0,3,0" Margin="2,1,2,0">
<Grid>
<ItemsPresenter Name="ItemsPresenter" />
<ContentControl Name="PART_TemplateContentControl" Visibility="Collapsed" Focusable="False" />
</Grid>
...
The culprit is this Border:
<Border BorderThickness="1,1,1,1" CornerRadius="2,2,2,2" BorderBrush="{TemplateBinding RibbonControlService.MouseOverBorderBrush}" Background="{TemplateBinding RibbonControlService.MouseOverBackground}" Name="PART_HotBackground" Opacity="0" SnapsToDevicePixels="True" Grid.RowSpan="3" />
When I comment out or remove that border and the triggers that go with it; it solves my problem.

Pinned item inside silverlight scrollviewer

Is there a way to have a control inside silverlight scrollviewer that does not scroll? For example I would like the similar behavior as you get when pinning a column in a datagrid so that I can have a footer to my listbox but still have it fit inside the scroll bars (looks better that way). here is a screen shot of what I have. The control I want inside is at the bottom.
Per request here is my control template for the listbox. I have a template for scrollviewer to but could not find a way to tell it to leave the border at bottom when scrolling everything else. If you look at the template I have the border outside of the scrollviewer to keep it from being scrolled but would like it to fit inside the scrollbar because I think it looks better.
<ControlTemplate TargetType="telerik:RadListBox">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border BorderThickness="0" Background="{StaticResource ListBoxBackground}" Grid.RowSpan="2">
</Border>
<ScrollViewer x:Name="PART_ScrollViewer"
Margin="0"
IsTabStop="False"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
TabNavigation="{TemplateBinding TabNavigation}"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Style="{StaticResource ScrollViewerStyle}">
<ItemsPresenter/>
</ScrollViewer>
<Border Grid.Row="1" Background="Transparent" BorderBrush="Tan" BorderThickness="1" Height="50">
<TextBlock Text="Dont scroll but keep in scrollviewer" TextWrapping="Wrap"/>
</Border>
<ContentPresenter x:Name="dragVisualPlaceholder" Visibility="Collapsed" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</Grid>
</ControlTemplate>

want to make scrollable tabs for a tabcontrol

Say I have a tab control, and I have over 50 tabs, where there is no enough space to hold so many tabs, how make these tabs scrollable?
Rick's answer actually breaks the vertical stretching of content inside the tabcontrol. It can be improved to retain vertical stretching by using a two row grid instead of a StackPanel.
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Hidden" >
<TabPanel x:Name="HeaderPanel"
Panel.ZIndex ="1"
KeyboardNavigation.TabIndex="1"
Grid.Column="0"
Grid.Row="0"
Margin="2,2,2,0"
IsItemsHost="true"/>
</ScrollViewer>
<ContentPresenter x:Name="PART_SelectedContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent" Grid.Row="1"/>
</Grid>
</ControlTemplate>
</TabControl.Template>
Override the TabControl ControlTemplate and add a ScrollViewer around the TabPanel like this sample:
<Grid>
<TabControl>
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Disabled">
<TabPanel x:Name="HeaderPanel"
Panel.ZIndex ="1"
KeyboardNavigation.TabIndex="1"
Grid.Column="0"
Grid.Row="0"
Margin="2,2,2,0"
IsItemsHost="true"/>
</ScrollViewer>
<ContentPresenter x:Name="PART_SelectedContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent"/>
</StackPanel>
</ControlTemplate>
</TabControl.Template>
<TabItem Header="TabItem1">TabItem1 Content</TabItem>
<TabItem Header="TabItem2">TabItem2 Content</TabItem>
<TabItem Header="TabItem3">TabItem3 Content</TabItem>
<TabItem Header="TabItem4">TabItem4 Content</TabItem>
<TabItem Header="TabItem5">TabItem5 Content</TabItem>
<TabItem Header="TabItem6">TabItem6 Content</TabItem>
<TabItem Header="TabItem7">TabItem7 Content</TabItem>
<TabItem Header="TabItem8">TabItem8 Content</TabItem>
<TabItem Header="TabItem9">TabItem9 Content</TabItem>
<TabItem Header="TabItem10">TabItem10 Content</TabItem>
</TabControl>
</Grid>
which gives this result:
Recently I've implemented such control. It contains two buttons (to scroll left and right) which switch their IsEnabled and Visibility states when it is necessary. Also it works perfectly with item selection: if you select a half-visible item, it will scroll to display it fully.
It looks so:
It isn't so much different from the default control, the scrolling is appeared automatically:
<tab:ScrollableTabControl ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
IsAddItemEnabled="False"
.../>
I've written the article about this ScrollableTabControl class in my blog here.
Source code you can find here: WpfScrollableTabControl.zip
The above solution is great for tab items with the tab control's "TabStripPlacement" property set to "Top". But if you are looking to have your tab items, say to the left side, then you will need to change a few things.
Here is a sample of how to get the scrollviewer to work with the TabStripPlacement to the Left:
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ScrollViewer
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
FlowDirection="RightToLeft">
<TabPanel
x:Name="HeaderPanel"
Panel.ZIndex ="0"
KeyboardNavigation.TabIndex="1"
IsItemsHost="true"
/>
</ScrollViewer>
<ContentPresenter
x:Name="PART_SelectedContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
ContentSource="SelectedContent" Grid.Column="1"
/>
</Grid>
</ControlTemplate>
Note that in the ScrollViewer I set FlowDirection="RightToLeft" so that the scroll bar would snap to the left of the tab items. If you are placing your tab items to the right the you will need to remove the FlowDirection property so that it defaults to the right side.
And here is the result:
For thoose who want to know how to make the scrollviewer scroll to the selected tab item.
Add this event SelectionChanged="TabControl_SelectionChanged" to your TabControl.
Then give a name like TabControlScroller to the ScrollViewer inside the template. You should end with something like this
<TabControl SelectionChanged="TabControl_SelectionChanged">
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ScrollViewer x:Name="TabControlScroller" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" >
<TabPanel x:Name="HeaderPanel"
Panel.ZIndex ="1"
KeyboardNavigation.TabIndex="1"
Grid.Column="0"
Grid.Row="0"
Margin="2,2,2,0"
IsItemsHost="true"/>
</ScrollViewer>
<ContentPresenter x:Name="PART_SelectedContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent" Grid.Row="1"/>
</Grid>
</ControlTemplate>
</TabControl.Template>
<!-- Your Tabitems-->
</TabControl>
Then in code behind you just have to add this method :
private void TabControl_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
TabControl tabControl = (TabControl)sender;
ScrollViewer scroller = (ScrollViewer)tabControl.Template.FindName("TabControlScroller", tabControl);
if (scroller != null)
{
double index = (double)(tabControl.SelectedIndex );
double offset = index * (scroller.ScrollableWidth / (double)(tabControl.Items.Count));
scroller.ScrollToHorizontalOffset(offset);
}
}
Place it inside a ScrollViewer.
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Hidden">
<TabControl ...>
...
</TabControl>
</ScrollViewer>

How do i make a WPF tab control's tab area smaller than the control?

I have a scenario where i have two buttons in the top right of my application, and i have a tab control that spans the entire application's screen. (Basically the two buttons are on the same horizontal line as the tabs in the tab control). The problem is that when i have multiple tabs open, the buttons and the tabs overlap. I don't want to have to specify the grid row/column numbers so that the buttons are above the tabs.
Is there any way to specify to the Tab control a certain area that it has to open tab controls before it automatically starts a second row of tabs?
In other words, if my tab control has a width of X, how can i tell the area that displays the actual tabs that it should only occupy say, x-15 amount of space.
Thanks!
You'll have to supply the ControlTemplate for the TabControl to do that. Here is an example where 100 pixels is reserved to the right of the header panel:
<Grid>
<TabControl>
<TabControl.Template>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid KeyboardNavigation.TabNavigation="Local"
SnapsToDevicePixels="true"
ClipToBounds="true">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnDefinition0"/>
<ColumnDefinition x:Name="ColumnDefinition1"
Width="100"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDefinition0"
Height="Auto"/>
<RowDefinition x:Name="RowDefinition1"
Height="*"/>
</Grid.RowDefinitions>
<TabPanel x:Name="HeaderPanel"
Panel.ZIndex ="1"
KeyboardNavigation.TabIndex="1"
Grid.Column="0"
Grid.Row="0"
Margin="2,2,2,0"
IsItemsHost="true"/>
<Border x:Name="ContentPanel"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
KeyboardNavigation.TabNavigation="Local"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="2">
<ContentPresenter x:Name="PART_SelectedContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent"/>
</Border>
</Grid>
</ControlTemplate>
</TabControl.Template>
<TabItem Header="Item1"/>
<TabItem Header="Item2"/>
<TabItem Header="Item3"/>
<TabItem Header="Item4"/>
</TabControl>
</Grid>
Resize the window to see it in action.

Resources