ejemplos wpf xaml tabcontrol

ejemplos - Cómo crear pestañas trapezoidales en el control de pestañas WPF



tabcontrol c# ejemplos (6)

¿Cómo crear pestañas trapezoidales en el control de pestañas WPF?
Me gustaría crear pestañas no rectangulares que se vean como pestañas en Google Chrome o como pestañas en el editor de código de VS 2008.

¿Se puede hacer con estilos de WPF o se debe dibujar en código?

¿Hay algún ejemplo de código disponible en internet?

Editar:

Hay muchos ejemplos que muestran cómo redondear esquinas o cambiar colores de pestañas, pero no pude encontrar ninguno que cambie la geometría de las pestañas como estos dos ejemplos:

Fichas del editor de código VS 2008


Pestañas de Google Chrome

Las pestañas de estos dos ejemplos no son rectángulos, sino trapecios.



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()); } } } }


Sí, puedes hacerlo; básicamente, todo lo que tienes que hacer es crear una plantilla de control personalizada. Consulte http://www.switchonthecode.com/tutorials/the-wpf-tab-control-inside-and-out para obtener un tutorial. Simplemente buscando en Google "wpf" "tabcontrol" "shape" aparece la página de resultados.

No he probado esto por mí mismo, pero debería poder reemplazar las etiquetas en la plantilla con etiquetas para obtener la forma que desea.


Traté de 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> <!-- TabControl style --> <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> <!-- TabItem 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 } }

Estos dos conversores que escribí para ajustar el tamaño de la pestaña a su contenido. En realidad, creo el objeto Path dependiendo del tamaño del contenido. Si no necesita pestañas con varios anchos, puede usar alguna 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)


Nota: Este es solo un apéndice de la gran respuesta de los rooks.

Mientras que la solución de rooks estaba funcionando 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 lanzó excepciones y no mostró la ventana. Además, todo el ControlTemplate para TabItem en TabControl.xaml tenía una línea azul ondulada y una información sobre herramientas me decía que se había producido 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, he encontrado una solución para que el ejemplo funcione ahora en tiempo de ejecución y en el diseñador también:

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 : Reemplace al final del método Convertir de la clase ContentToPathConverter ...

return ps;

... por ...

PathFigure figure = new PathFigure(new Point(1, 0), ps, false); PathGeometry geometry = new PathGeometry(); geometry.Figures.Add(figure); return geometry;

No tengo ninguna explicación de por qué esto funciona estable en el diseñador pero no en el código original de rooks.


<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>