Как создать трапециевидные выступы на вкладке элемент управления WPF
Как создать трапециевидные вкладки в элементе управления вкладками WPF?
Я хотел бы создать не прямоугольные вкладки, которые выглядят как вкладки в Google Chrome или как вкладки в редакторе кода VS 2008.
можно ли это сделать с помощью стилей WPF или он должен быть нарисован в коде?
есть ли какой-либо пример кода, доступный в интернете?
Edit:
есть много примеров, которые показывают, как закруглить углы или изменить цвет вкладок, но я не мог найти любой что изменяет геометрию вкладки, как это два примера:
VS 2008 редактор кода вкладки
вкладки Google Chrome
вкладки в этих двух примерах не прямоугольники, а трапеции.
6 ответов:
Я попытался найти некоторые шаблоны управления или решения для этой проблемы в интернете, но я не нашел никакого "приемлемого" решения для меня. Поэтому я написал это по-своему, и вот пример моей первой (и последней=)) попытки сделать это:
<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 } }эти два конвертера я написал, чтобы настроить размер вкладки на ее содержание. На самом деле, я делаю путь объекта в зависимости от размера контента. Если вам не нужны вкладки с различной шириной, вы можете использовать некоторые измененные копии это:
<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 WPF: дизайнер бросил исключения и не отобразил окно. Также весь ControlTemplate для TabItem в TabControl.xaml имел синюю линию закорючки, и подсказка сообщала мне, что произошло исключение NullReferenceException. У меня было такое же поведение при движении соответствующий код в моем приложении. Проблемы были на двух разных машинах, поэтому я считаю, что это не было связано с какими-либо проблемами моей установки.
в случае, если кто-то испытывает те же проблемы, я нашел исправление, так что пример теперь работает во время выполнения и в конструкторе:
первый: заменить в коде 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>... от...
<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 ...
return ps;... от...
PathFigure figure = new PathFigure(new Point(1, 0), ps, false); PathGeometry geometry = new PathGeometry(); geometry.Figures.Add(figure); return geometry;у меня нет объяснения, почему это работает стабильно в конструкторе, но не исходный код ладьи.
Я только что закончил управление вкладками Google Chrome для WPF. Вы можете найти проект по адресу https://github.com/realistschuckle/wpfchrometabs и сообщения в блоге, описывающие его в
- Google Chrome-как WPF Tab Control
- ChromeTabControl и визуальные дети в WPF
- WPF Chrome вкладки функционируют
надеюсь, что это поможет вам получить лучшее понимание построения пользовательские вкладки управления с нуля.
<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>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; } } }главное:
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 для учебника. Просто погуглите "МОФ" "управления TabControl" "форма" появляется на страницах результатов.
Я не пробовал это сам, но вы должны быть в состоянии заменить тег(ы) в шаблоне с тегами, чтобы получить нужную форму.


Comments