2009-02-18 35 views
72

¿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 pestañas similares en el editor de código de VS 2008.Cómo crear pestañas trapezoidales en el control de pestañas WPF

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

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

Editar:

Hay un montón de ejemplos que muestran cómo redondear las esquinas o cambiar los colores de las pestañas, pero no pude encontrar ninguna que cambia la geometría de la pestaña, como estos dos ejemplos:

VS fichas Editor de código 2008 aquí
VS 2008 Code Editor Tabs


Google Chrome
alt text

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

Respuesta

98

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 escribí en mi camino y aquí está un ejemplo de mi primer (y último =)) tratan 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 convertidores que escribí para ajustar el tamaño de la pestaña de su contenido. En realidad, creo el objeto Path dependiendo del tamaño del contenido. Si usted no necesita pestañas con diferentes anchos, se puede utilizar una cierta copia modificada de este:

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

screenshot

sample project(vs2010)

+2

@rooks: ¡Gran ejemplo, gracias! Funciona en tiempo de ejecución para mí, pero tengo problemas en el diseñador de WPF en VS2010. El diseñador se bloquea con una NullReferenceException que parece ser causada por el elemento 'Path.Data' en el XAML. Cuando comento este elemento, el diseñador es estable. ¿Tienes alguna pista de cómo se puede solucionar este problema? – Slauma

+2

@rooks: Mientras tanto, he encontrado una solución para que tu ejemplo funcione también en la vista del diseñador de VS2010: mira mi respuesta en este hilo. – Slauma

+0

Guau, esto es exactamente lo que acabo de pasar la última hora tratando de lograr. Deja el diseño para los diseñadores que digo! +1 y 100 más si pudiera. –

4

sí, puede hacerlo; básicamente, todo lo que tiene 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 de la plantilla con etiquetas para obtener la forma que desea.

+0

Vi ese ejemplo y algo más. Todos usan esquinas redondeadas y aplican un poco de color a las pestañas, pero todavía son pestañas rectangulares. Quiero trapecio. – zendar

+0

debería ser capaz de cambiar las etiquetas a etiquetas –

+0

Sí, pero aún así, no habrá superposición como en el ejemplo de Chrome. ¿Alguna idea sobre cómo hacer eso? –

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

Nota: Esto es sólo una apéndice a la gran respuesta de los grajos.

Mientras la solución de rooks funcionaba perfectamente en tiempo de ejecución, 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 experimenta los mismos problemas que he encontrado una solución para que el ejemplo funciona ahora en tiempo de ejecución y en el diseñador así:

Primera: Reemplazar en el código XAML-TabControl .. .

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

Segunda: Re lugar al final del método Convert 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 por qué esto funciona estable en el diseñador, pero no grajos 'código original.

+2

+1, además útil. –

5

Sé que esto es viejo, pero me gustaría proponer:

enter image description here

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> 

modelo de vista:

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