WPF Flexible TabControl Header - wpf

I want to have a TabControl with multiple TabItems. These TabItems each have a header text. These texts may vary a lot in length (like 5 chars long and 15 chars long).
I want the TabControl to align the headers in one row only.
All tab headers should use the same width, and when there is enough space available, i want them the to use all the space available, up to a MaxWidth, that is the same for all items.
So if i want to use vMaxWidth` of 100 for 7 items, the tab header should be max 700 in width. If there is more space available, it should be ignored.
If there is less space available, i want that space to be distributed equally between the items. If the text gets cut off, i want to use TextWrapping.
I have tried multiple approaches to this problem now, this is my current setup:
<Style x:Key="Style-TabControl-Main" TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid KeyboardNavigation.TabNavigation="Local">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border BorderThickness="0,0,0,1" Margin="13,0,0,0" BorderBrush="{StaticResource Brush-White}">
<StackPanel Panel.ZIndex="1" x:Name="HeaderPanel" IsItemsHost="True" KeyboardNavigation.TabIndex="1" Background="Transparent"
Orientation="Horizontal"/>
</Border>
<Border x:Name="Border"
Grid.Row="1" Grid.ColumnSpan="2"
KeyboardNavigation.TabNavigation="Local"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And the TabItem Style
<Style x:Key="Style-TabItem-Main" TargetType="{x:Type TabItem}">
<Setter Property="Height" Value="31"/>
<Setter Property="Width" Value="180" />
<Setter Property="Foreground" Value="{DynamicResource Brush-BrightRegular-Foreground}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border x:Name="Border" Cursor="Hand"
Margin="2,0,0,0"
BorderThickness="1,1,1,0"
CornerRadius="4,4,0,0"
BorderBrush="{DynamicResource Brush-BrightRegular-Background}"
Background="{DynamicResource Brush-White}">
<ContentPresenter x:Name="Content" VerticalAlignment="Center" HorizontalAlignment="Stretch" ContentSource="Header" RecognizesAccessKey="True"
TextBlock.TextAlignment="Center" TextBlock.FontSize="16" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="{DynamicResource Brush-White}"/>
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Brush-DefaultDark-Background}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I am using a StackPanel instead of a TabPanel to get rid of the "stacking", that occurs, when you resize a default TabControl. However, i cannot get the rest of my requirements to work. I tried applying a MaxWidth (instead of fixed width) to the TabItem headers, but that of course doesn't work, because the item than shrinks to its minimum required size.

Step 1 (first attempt): Put headers in a single row, and give each header the same width.
This can be achieved by using a UniformGrid instead of the standard TabPanel, and lock its row count to 1. Here is a stripped-down version of your TabControl style:
<Style x:Key="Style-TabControl-Main" TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border>
<UniformGrid x:Name="HeaderPanel" IsItemsHost="True"
Rows="1" />
</Border>
<Border x:Name="Border" Grid.Row="1"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Step 2: Restrict headers to a MaxWidth and apply text wrapping.
The MaxWidth can be set in the TabItem style, along with a HeaderTemplate which wraps text (you can still use your custom ControlTemplate here to style the TabItem parts):
<Style x:Key="Style-TabItem-Main" TargetType="{x:Type TabItem}">
<Setter Property="MaxWidth" Value="100" />
<!--https://social.msdn.microsoft.com/forums/vstudio/en-US/df4f7fc3-f0ec-4ed1-a022-a32650e49cb3/how-to-wrap-header-text-in-tabcontrol-->
<Setter Property="HeaderTemplate" >
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" TextWrapping="Wrap" />
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
...
</Setter>
</Style>
Troubleshooting: Now, if you apply the MaxWidth in Step 2, you'll probably want to left-align the UniformGrid when the TabControl gets too wide..
<UniformGrid x:Name="HeaderPanel" IsItemsHost="True"
Rows="1" HorizontalAlignment="Left" />
..but you don't want that when the MaxWidth hasn't been reached yet, and the items should stretch across the entire width of the TabControl (aka Step 1). So we need a way to switch that HorizontalAlignment depending on whether the items' MaxWidth (if set) has been reached.
Step 1 (revisited): Let's try to make our own UniformGrid:
public class UniformTabPanel : UniformGrid
{
public UniformTabPanel()
{
this.IsItemsHost = true;
this.Rows = 1;
//Default, so not really needed..
this.HorizontalAlignment = HorizontalAlignment.Stretch;
}
protected override Size MeasureOverride(Size constraint)
{
var totalMaxWidth = this.Children.OfType<TabItem>().Sum(tab => tab.MaxWidth);
if (!double.IsInfinity(totalMaxWidth))
{
this.HorizontalAlignment = (constraint.Width > totalMaxWidth)
? HorizontalAlignment.Left
: HorizontalAlignment.Stretch;
}
return base.MeasureOverride(constraint);
}
}
Now, we can replace the UniformGrid in our TabControl style this new panel:
...
<Border>
<mycontrols:UniformTabPanel x:Name="HeaderPanel" />
</Border>
...
...and the TabControl should function as expeced.

Sphinxx answer is correct, however i needed to add the following code to the UniformTabPanel, to make it work like i want (resize the headers to maxwidth when enough space is available)
I added the following code to the UniformTabPanel, and it now does what i need:
protected override Size MeasureOverride(Size constraint)
{
var children = this.Children.OfType<TabItem>();
var totalMaxWidth = children.Sum(tab => tab.MaxWidth);
if (!double.IsInfinity(totalMaxWidth))
{
this.HorizontalAlignment = (constraint.Width > totalMaxWidth)
? HorizontalAlignment.Left
: HorizontalAlignment.Stretch;
foreach (var child in children)
{
child.Width = this.HorizontalAlignment == System.Windows.HorizontalAlignment.Left
? child.MaxWidth
: Double.NaN;
}
}
return base.MeasureOverride(constraint);
}

Related

GridViewHeaderRowPresenter scrolls with custom TreeListView control

I have a custom TreeListView - based on the standard TreeView - with the following style:
<Style TargetType="{x:Type c:TreeListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type: c:TreeListView}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<GridViewHeaderRowPresenter DockPanel.Dock="Top" Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}" />
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As I have no ScrollViewer, the content will not scroll so if the content requires more space than is available, it looks like this:
So I added a ScrollViewer so that the style now Looks like this:
<Style TargetType="{x:Type c:TreeListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type: c:TreeListView}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Focusable="False" Padding="{TemplateBinding Padding">
<DockPanel>
<GridViewHeaderRowPresenter DockPanel.Dock="Top" Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}" />
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</DockPanel>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
With the content expanded as in the first Image, the Content is not able to scroll, but the column Header scrools as well, which is not wished for :D
Could anyone please help me so that only the content gets scrolled, and not the columns bound to the GridViewHeaderRowPresenter?
Thank you!
[EDIT]
Thanks to the related links to the right, I found the answer here.
Instead of having the DockPanel within the ScrollViewer I needed only to have the ItemsPresenter inside the ScrollViewer. So with the following Style it now works.
<Style TargetType="{x:Type c:TreeListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:TreeListView}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<GridViewHeaderRowPresenter DockPanel.Dock="Top" Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}" />
<ScrollViewer Focusable="False" Padding="{TemplateBinding Padding}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Many thanks to Nikhil Agrawal for his code!
The solution given is incomplete and will only work if you have a fixed width control. By only wrapping the ItemsPresenter the header columns will not scroll horizontally if needed. This results in shifted value columns which then mismatch the headers.
I've tried applying SelectiveScrollingGrid.SelectiveScrollingOrientation="Horizontal" to GridViewHeaderRowPresenter but haven't been successfull with that approach.
Instead, you could add a scroll viewer to the GridViewHeaderRowPresenter and to the ItemsPresenter and then synchronize them. It's not the best solution, but at least it works.
<Style TargetType="{x:Type local:TreeListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TreeListView}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<ScrollViewer DockPanel.Dock="Top"
HorizontalScrollBarVisibility="Hidden"
Name="scrollViewerHeader"
VerticalScrollBarVisibility="Disabled">
<GridViewHeaderRowPresenter Columns="{StaticResource gvcc}"/>
</ScrollViewer>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
Name="scrollViewerBody"
VerticalScrollBarVisibility="Auto">
<ItemsPresenter />
</ScrollViewer>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
And the code behind:
public class TreeListView : TreeView
{
private ScrollViewer _scrollViewerHeader;
private ScrollViewer _scrollViewerBody;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_scrollViewerHeader = (ScrollViewer)Template.FindName("scrollViewerHeader", this);
_scrollViewerBody = (ScrollViewer)Template.FindName("scrollViewerBody", this);
_scrollViewerBody.ScrollChanged += (sender, e) =>
{
_scrollViewerHeader.Width = e.ViewportWidth;
_scrollViewerHeader.ScrollToHorizontalOffset(e.HorizontalOffset);
};
}
}
The way DataGrid solves it is it customizes the Template of the ScrollViewer to place the GridViewHeaderRowPresenter outside the ScrollViewer ScrollContentPresenter:
https://github.com/dotnet/wpf/blob/059ba38771aef10d2172a3ca0e247dfbfa8cefde/src/Microsoft.DotNet.Wpf/src/Themes/PresentationFramework.Aero2/Themes/Aero2.NormalColor.xaml#L987-L993
This way when the scrollbars scroll, they don't impact the header row.
The ListView that uses GridView does a similar thing here:
https://github.com/dotnet/wpf/blob/059ba38771aef10d2172a3ca0e247dfbfa8cefde/src/Microsoft.DotNet.Wpf/src/Themes/PresentationFramework.Aero2/Themes/Aero2.NormalColor.xaml#L2628
At the same time they wrap the header row in another ScrollViewer, and honestly I don't understand why.
Once I find out more I will expand on this answer. I think this approach if it works is cleaner than synchronizing scroll viewers.

wpf expander template: No ItemsPresenter present when created in Collapsed state

Here's in short a template I use for a wpf TreeViewItem:
<Style x:Key="MyExpanderStyle" TargetType="Expander">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<Border CornerRadius="2" BorderBrush="Gray" BorderThickness="1">
<Grid SnapsToDevicePixels="True">
<Grid.RowDefinitions>
<RowDefinition Name="ExpHeaderRow" Height="20"/>
<RowDefinition Name="ExpContentRow" Height="*"/>
</Grid.RowDefinitions>
<HeaderedContentControl Grid.Row="0" Header="{Binding}" HeaderTemplateSelector="{StaticResource MyHeaderTemplateSelector}"/>
<ContentPresenter x:Name="ExpandSite" Grid.Row="1" Visibility="Collapsed" Margin="{TemplateBinding Padding}">
</ContentPresenter>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
the style is used in another style applied to a TreeViewItem:
<Style x:Key="tviStyle" TargetType="TreeViewItem"}
...
<Expander Style="{StaticResource MyExpanderStyle}" IsExpanded="{Binding Path=IsExpanded}">
<Expander.Content>
<ItemsPresenter/> <!-- is null when not yet expanded !! -->
</Expander.Content>
</Expander>
...
now in code, i use this:
ItemsPresenter itemsPresenter = itemsControl.FindVisualDescendant<ItemsPresenter>();
which gives me the itemsPresenter, when I have set IsExpanded = true in the viewmodel constructor.
It gives me null, when IsExpanded = False at construction.
So wpf creates UIElements at the point the Expanders state goes to "Expanded". That is ok.
But there are cases when Items get created in a collapsed state, and I want to access the data that is bound on the Expanders Content (the itemsPresenter). The viewmodels have been created, all data is present, except the wpf UIElements.
There are clumsy ways like going Expanded = true, in the viewmodel constructor, then set Expanded = false (e.g. in OnLoaded)
Is there a nicer way? Thank you!

How to set GroupStyle for custom control

I have created a custom control library project and did the followings:
The custom control is derived from ComboBox
Add a resource dictionary file rd.xaml under Themes folder
Define some styles in the rd.xaml file
<Style x:Key="GroupComboBoxStyle" TargetType="{x:Type local:GroupComboBox}">
<Setter Property="ItemContainerStyle" >
<Setter.Value>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsEnabled" Value="{Binding Available}"/>
</Style>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:GroupComboBox}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" Orientation="Horizontal" Width="150" Height="Auto" >
<!-- add scroll bar -->
</WrapPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Item}" Width="40"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<CollectionViewSource x:Key="groupedData" Source="{Binding Items}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Category"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="groupComboBoxItemStyle" TargetType="{x:Type ComboBoxItem}">
<Setter Property="Width" Value="50" />
</Style>
<GroupStyle x:Key="groupStyle">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="2">
<TextBlock Text="{Binding Name}" HorizontalAlignment="Stretch" Background="YellowGreen"/>
</Border>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
Then I want to set the group style to my custom control at runtime
But the groupstyle cannot be found, how to get it from the resource dictionary file?
public GroupComboBox()
{
GroupStyle style = new GroupStyle();
// get the groupstyle
style.HeaderTemplate = (DataTemplate)this.FindResource("groupStyle");
this.GroupStyle.Add(style);
}
A WPF CustomControl is supposed to be lookless. This means, that the code should only contain the control's logic but nothing related to how it looks, styling, etc. This should all be done using the style which is created for you in the Generic.xaml.
Anyway, it is totally valid to wish for a green background in your header... I would suggest to create a bindable dependency property for a DefaultGroupStyle in your control. I've implemented and tested it and it does the trick:
The control GroupComboBox:
public class GroupComboBox : ComboBox
{
public static readonly DependencyProperty DefaultGroupStyleProperty =
DependencyProperty.Register("DefaultGroupStyle", typeof (GroupStyle), typeof (GroupComboBox), new PropertyMetadata(default(GroupStyle), OnDefaultGroupStyleChanged));
private static void OnDefaultGroupStyleChanged(DependencyObject s, DependencyPropertyChangedEventArgs a)
{
var c = (GroupComboBox) s;
if (a.NewValue == null) return;
if (c.GroupStyle.Count == 0)
c.GroupStyle.Add((GroupStyle) a.NewValue);
}
public GroupStyle DefaultGroupStyle
{
get { return (GroupStyle) GetValue(DefaultGroupStyleProperty); }
set { SetValue(DefaultGroupStyleProperty, value); }
}
static GroupComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(GroupComboBox), new FrameworkPropertyMetadata(typeof(GroupComboBox)));
}
}
and the styles in Generic.xaml (feel free to move the styles to another file but don't forget to merge it into Generic.xaml. Note that I removed the key on the default style for the ComboBox. It won't get applied automatically otherwise...
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1">
<GroupStyle x:Key="GroupStyle">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="2">
<TextBlock Text="{Binding Name}" HorizontalAlignment="Stretch" Background="YellowGreen"/>
</Border>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
<Style TargetType="{x:Type local:GroupComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:GroupComboBox}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="DefaultGroupStyle" Value="{StaticResource GroupStyle}" />
</Style>
</ResourceDictionary>
Please let me know if this works for you and feel free to ask, if there's anything unclear.

WPF multi-line TabControl without rearranging rows

The WPF TabControl with its default TabPanel arranges tab items into multiple rows when the horizontal size is too small. Then the tab selection changes the order of these rows, so the selected tab item is always in the first row.
I found several articles on how to replace TabPanel with another items control so instead of the multiline behavior they get scrolling tabs.
I would like to keep the multiple rows (no scrolling), but disable the rearrangement of rows. Once the tabs are created, they should stay in position, no matter how the selection changes. Is this possible?
have you tried overriding the default style with something like this? ie: using a wrappanel instead of a TabPanel?
<Style x:Key="{x:Type TabControl}" TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid TabNavigation="Local" SnapsToDevicePixels="true" ClipToBounds="true">
<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>
<WrapPanel Name="HeaderPanel" ZIndex="1" TabIndex="1" Column="0" Row="0" Margin="2,2,2,0" IsItemsHost="true" />
<Border Name="ContentPanel" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" TabNavigation="Local" DirectionalNavigation="Contained" TabIndex="2" Column="0" Row="1">
<ContentPresenter Name="PART_SelectedContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Margin="{TemplateBinding Padding}" ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The only solution I found was modifying the framework's TabPanel class so that its int GetActiveRow(int[] solution) method always returns 0. Although this solves the problem, I'm not sure it is legal to use the framework's source in this way.
I think vigoo's answer is incomplete. int GetActiveRow(int[] solution) is a private method for the TabPanel class inside of the System.Windows.Controls.Primitives namespace. So you must create a copy of the TabPanel class from the WPF source code (which is available at WPF source github and place it in a new namespace.
// Returns the row which contain the child with IsSelected==true
private int GetActiveRow(int[] solution)
{
// Prevent Tabs from re-ordering when selecting a tab
return 0;
/*int activeRow = 0;
int childIndex = 0;
if (solution.Length > 0)
{
foreach (UIElement child in InternalChildren)
{
if (child.Visibility == Visibility.Collapsed)
continue;
bool isActiveTab = (bool)child.GetValue(Selector.IsSelectedProperty);
if (isActiveTab)
{
return activeRow;
}
if (activeRow < solution.Length && solution[activeRow] == childIndex)
{
activeRow++;
}
childIndex++;
}
}
// If the is no selected element and aligment is Top - then the active row is the last row
if (TabStripPlacement == Dock.Top)
{
activeRow = _numRows - 1;
}
return activeRow;*/
}
Then create a template for the TabControl and reference your new TabPanel in the Template (I called it overrides:TabPanel here). Don't forget to add a reference to the new TabPanel class in your ResourceDictionary (or wherever you defined the new style) xmlns:overrides="clr-namespace:MyNamespace.WPF.Overrides".
<Style x:Key="staticTabs"
TargetType="{x:Type TabControl}">
<Setter Property="Padding" Value="2" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Background" Value="{StaticResource TabItem.Selected.Background}" />
<Setter Property="BorderBrush" Value="{StaticResource TabItem.Selected.Border}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid x:Name="templateRoot"
ClipToBounds="true"
KeyboardNavigation.TabNavigation="Local"
SnapsToDevicePixels="true">
<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>
<overrides:TabPanel x:Name="headerPanel"
Grid.Row="0"
Grid.Column="0"
Margin="2,2,2,0"
Panel.ZIndex="1"
Background="Transparent"
Grid.IsSharedSizeScope="True"
IsItemsHost="true"
KeyboardNavigation.TabIndex="1" />
<Border x:Name="contentPanel"
Grid.Row="1"
Grid.Column="0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2"
KeyboardNavigation.TabNavigation="Local">
<ContentPresenter x:Name="PART_SelectedContentHost"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Finally your Tab control will reference this new Template:
<TabControl Style="{StaticResource staticTabs}" />

WPF: How can I remove the searchbox in a DocumentViewer?

My XAML code is like this:
<Window
xmlns ='http://schemas.microsoft.com/netfx/2007/xaml/presentation'
xmlns:x ='http://schemas.microsoft.com/winfx/2006/xaml'
Title ='Print Preview - More stuff here'
Height ='200'
Width ='300'
WindowStartupLocation ='CenterOwner'>
<DocumentViewer Name='dv1' ... />
</Window>
How can I, in XAML or in C#, eliminate the search box?
You can do something similar to Cheeso's answer with a style for ContentControl and a trigger to hide it when the name is PART_FindToolBarHost.
<DocumentViewer>
<DocumentViewer.Resources>
<Style TargetType="ContentControl">
<Style.Triggers>
<Trigger Property="Name" Value="PART_FindToolBarHost">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</DocumentViewer.Resources>
</DocumentViewer>
Vlad's answer led me to look at how to programmatically grab the ContentControl that holds the find toolbar. I didn't really want to write an entirely new template for the DocumentViewer; I wanted to change (hide) only one control. That reduced the problem to how to retrieve a control that is applied via a template?.
Here's what I figured out:
Window window = ... ;
DocumentViewer dv1 = LogicalTreeHelper.FindLogicalNode(window, "dv1") as DocumentViewer;
ContentControl cc = dv1.Template.FindName("PART_FindToolBarHost", dv1) as ContentControl;
cc.Visibility = Visibility.Collapsed;
As Vlad pointed out you can replace the control template. Unfortunately, the control template available on MSDN is not the real control template used by the DocumentViewer control. Here is the correct template modified to hide the search bar by setting Visibility="Collapsed" on PART_FindToolBarHost:
<!-- DocumentViewer style with hidden search bar. -->
<Style TargetType="{x:Type DocumentViewer}" xmlns:Documents="clr-namespace:System.Windows.Documents;assembly=PresentationUI">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="ContextMenu" Value="{DynamicResource {ComponentResourceKey ResourceId=PUIDocumentViewerContextMenu, TypeInTargetAssembly={x:Type Documents:PresentationUIStyleResources}}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DocumentViewer}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Focusable="False">
<Grid Background="{TemplateBinding Background}" KeyboardNavigation.TabNavigation="Local">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ContentControl Grid.Column="0" Focusable="{TemplateBinding Focusable}" Grid.Row="0" Style="{DynamicResource {ComponentResourceKey ResourceId=PUIDocumentViewerToolBarStyleKey, TypeInTargetAssembly={x:Type Documents:PresentationUIStyleResources}}}" TabIndex="0"/>
<ScrollViewer x:Name="PART_ContentHost" CanContentScroll="true" Grid.Column="0" Focusable="{TemplateBinding Focusable}" HorizontalScrollBarVisibility="Auto" IsTabStop="true" Grid.Row="1" TabIndex="1"/>
<DockPanel Grid.Row="1">
<FrameworkElement DockPanel.Dock="Right" Width="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"/>
<Rectangle Height="10" Visibility="Visible" VerticalAlignment="top">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#66000000" Offset="0"/>
<GradientStop Color="Transparent" Offset="1"/>
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</DockPanel>
<ContentControl x:Name="PART_FindToolBarHost" Grid.Column="0" Focusable="{TemplateBinding Focusable}" Grid.Row="2" TabIndex="2" Visibility="Collapsed"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You need to add a reference to PresentationUI.dll. This assembly is located in the folder %WINDIR%\Microsoft.NET\Framework\v4.0.30319\WPF.
You can replace a control template for it. For your reference: the default DocumentViewer's control template is here: http://msdn.microsoft.com/en-us/library/aa970452.aspx
The search toolbar's name is PART_FindToolBarHost, so you can also just assign its Visibility to Collapsed.
Edit:
As the comment from #Martin suggests, the control template in MSDN (referenced above) is not fully correct. A better way to extract a template which is actually used in WPF by default would be using Blend (Edit Control Template in the context menu, if I am not mistaken).
In order to get Cheeso's answer to work in the constructor I had to add:
dv1.ApplyTemplate();
otherwise cc comes out null. See the answer here
<DocumentViewer>
<DocumentViewer.Resources>
<!-- Toolbar -->
<Style TargetType="ToolBar">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
<!-- Search -->
<Style TargetType="ContentControl">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</DocumentViewer.Resources>
</DocumentViewer>
Are you sure you need a DocumentViewer? You could use a FlowDocumentScrollViewer instead, or if you like pagination or multi-column display, you could use a FlowDocumentPageViewer.

Resources