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 replac开发者_如何转开发e 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}" />
精彩评论