background
Thanks to @kankankan of WeChat [Avalonia Development and Exchange Group] for providing the code example:


The following picture shows the effect modified according to personalized requirements:

为了兼容Semi.Avalonia主题风格,我们的TabControl控件主题从参考Semi的Card风格控件主题开始,Semi的效果如下:

After we modified it, when switching themes, the display effect is as follows:

use
It is recommended to copy the control code in this article for self-maintenance. This control may not be updated in a timely manner.
This control is secondary developed based on Semi, so the following NuGet packages need to be installed:
Install-Package Semi.Avalonia -Version 11.2.1.8
Install-Package CodeWF.AvaloniaControls -Version 0.1.1.6
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="CodeWF.AvaloniaControls.Demo.App"
xmlns:semi="https://irihi.tech/semi"
xmlns:codewf="https://codewf.com">
<Application.Styles>
<semi:SemiTheme Locale="zh-CN" />
<codewf:CodeWFTheme />
</Application.Styles>
</Application>
Using reference, the effect has been shown above, and the code is as follows:
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="CodeWF.AvaloniaControls.Demo.Pages.TabControlDemo">
<Grid RowDefinitions="20 Auto 20 Auto" ColumnDefinitions="20 * 20">
<TabControl Grid.Row="1" Grid.Column="1" VerticalAlignment="Top"
Theme="{StaticResource TrapezoidShapedTabControl}"
CornerRadius="10 10 0 0" TabStripPlacement="Top">
<TabControl.Styles>
<Style Selector="TabItem">
<Setter Property="CornerRadius" Value="10 10 0 0" />
<Setter Property="Padding" Value="12 8" />
</Style>
</TabControl.Styles>
<TabItem Header="数据管理" />
<TabItem Header="系统设置" />
<TabItem Header="用户中心" />
<TabItem Header="日志记录" />
<TabItem Header="帮助文档" />
</TabControl>
<TabControl Grid.Row="3" Grid.Column="1" VerticalAlignment="Top"
Theme="{StaticResource TrapezoidShapedTabControl}"
CornerRadius="10 10 0 0" TabStripPlacement="Top">
<TabControl.Styles>
<Style Selector="TabControl">
<Setter Property="Background" Value="#551890FF"></Setter>
</Style>
<Style Selector="TabItem">
<Setter Property="CornerRadius" Value="10 10 0 0" />
<Setter Property="Foreground" Value="#FFFFFF" />
<Setter Property="Padding" Value="12 8" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="50%, 0%"
EndPoint="50%, 100%">
<GradientStops>
<GradientStop Color="#BAE7FF" Offset="0" />
<GradientStop Color="#FFFFFF" Offset="1" />
</GradientStops>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
</TabControl.Styles>
<TabControl.Resources>
<SolidColorBrush x:Key="TabItemLineHeaderPointeroverForeground">#1890FF</SolidColorBrush>
<SolidColorBrush x:Key="TabItemLineHeaderSelectedForeground">#1890FF</SolidColorBrush>
</TabControl.Resources>
<TabItem Header="数据管理" />
<TabItem Header="系统设置" />
<TabItem Header="用户中心" />
<TabItem Header="日志记录" />
<TabItem Header="帮助文档" />
</TabControl>
</Grid>
</UserControl>
achieve
Talking about code is always boring, let's give a rough idea.
This is the ControlTheme code for Semi's TabControl:

We mainly modify the border style of TabItem, so we directly copy and paste this code from Semi, change the Key to ControlTheme, and then point the Value of the box selected part of ItemContainerTheme in the figure to another TabItem control theme. Other parts of the code are modified as needed. I haven't touched other code. The following picture is a screenshot of the modified code:

The control theme code of TabItem is as follows, and the key code is the custom border code location:

