Intenté encontrar algunas plantillas de control o soluciones para este problema en Internet, pero no encontré ninguna solución "aceptable" para mí. Así que lo escribí a mi manera y aquí hay un ejemplo de mi primer (y último =)) intento de hacerlo:
<Window x:Class="TabControlTemplate.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:TabControlTemplate"
Title="Window1" Width="600" Height="400">
<Window.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#FF3164a5" Offset="1"/>
<GradientStop Color="#FF8AAED4" Offset="0"/>
</LinearGradientBrush>
</Window.Background>
<Window.Resources>
<src:ContentToPathConverter x:Key="content2PathConverter"/>
<src:ContentToMarginConverter x:Key="content2MarginConverter"/>
<SolidColorBrush x:Key="BorderBrush" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="HoverBrush" Color="#FFFF4500"/>
<LinearGradientBrush x:Key="TabControlBackgroundBrush" EndPoint="0.5,0" StartPoint="0.5,1">
<GradientStop Color="#FFa9cde7" Offset="0"/>
<GradientStop Color="#FFe7f4fc" Offset="0.3"/>
<GradientStop Color="#FFf2fafd" Offset="0.85"/>
<GradientStop Color="#FFe4f6fa" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="TabItemPathBrush" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FF3164a5" Offset="0"/>
<GradientStop Color="#FFe4f6fa" Offset="1"/>
</LinearGradientBrush>
<Style x:Key="TabControlStyle" TargetType="{x:Type TabControl}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="1" BorderThickness="2,0,2,2" Panel.ZIndex="2" CornerRadius="0,0,2,2"
BorderBrush="{StaticResource BorderBrush}"
Background="{StaticResource TabControlBackgroundBrush}">
<ContentPresenter ContentSource="SelectedContent"/>
</Border>
<StackPanel Orientation="Horizontal" Grid.Row="0" Panel.ZIndex="1" IsItemsHost="true"/>
<Rectangle Grid.Row="0" Height="2" VerticalAlignment="Bottom"
Fill="{StaticResource BorderBrush}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Type TabItem}" TargetType="{x:Type TabItem}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Grid x:Name="grd">
<Path x:Name="TabPath" StrokeThickness="2"
Margin="{Binding ElementName=TabItemContent, Converter={StaticResource content2MarginConverter}}"
Stroke="{StaticResource BorderBrush}"
Fill="{StaticResource TabItemPathBrush}">
<Path.Data>
<PathGeometry>
<PathFigure IsClosed="False" StartPoint="1,0"
Segments="{Binding ElementName=TabItemContent, Converter={StaticResource content2PathConverter}}">
</PathFigure>
</PathGeometry>
</Path.Data>
<Path.LayoutTransform>
<ScaleTransform ScaleY="-1"/>
</Path.LayoutTransform>
</Path>
<Rectangle x:Name="TabItemTopBorder" Height="2" Visibility="Visible"
VerticalAlignment="Bottom" Fill="{StaticResource BorderBrush}"
Margin="{Binding ElementName=TabItemContent, Converter={StaticResource content2MarginConverter}}" />
<ContentPresenter x:Name="TabItemContent" ContentSource="Header"
Margin="10,2,10,2" VerticalAlignment="Center"
TextElement.Foreground="#FF000000"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True" SourceName="grd">
<Setter Property="Stroke" Value="{StaticResource HoverBrush}" TargetName="TabPath"/>
</Trigger>
<Trigger Property="Selector.IsSelected" Value="True">
<Setter Property="Fill" TargetName="TabPath">
<Setter.Value>
<SolidColorBrush Color="#FFe4f6fa"/>
</Setter.Value>
</Setter>
<Setter Property="BitmapEffect">
<Setter.Value>
<DropShadowBitmapEffect Direction="302" Opacity="0.4"
ShadowDepth="2" Softness="0.5"/>
</Setter.Value>
</Setter>
<Setter Property="Panel.ZIndex" Value="2"/>
<Setter Property="Visibility" Value="Hidden" TargetName="TabItemTopBorder"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Margin="20">
<TabControl Grid.Row="0" Grid.Column="1" Margin="5" TabStripPlacement="Top"
Style="{StaticResource TabControlStyle}" FontSize="16">
<TabItem Header="MainTab">
<Border Margin="10">
<TextBlock Text="The quick brown fox jumps over the lazy dog."/>
</Border>
</TabItem>
<TabItem Header="VeryVeryLongTab" />
<TabItem Header="Tab" />
</TabControl>
</Grid>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace TabControlTemplate
{
public partial class Window1
{
public Window1()
{
InitializeComponent();
}
}
public class ContentToMarginConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new Thickness(0, 0, -((ContentPresenter)value).ActualHeight, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
public class ContentToPathConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var ps = new PathSegmentCollection(4);
ContentPresenter cp = (ContentPresenter)value;
double h = cp.ActualHeight > 10 ? 1.4 * cp.ActualHeight : 10;
double w = cp.ActualWidth > 10 ? 1.25 * cp.ActualWidth : 10;
ps.Add(new LineSegment(new Point(1, 0.7 * h), true));
ps.Add(new BezierSegment(new Point(1, 0.9 * h), new Point(0.1 * h, h), new Point(0.3 * h, h), true));
ps.Add(new LineSegment(new Point(w, h), true));
ps.Add(new BezierSegment(new Point(w + 0.6 * h, h), new Point(w + h, 0), new Point(w + h * 1.3, 0), true));
return ps;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
Escribí estos dos convertidores para ajustar el tamaño de la pestaña a su contenido. En realidad, hago un objeto Path según el tamaño del contenido. Si no necesita pestañas con varios anchos, puede usar una copia modificada de esto:
<Style x:Key="tabPath" TargetType="{x:Type Path}">
<Setter Property="Stroke" Value="Black"/>
<Setter Property="Data">
<Setter.Value>
<PathGeometry Figures="M 0,0 L 0,14 C 0,18 2,20 6,20 L 60,20 C 70,20 80,0 84,0"/>
</Setter.Value>
</Setter>
</Style>
pantalla:
proyecto de muestra (vs2010)
Path.Data
elemento en el XAML. Cuando comento este elemento, el diseñador es estable. ¿Tiene alguna idea de cómo se podría solucionar este problema?Nota: Este es solo un apéndice de la gran respuesta de las torres.
Si bien la solución de rooks funcionaba perfectamente en tiempo de ejecución para mí, tuve algunos problemas al abrir MainWindow en la superficie del diseñador VS2010 WPF: el diseñador arrojó excepciones y no mostró la ventana. Además, todo el ControlTemplate para TabItem en TabControl.xaml tenía una línea ondulada azul y una información sobre herramientas me decía que se produjo una NullReferenceException. Tuve el mismo comportamiento al mover el código relevante a mi aplicación. Los problemas estaban en dos máquinas diferentes, así que creo que no estaba relacionado con ningún problema de mi instalación.
En caso de que alguien experimente los mismos problemas, encontré una solución para que el ejemplo funcione ahora en tiempo de ejecución y también en el diseñador:
Primero : Reemplazar en el código TabControl-XAML ...
<Path x:Name="TabPath" StrokeThickness="2" Margin="{Binding ElementName=TabItemContent, Converter={StaticResource content2MarginConverter}}" Stroke="{StaticResource BorderBrush}" Fill="{StaticResource TabItemPathBrush}"> <Path.Data> <PathGeometry> <PathFigure IsClosed="False" StartPoint="1,0" Segments="{Binding ElementName=TabItemContent, Converter={StaticResource content2PathConverter}}"> </PathFigure> </PathGeometry> </Path.Data> <Path.LayoutTransform> <ScaleTransform ScaleY="-1"/> </Path.LayoutTransform> </Path>
... por ...
<Path x:Name="TabPath" StrokeThickness="2" Margin="{Binding ElementName=TabItemContent, Converter={StaticResource content2MarginConverter}}" Stroke="{StaticResource BorderBrush}" Fill="{StaticResource TabItemPathBrush}" Data="{Binding ElementName=TabItemContent, Converter={StaticResource content2PathConverter}}"> <Path.LayoutTransform> <ScaleTransform ScaleY="-1"/> </Path.LayoutTransform> </Path>
Segundo : Reemplazar al final del método Convert de la clase ContentToPathConverter ...
... por ...
No tengo una explicación de por qué esto funciona de forma estable en el diseñador pero no en el código original de rooks.
fuente
Acabo de terminar un control de pestañas similar a Google Chrome para WPF. Puede encontrar el proyecto en https://github.com/realistschuckle/wpfchrometabs y publicaciones de blog que lo describen en
Espero que le ayude a comprender mejor cómo crear un control de pestaña personalizado desde cero.
fuente
<Grid> <Grid.Resources> <Style TargetType="{x:Type TabControl}"> <Setter Property="ItemContainerStyle"> <Setter.Value> <Style> <Setter Property="Control.Height" Value="20"></Setter> <Setter Property="Control.Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TabItem}"> <Grid Margin="0 0 -10 0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="10"> </ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition Width="10"></ColumnDefinition> </Grid.ColumnDefinitions> <Path Data="M10 0 L 0 20 L 10 20 " Fill="{TemplateBinding Background}" Stroke="Black"></Path> <Rectangle Fill="{TemplateBinding Background}" Grid.Column="1"></Rectangle> <Rectangle VerticalAlignment="Top" Height="1" Fill="Black" Grid.Column="1"></Rectangle> <Rectangle VerticalAlignment="Bottom" Height="1" Fill="Black" Grid.Column="1"></Rectangle> <ContentPresenter Grid.Column="1" ContentSource="Header" /> <Path Data="M0 20 L 10 20 L0 0" Fill="{TemplateBinding Background}" Grid.Column="2" Stroke="Black"></Path> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsSelected" Value="True"> <Trigger.Setters> <Setter Property="Background" Value="Beige"></Setter> <Setter Property="Panel.ZIndex" Value="1"></Setter> </Trigger.Setters> </Trigger> <Trigger Property="IsSelected" Value="False"> <Trigger.Setters> <Setter Property="Background" Value="LightGray"></Setter> </Trigger.Setters> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </Setter.Value> </Setter> </Style> </Grid.Resources> <TabControl> <TabItem Header="One" ></TabItem> <TabItem Header="Two" ></TabItem> <TabItem Header="Three" ></TabItem> </TabControl> </Grid>
fuente
Sé que esto es antiguo pero me gustaría proponer:
XAML:
<Window.Resources> <ControlTemplate x:Key="trapezoidTab" TargetType="TabItem"> <Grid> <Polygon Name="Polygon_Part" Points="{Binding TabPolygonPoints}" /> <ContentPresenter Name="TabContent_Part" Margin="{TemplateBinding Margin}" Panel.ZIndex="100" ContentSource="Header" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="False"> <Setter TargetName="Polygon_Part" Property="Stroke" Value="LightGray"/> <Setter TargetName="Polygon_Part" Property="Fill" Value="DimGray" /> </Trigger> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="Polygon_Part" Property="Fill" Value="Goldenrod" /> <Setter TargetName="Polygon_Part" Property="Stroke" Value="LightGray"/> </Trigger> <Trigger Property="IsSelected" Value="False"> <Setter Property="Panel.ZIndex" Value="90"/> </Trigger> <Trigger Property="IsSelected" Value="True"> <Setter Property="Panel.ZIndex" Value="100"/> <Setter TargetName="Polygon_Part" Property="Stroke" Value="LightGray"/> <Setter TargetName="Polygon_Part" Property="Fill" Value="LightSlateGray "/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Window.Resources> <!-- Test the tabs--> <TabControl Name="FruitTab"> <TabItem Header="Apple" Template="{StaticResource trapezoidTab}" /> <TabItem Margin="-8,0,0,0" Header="Grapefruit" Template="{StaticResource trapezoidTab}" /> <TabItem Margin="-16,0,0,0" Header="Pear" Template="{StaticResource trapezoidTab}"/> </TabControl>
ViewModel:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Shapes; using System.ComponentModel; using System.Globalization; using System.Windows.Media; namespace TrapezoidTab { public class TabHeaderViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _tabHeaderText; private List<Point> _polygonPoints; private PointCollection _pointCollection; public TabHeaderViewModel(string tabHeaderText) { _tabHeaderText = tabHeaderText; TabPolygonPoints = GenPolygon(); } public PointCollection TabPolygonPoints { get { return _pointCollection; } set { _pointCollection = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("TabPolygonPoints")); } } public string TabHeaderText { get { return _tabHeaderText; } set { _tabHeaderText = value; TabPolygonPoints = GenPolygon(); if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("TabHeaderText")); } } private PointCollection GenPolygon() { var w = new FormattedText(_tabHeaderText, CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, new Typeface("Tahoma"), 12, Brushes.Black); var width = w.Width + 30; _polygonPoints = new List<Point>(4); _pointCollection = new PointCollection(4); _polygonPoints.Add(new Point(2, 21)); _polygonPoints.Add(new Point(10, 2)); _polygonPoints.Add(new Point(width, 2)); _polygonPoints.Add(new Point(width + 8, 21)); foreach (var point in _polygonPoints) _pointCollection.Add(point); return _pointCollection; } } }
Principal:
namespace TrapezoidTab { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); foreach (var obj in FruitTab.Items) { var tab = obj as TabItem; if (tab == null) continue; tab.DataContext = new TabHeaderViewModel(tab.Header.ToString()); } } } }
fuente
sí, puedes hacer eso, básicamente todo lo que tienes que hacer es crear una plantilla de control personalizada. Visite http://www.switchonthecode.com/tutorials/the-wpf-tab-control-inside-and-out para ver un tutorial. Con solo buscar en Google "wpf" "tabcontrol" "shape" se muestran páginas de resultados.
No lo he probado yo mismo, pero debería poder reemplazar la (s) etiqueta (s) en la plantilla con etiquetas para obtener la forma que desea.
fuente
Para inclinar los bordes de las pestañas izquierdo y derecho , aquí hay una modificación de la mejora de Slauma a la respuesta aceptada de la torre . Este es un reemplazo del método Convert de la clase ContentToPathConverter:
También en ControlTemplate de TabControl, agregue márgenes izquierdos (y opcionalmente derechos) al contenedor de los elementos de la pestaña (se agrega el único cambio
Margin="20,0,20,0"
):<Style x:Key="TabControlStyle" TargetType="{x:Type TabControl}"> ... <Setter Property="Template"> ... <StackPanel Grid.Row="0" Panel.ZIndex="1" Orientation="Horizontal" IsItemsHost="true" Margin="20,0,20,0"/>
Problema: hay un pequeño "error" visual en la parte inferior del borde izquierdo de la pestaña, cuando no es la pestaña seleccionada. Creo que está relacionado con "retroceder" para comenzar antes del comienzo del área de pestañas. O bien relacionado con el dibujo de la línea en la parte inferior de la pestaña (que no sabe que comenzamos antes del borde izquierdo del rectángulo "normal" de las pestañas).
fuente