如何在WPF选项卡控件中创build梯形选项卡
如何在WPF选项卡控件中创build梯形选项卡?
我想创build非矩形的选项卡,看起来像谷歌浏览器中的选项卡或VS 2008的代码编辑器中的标签。
可以用WPF样式完成,还是必须在代码中绘制?
互联网上有没有代码可用的例子?
编辑:
有很多示例显示了如何修改标签的边angular或改变颜色,但是我找不到任何改变标签几何体的例子,就像这两个例子:
VS 2008代码编辑器选项卡
Google Chrome标签
这两个例子中的选项卡不是矩形,而是梯形。
我试图find一些控制模板或networking上的这个问题的解决scheme,但我没有find任何“可接受”的解决scheme。 所以我用自己的方式写下来,这是我第一次(也是最后一次)尝试这样做的一个例子:
<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 } }
我写的这两个转换器将标签大小调整为其内容。 实际上,我根据内容大小制作Path对象。 如果你不需要各种宽度的标签,你可以使用这个修改后的副本:
<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>
屏幕:
示例项目(vs2010)
注意:这只是白嘴鸦伟大答案的附录。
虽然白嘴鸦的解决scheme在运行时对我来说是完美的,但在VS2010 WPFdevise器表面打开MainWindow时遇到了一些麻烦:devise者抛出exception,并没有显示窗口。 此外,TabControl.xaml中的TabItem的整个ControlTemplate都有蓝色的波浪线,工具提示告诉我发生了NullReferenceException。 将相关代码移到我的应用程序中时,我的行为也是一样的。 问题出在两台不同的机器上,所以我相信这跟我的安装没有任何关系。
如果有人遇到同样的问题,我已经find了一个修复程序,以便该示例现在在运行时和devise器中工作:
首先 :replaceTabControl-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>
…通过…
<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>
第二 :在ContentToPathConverter类的Convert方法的末尾replace…
return ps;
…通过…
PathFigure figure = new PathFigure(new Point(1, 0), ps, false); PathGeometry geometry = new PathGeometry(); geometry.Figures.Add(figure); return geometry;
我没有解释为什么在devise者中运行稳定,而不是白嘴鸦的原始代码。
我刚刚完成了一个类似于Google Chrome的Tab Control for WPF。 您可以在https://github.com/realistschuckle/wpfchrometabsfind该项目,并在;
- 谷歌浏览器像WPF选项卡控件
- ChromeTabControl和WPF中的可视化子项
- WPF Chrome标签function
希望能够帮助您更好地了解从头开始构build自定义选项卡控件。
<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>
我知道这是旧的,但我想提出:
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>
视图模型:
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; } } }
主要:
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()); } } } }
是的,你可以做到这一点 – 基本上你所要做的就是制作一个自定义的控制模板。 查看http://www.switchonthecode.com/tutorials/the-wpf-tab-control-inside-and-out了解教程。; 只要使用“wpf”“tabcontrol”“形状”打开结果页面。
我自己并没有尝试过,但是你应该能够用标签replace模板中的标签来获得你想要的形状。