Among them, TrapezoidShapedTabItemBorder inherits from Control, mainly rewriting its Render method:
public partial class TrapezoidShapedTabItemBorder : Control
{
public const double DiagonalFilletRatio = 0.8;
public static readonly StyledProperty<IBrush> BorderBrushProperty =
AvaloniaProperty.Register<TrapezoidShapedTabItemBorder, IBrush>(nameof(BorderBrush),
new SolidColorBrush(Color.Parse("#05CCCCCC")));
public static readonly StyledProperty<double> BorderThicknessProperty =
AvaloniaProperty.Register<TrapezoidShapedTabItemBorder, double>(nameof(BorderThickness), 1);
public static readonly StyledProperty<IBrush> BackgroundProperty =
AvaloniaProperty.Register<TrapezoidShapedTabItemBorder, IBrush>(nameof(Background), Brushes.DarkGreen);
public IBrush BorderBrush
{
get => GetValue(BorderBrushProperty);
set => SetValue(BorderBrushProperty, value);
}
public double BorderThickness
{
get => GetValue(BorderThicknessProperty);
set => SetValue(BorderThicknessProperty, value);
}
public IBrush Background
{
get => GetValue(BackgroundProperty);
set => SetValue(BackgroundProperty, value);
}
public override void Render(DrawingContext context)
{
base.Render(context);
if (BorderThickness < 1)
{
return;
}
if (Parent?.Parent?.Parent is not TabControl tabControl ||
Parent?.Parent is not TabItem currentTabItem)
{
return;
}
var index = tabControl.Items.IndexOf(currentTabItem);
var isFirst = index == 0;
var isLast = index == tabControl.Items.Count - 1;
var radius = currentTabItem.CornerRadius;
// 获取控件的尺寸
var rect = new Rect(Bounds.Size);
var borderThickness = BorderThickness;
// 偏移路径使线条对齐像素网格
var halfBorder = borderThickness / 2.0;
var adjustedRect = rect.Deflate(halfBorder);
// 设置边框路径
var pathGeometry = new StreamGeometry();
using (var ctx = pathGeometry.Open())
{
if (isFirst & !isLast)
{
if (tabControl.TabStripPlacement == Dock.Top)
{
DrawTopFirstTabItemBorder(ctx, adjustedRect, radius, rect);
}
}
else if (!isFirst && isLast)
{
if (tabControl.TabStripPlacement == Dock.Top)
{
DrawTopLastTabItemBorder(ctx, adjustedRect, radius, rect);
}
}
else
{
if (tabControl.TabStripPlacement == Dock.Top)
{
DrawTopOtherTabItemBorder(ctx, adjustedRect, radius, rect);
}
}
// 底边消失(不绘制)
// 这里直接跳过底边路径,确保底边消失
ctx.EndFigure(isClosed: true);
}
// 绘制边框
context.DrawGeometry(Background, new Pen(BorderBrush, BorderThickness)
{
Thickness = BorderThickness,
LineJoin = PenLineJoin.Round, // 圆角连接
LineCap = PenLineCap.Round // 圆角端点
}, pathGeometry);
}
}
In Render, according to which the current TabItem is the first, last, and middle part of TabControl, different methods are called to draw the border, such as drawing the first TabItem. The effect diagram is as follows:

First analyze:
- This is a right-angled trapezoid
- The left line is a straight line
- The upper left corner is a quarter-inner circle
- There is another inner circle in the upper right corner (can be drawn to scale)
- The right line is a diagonal line with a slope
- Lower left corner and lower right corner can have outer arcs
The border drawing code is as follows:
private static void DrawTopFirstTabItemBorder(StreamGeometryContext ctx, Rect adjustedRect, CornerRadius radius,
Rect rect)
{
var x = adjustedRect.Left;
var y = adjustedRect.Bottom;
// 左下角开始
ctx.BeginFigure(new Point(x, y), isFilled: true);
// 左下角外圆
if (radius.BottomLeft > 0)
{
x = rect.Left + radius.BottomLeft;
y = adjustedRect.Bottom - radius.BottomLeft;
ctx.ArcTo(
new Point(x, y),
new Size(radius.BottomLeft, radius.BottomLeft),
0,
false,
SweepDirection.CounterClockwise);
}
// 左边直线
y = adjustedRect.Top + radius.TopLeft;
ctx.LineTo(new Point(x, y));
// 左上角内圆角
if (radius.TopLeft > 0)
{
x += radius.TopLeft;
y = adjustedRect.Top;
ctx.ArcTo(
new Point(x, y),
new Size(radius.TopLeft, radius.TopLeft),
0,
false,
SweepDirection.Clockwise);
}
// 上边直线
x = adjustedRect.Right - radius.TopRight * 2 - radius.BottomRight * 2;
ctx.LineTo(new Point(x, y));
// 右上角内圆角
if (radius.TopRight > 0)
{
x += radius.TopRight;
y += radius.TopRight * DiagonalFilletRatio;
ctx.ArcTo(
new Point(x, y),
new Size(radius.TopRight, radius.TopRight),
0,
false,
SweepDirection.Clockwise);
}
// 右边斜线
x = adjustedRect.Right - radius.BottomRight;
y = adjustedRect.Bottom - radius.BottomRight;
ctx.LineTo(new Point(x, y));
// 右下角外圆
if (radius.BottomRight > 0)
{
x = rect.Right;
y = adjustedRect.Bottom;
ctx.ArcTo(
new Point(x, y),
new Size(radius.BottomRight, radius.BottomRight),
0,
false,
SweepDirection.CounterClockwise);
}
}
Use StreamGeometryContext to call the LineTo method to draw straight lines, and call the ArcTo method to draw arcs (inner circles and outer circles). Various styles of borders can be drawn. The drawing methods for the last TabItem and the TabItem in the middle are similar.
Is the TabItem effect of Edge easy to achieve?

summary
This article is just a rough idea. You can look at the specific implementation code and draw inferences from one example. Other control effects can be implemented in a similar way.
Warehouse:
CodeWF.AvaloniaControls:https://github.com/dotnet9/CodeWF.AvaloniaControls
CodeF.ToolBox:https://github.com/dotnet9/CodeWF.Toolbox