返回提交历史
Modified
XFEToolBox.Client.Wpf.Test/Program.cs
+210
-0
Added
XFEToolBox.WpfCore/Controls/UniformRoundedBorder.cs
+101
-0
Modified
XFEToolBox.WpfCore/Resources/Style/StandardControlsStyle.xaml
+37
-22
Modified
XFEToolBox/Profiles/CrossVersionProfiles/ConsoleProfile.cs
+2
-2
Modified
XFEToolBox/Views/Pages/SettingPage.xaml
+1
-1
Modified
XfeTestArtifacts/test-results.json
+17
-6
Modified
XfeTestArtifacts/test-results.xml
+3
-2
XFEstudio/XFEToolBox
优化TabView与NavigationView圆角边框一致性
本次优化统一了WPF TabView 和 NavigationView 控件的圆角边框渲染与裁剪逻辑,提升视觉一致性。新增 UniformRoundedBorder 控件,解决原生 Border 圆角像素差异问题,并适配缩放与 DPI 变化。重构相关样式,调整边距、边框层级与内容对齐方式,确保选中状态下边框完整显示。NavigationView 采用双层 UniformRoundedBorder 实现裁剪与描边分离,导航项区域整体内缩避免边界裁剪。补充自动化测试,验证控件边框、圆角、内容对齐及滚动裁剪的像素级一致性。修正“第二调试模式”文案为“远程调试模式”,并更新测试结果文件。
a7a3fc3
代码差异
7 个文件
+371
-33
@@ -4,15 +4,169 @@ using System.Windows;
4
4
using System.Windows.Controls;
5
5
using System.Windows.Interop;
6
6
using System.Windows.Media;
7
using System.Windows.Media.Imaging;
7
8
using System.Windows.Shell;
8
9
using System.Windows.Threading;
9
10
using XFEToolBox.Client.Utilities;
11
using XFEToolBox.WpfCore.Controls;
10
12
using XFEToolBox.WpfCore.Windowing;
11
13
12
14
namespace XFEToolBox.Client.Wpf.Test;
13
15
14
16
public class Program
15
17
{
18
[Test]
19
public static void TabAndNavigationOutlinesStayInsideTheirLayoutBounds()
20
{
21
Exception? failure = null;
22
var thread = new Thread(() =>
23
{
24
try
25
{
26
var dispatcher = Dispatcher.CurrentDispatcher;
27
var tabView = new TabView { Width = 420, Height = 210, SelectedIndex = 0 };
28
tabView.Items.Add(new TabItem { Header = "概览", Content = new TextBlock { Text = "概览内容" } });
29
tabView.Items.Add(new TabItem { Header = "日志", Content = new TextBlock { Text = "日志内容" } });
30
tabView.Items.Add(new TabItem { Header = "设置", Content = new TextBlock { Text = "设置内容" } });
31
32
var navigationView = new NavigationView
33
{
34
Width = 420,
35
Height = 230,
36
NavigationWidth = new GridLength(160),
37
SelectedIndex = 0
38
};
39
navigationView.Items.Add(new TabItem { Header = "常规", Content = new TextBlock { Text = "常规设置" } });
40
navigationView.Items.Add(new TabItem { Header = "网络", Content = new TextBlock { Text = "网络设置" } });
41
navigationView.Items.Add(new TabItem { Header = "高级", Content = new TextBlock { Text = "高级设置" } });
42
navigationView.Items.Add(new TabItem { Header = "外观", Content = new TextBlock { Text = "外观设置" } });
43
navigationView.Items.Add(new TabItem { Header = "通知", Content = new TextBlock { Text = "通知设置" } });
44
navigationView.Items.Add(new TabItem { Header = "隐私", Content = new TextBlock { Text = "隐私设置" } });
45
46
var panel = new StackPanel();
47
panel.Children.Add(tabView);
48
panel.Children.Add(navigationView);
49
var window = new Window
50
{
51
Width = 480,
52
Height = 500,
53
Left = -10_000,
54
Top = -10_000,
55
ShowInTaskbar = false,
56
WindowStartupLocation = WindowStartupLocation.Manual,
57
Content = panel
58
};
59
window.Resources.MergedDictionaries.Add(new ResourceDictionary
60
{
61
Source = new Uri("/XFEToolBox.WpfCore;component/Resources/Style/ToolThemeResources.xaml", UriKind.Relative)
62
});
63
window.Show();
64
PumpDispatcher(dispatcher);
65
66
for (var index = 0; index < tabView.Items.Count; index++)
67
{
68
tabView.SelectedIndex = index;
69
PumpDispatcher(dispatcher);
70
window.UpdateLayout();
71
72
var item = (TabItem)tabView.Items[index];
73
item.ApplyTemplate();
74
var outline = (Border?)item.Template.FindName("SelectedOutline", item);
75
var headerContent = (ContentPresenter?)item.Template.FindName("HeaderContent", item);
76
var connector = (Border?)item.Template.FindName("ContentConnector", item);
77
78
Ensure(outline is { Visibility: Visibility.Visible, ActualWidth: >= 1 }, $"第 {index + 1} 个页签轮廓没有显示。");
79
Ensure(outline!.BorderThickness == new Thickness(1, 1, 1, 0), $"第 {index + 1} 个页签轮廓缺少侧边框。");
80
Ensure(connector is { Visibility: Visibility.Visible }, $"第 {index + 1} 个页签没有与内容面板连接。");
81
82
var itemRight = item.TranslatePoint(new Point(item.ActualWidth, 0), window).X;
83
var edgeRight = outline!.TranslatePoint(new Point(outline.ActualWidth, 0), window).X;
84
Ensure(edgeRight <= itemRight - 4,
85
$"第 {index + 1} 个页签右边框仍位于可裁剪边界:{edgeRight:N2} >= {itemRight:N2}。");
86
Ensure(headerContent is not null, $"第 {index + 1} 个页签缺少标题内容。");
87
var outlineLeft = outline.TranslatePoint(new Point(0, 0), window).X;
88
var headerLeft = headerContent!.TranslatePoint(new Point(0, 0), window).X;
89
var outlineCenter = outlineLeft + outline.ActualWidth / 2;
90
var headerCenter = headerLeft + headerContent.ActualWidth / 2;
91
Ensure(Math.Abs(outlineCenter - headerCenter) <= 0.5,
92
$"第 {index + 1} 个页签标题没有居中:{headerCenter:N2} != {outlineCenter:N2}。");
93
}
94
95
tabView.SelectedIndex = 0;
96
PumpDispatcher(dispatcher);
97
window.UpdateLayout();
98
var firstItem = (TabItem)tabView.Items[0];
99
var firstOutline = (Border?)firstItem.Template.FindName("SelectedOutline", firstItem);
100
var contentFrame = (Border?)tabView.Template.FindName("ContentFrame", tabView);
101
Ensure(firstOutline is not null && contentFrame is not null, "TabView 缺少用于对齐的轮廓或内容面板。");
102
var firstOutlineLeft = firstOutline!.TranslatePoint(new Point(0, 0), window).X;
103
var contentFrameLeft = contentFrame!.TranslatePoint(new Point(0, 0), window).X;
104
Ensure(Math.Abs(firstOutlineLeft - contentFrameLeft) <= 0.5,
105
$"首个页签与内容面板左侧没有对齐:{firstOutlineLeft:N2} != {contentFrameLeft:N2}。");
106
107
navigationView.ApplyTemplate();
108
window.UpdateLayout();
109
var navigationFrameHost = (Grid?)navigationView.Template.FindName("NavigationFrameHost", navigationView);
110
var navigationClip = (UniformRoundedBorder?)navigationView.Template.FindName("NavigationClip", navigationView);
111
var navigationFrame = (Border?)navigationView.Template.FindName("NavigationFrame", navigationView);
112
Ensure(navigationFrameHost is not null && navigationClip is not null && navigationFrame is not null,
113
"NavigationView 缺少独立的圆角裁剪层或描边层。");
114
Ensure(navigationFrame!.ActualWidth > 0 && navigationFrame.ActualHeight > 0, "NavigationView 导航边框没有完成布局。");
115
Ensure(navigationFrame.BorderThickness == new Thickness(1), "NavigationView 四侧边框厚度不完整。");
116
Ensure(navigationFrame.CornerRadius == new CornerRadius(15), "NavigationView 四角没有保持统一圆角。");
117
Ensure(navigationFrameHost!.Margin == new Thickness(2), "NavigationView 边框没有保留防裁剪间距。");
118
Ensure(navigationClip!.CornerRadius == navigationFrame.CornerRadius,
119
"NavigationView 裁剪层与描边层的圆角半径不一致。");
120
Ensure(Math.Abs(navigationClip.ActualWidth - navigationFrame.ActualWidth) <= 0.5 &&
121
Math.Abs(navigationClip.ActualHeight - navigationFrame.ActualHeight) <= 0.5,
122
"NavigationView 裁剪层与描边层的尺寸不一致。");
123
Ensure(navigationFrame is UniformRoundedBorder && navigationClip.Clip is RectangleGeometry,
124
"NavigationView 没有使用圆角裁剪,内部内容可能覆盖下半部圆角。");
125
EnsureVerticallyMirroredCorners(navigationFrame, 18);
126
for (var index = 0; index < navigationView.Items.Count; index++)
127
{
128
var item = (TabItem)navigationView.Items[index];
129
item.ApplyTemplate();
130
var surface = (Border?)item.Template.FindName("Surface", item);
131
var headerContent = (ContentPresenter?)item.Template.FindName("HeaderContent", item);
132
Ensure(surface is not null && headerContent is not null, $"第 {index + 1} 个导航项缺少表面或标题内容。");
133
var surfaceLeft = surface!.TranslatePoint(new Point(0, 0), item).X;
134
var headerLeft = headerContent!.TranslatePoint(new Point(0, 0), item).X;
135
var surfaceCenter = surfaceLeft + surface.ActualWidth / 2;
136
var headerCenter = headerLeft + headerContent.ActualWidth / 2;
137
Ensure(Math.Abs(surfaceCenter - headerCenter) <= 0.5,
138
$"第 {index + 1} 个导航项标题没有居中:{headerCenter:N2} != {surfaceCenter:N2}。");
139
}
140
var navigationScroller = (ScrollViewer?)navigationView.Template.FindName("NavigationScroller", navigationView);
141
Ensure(navigationScroller is not null, "NavigationView 缺少独立导航滚动区域。");
142
navigationScroller!.ScrollToEnd();
143
PumpDispatcher(dispatcher);
144
window.UpdateLayout();
145
Ensure(navigationScroller.VerticalOffset > 0, "NavigationView 滚动状态没有被覆盖测试。");
146
Ensure(navigationClip.Clip is RectangleGeometry, "NavigationView 滚动后丢失圆角裁剪。");
147
148
window.Close();
149
dispatcher.InvokeShutdown();
150
}
151
catch (Exception exception)
152
{
153
failure = exception;
154
}
155
})
156
{
157
IsBackground = true
158
};
159
160
thread.SetApartmentState(ApartmentState.STA);
161
thread.Start();
162
Ensure(thread.Join(TimeSpan.FromSeconds(15)), "页签与导航边框测试超时。");
163
if (failure is not null)
164
{
165
Console.WriteLine(failure);
166
throw new InvalidOperationException("页签或导航边框可能再次被裁剪。", failure);
167
}
168
}
169
16
170
[Test]
17
171
public static void FramelessMaximizedWindowStaysInsideMonitorWorkArea()
18
172
{
@@ -211,6 +365,62 @@ public class Program
211
365
Dispatcher.PushFrame(frame);
212
366
}
213
367
368
private static void EnsureVerticallyMirroredCorners(FrameworkElement element, int sampleSize)
369
{
370
var width = Math.Max(1, (int)Math.Ceiling(element.ActualWidth));
371
var height = Math.Max(1, (int)Math.Ceiling(element.ActualHeight));
372
var bitmap = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
373
bitmap.Render(element);
374
375
var stride = width * 4;
376
var pixels = new byte[stride * height];
377
bitmap.CopyPixels(pixels, stride, 0);
378
var size = Math.Min(sampleSize, Math.Min(width, height) / 2);
379
var maximumDifference = 0;
380
var differenceLocation = string.Empty;
381
var differenceValues = string.Empty;
382
383
for (var y = 0; y < size; y++)
384
{
385
for (var x = 0; x < size; x++)
386
{
387
var leftDifference = PixelDifference(pixels, stride, x, y, x, height - 1 - y);
388
if (leftDifference > maximumDifference)
389
{
390
maximumDifference = leftDifference;
391
differenceLocation = $"left ({x},{y})/({x},{height - 1 - y})";
392
differenceValues = $"{PixelValue(pixels, stride, x, y)}/{PixelValue(pixels, stride, x, height - 1 - y)}";
393
}
394
var rightDifference = PixelDifference(pixels, stride, width - 1 - x, y, width - 1 - x, height - 1 - y);
395
if (rightDifference > maximumDifference)
396
{
397
maximumDifference = rightDifference;
398
differenceLocation = $"right ({width - 1 - x},{y})/({width - 1 - x},{height - 1 - y})";
399
differenceValues = $"{PixelValue(pixels, stride, width - 1 - x, y)}/{PixelValue(pixels, stride, width - 1 - x, height - 1 - y)}";
400
}
401
}
402
}
403
404
Ensure(maximumDifference <= 1,
405
$"NavigationView 上下圆角的像素差异过大:{maximumDifference},位置 {differenceLocation},像素 {differenceValues},布局 {element.ActualWidth:N3}×{element.ActualHeight:N3},位图 {width}×{height}。");
406
}
407
408
private static int PixelDifference(byte[] pixels, int stride, int firstX, int firstY, int secondX, int secondY)
409
{
410
var first = firstY * stride + firstX * 4;
411
var second = secondY * stride + secondX * 4;
412
var difference = 0;
413
for (var channel = 0; channel < 4; channel++)
414
difference = Math.Max(difference, Math.Abs(pixels[first + channel] - pixels[second + channel]));
415
return difference;
416
}
417
418
private static string PixelValue(byte[] pixels, int stride, int x, int y)
419
{
420
var offset = y * stride + x * 4;
421
return $"[{pixels[offset]},{pixels[offset + 1]},{pixels[offset + 2]},{pixels[offset + 3]}]";
422
}
423
214
424
private const uint MonitorDefaultToNearest = 0x00000002;
215
425
216
426
[DllImport("user32.dll")]
@@ -0,0 +1,101 @@
1
using System.Windows;
2
using System.Windows.Controls;
3
using System.Windows.Media;
4
5
namespace XFEToolBox.WpfCore.Controls;
6
7
/// <summary>
8
/// 使用同一几何算法绘制并裁剪四个等半径圆角,避免原生 Border 在缩放后的上下圆角差异。
9
/// </summary>
10
public sealed class UniformRoundedBorder : Border
11
{
12
protected override void OnRender(DrawingContext drawingContext)
13
{
14
if (!TryGetUniformCornerRadius(out var radius) || !TryGetUniformBorderThickness(out var thickness))
15
{
16
base.OnRender(drawingContext);
17
return;
18
}
19
20
var bounds = new Rect(0, 0, ActualWidth, ActualHeight);
21
if (bounds.IsEmpty)
22
return;
23
24
if (Background is not null)
25
drawingContext.DrawGeometry(Background, null, CreateGeometry(bounds, radius));
26
27
if (BorderBrush is null || thickness <= 0)
28
return;
29
30
var outerGeometry = CreateGeometry(bounds, radius);
31
var innerWidth = ActualWidth - thickness * 2;
32
var innerHeight = ActualHeight - thickness * 2;
33
if (innerWidth <= 0 || innerHeight <= 0)
34
{
35
drawingContext.DrawGeometry(BorderBrush, null, outerGeometry);
36
return;
37
}
38
39
// A filled even-odd ring stays symmetric at the first and last pixel rows. A centered Pen stroke
40
// follows WPF's half-open raster edge rules and produces visibly different top/bottom antialiasing.
41
var borderGeometry = new GeometryGroup { FillRule = FillRule.EvenOdd };
42
borderGeometry.Children.Add(outerGeometry);
43
borderGeometry.Children.Add(CreateGeometry(
44
new Rect(thickness, thickness, innerWidth, innerHeight),
45
Math.Max(0, radius - thickness)));
46
borderGeometry.Freeze();
47
drawingContext.DrawGeometry(BorderBrush, null, borderGeometry);
48
}
49
50
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
51
{
52
base.OnRenderSizeChanged(sizeInfo);
53
UpdateClip();
54
}
55
56
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
57
{
58
base.OnPropertyChanged(e);
59
if (e.Property == CornerRadiusProperty)
60
UpdateClip();
61
}
62
63
private void UpdateClip()
64
{
65
// Clip is only needed for hosted content. Applying the clip to an outline-only instance also clips
66
// its own one-pixel stroke at the bottom/right raster boundary and makes mirrored corners differ.
67
if (Child is null || ActualWidth <= 0 || ActualHeight <= 0 || !TryGetUniformCornerRadius(out var radius))
68
{
69
Clip = null;
70
return;
71
}
72
73
Clip = CreateGeometry(new Rect(0, 0, ActualWidth, ActualHeight), radius);
74
}
75
76
private bool TryGetUniformCornerRadius(out double radius)
77
{
78
radius = CornerRadius.TopLeft;
79
return AreClose(radius, CornerRadius.TopRight) &&
80
AreClose(radius, CornerRadius.BottomRight) &&
81
AreClose(radius, CornerRadius.BottomLeft);
82
}
83
84
private bool TryGetUniformBorderThickness(out double thickness)
85
{
86
thickness = BorderThickness.Left;
87
return AreClose(thickness, BorderThickness.Top) &&
88
AreClose(thickness, BorderThickness.Right) &&
89
AreClose(thickness, BorderThickness.Bottom);
90
}
91
92
private static Geometry CreateGeometry(Rect bounds, double requestedRadius)
93
{
94
var radius = Math.Min(Math.Max(0, requestedRadius), Math.Min(bounds.Width, bounds.Height) / 2);
95
var geometry = new RectangleGeometry(bounds, radius, radius);
96
geometry.Freeze();
97
return geometry;
98
}
99
100
private static bool AreClose(double left, double right) => Math.Abs(left - right) < 0.001;
101
}
@@ -672,26 +672,39 @@
672
672
673
673
<Style x:Key="TabViewItemStyle" TargetType="TabItem">
674
674
<Setter Property="Padding" Value="15,10"/><Setter Property="MinHeight" Value="42"/>
675
<Setter Property="Margin" Value="1,0,5,0"/>
675
<Setter Property="Margin" Value="0,0,5,0"/>
676
676
<Setter Property="Panel.ZIndex" Value="0"/>
677
677
<Setter Property="Foreground" Value="{DynamicResource ToolTextSecondaryBrush}"/>
678
678
<Setter Property="Background" Value="Transparent"/><Setter Property="BorderBrush" Value="Transparent"/>
679
<Setter Property="BorderThickness" Value="1,1,1,0"/>
679
<Setter Property="BorderThickness" Value="1"/>
680
680
<Setter Property="FocusVisualStyle" Value="{x:Null}"/><Setter Property="Cursor" Value="Hand"/>
681
681
<Setter Property="Template"><Setter.Value><ControlTemplate TargetType="TabItem">
682
<Grid Panel.ZIndex="2">
682
<Grid Panel.ZIndex="2" SnapsToDevicePixels="True" UseLayoutRounding="True">
683
683
<Border x:Name="Surface" Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}"
684
684
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
685
685
CornerRadius="11,11,0,0">
686
<ContentPresenter ContentSource="Header" HorizontalAlignment="Center" VerticalAlignment="Center"/>
686
<ContentPresenter x:Name="HeaderContent" ContentSource="Header" Margin="0,0,5,0"
687
HorizontalAlignment="Center" VerticalAlignment="Center"/>
687
688
</Border>
689
<!-- This overlay does not participate in the auto-size measurement. Its trailing edge is
690
deliberately inset so TabPanel/DPI clipping cannot remove the right side. -->
691
<Border x:Name="SelectedOutline" Margin="0,0,5,0"
692
BorderBrush="{DynamicResource MainColor}" BorderThickness="1,1,1,0"
693
CornerRadius="11,11,0,0" Visibility="Collapsed"
694
IsHitTestVisible="False" Panel.ZIndex="4"/>
695
<Border x:Name="ContentConnector" Height="2" Margin="1,0,6,0"
696
VerticalAlignment="Bottom" Background="Transparent" Visibility="Collapsed"
697
IsHitTestVisible="False" Panel.ZIndex="3"/>
688
698
</Grid>
689
699
<ControlTemplate.Triggers>
690
700
<Trigger Property="IsMouseOver" Value="True"><Setter TargetName="Surface" Property="Background" Value="{DynamicResource ToolControlHoverBrush}"/></Trigger>
691
701
<Trigger Property="IsSelected" Value="True">
692
702
<Setter Property="Panel.ZIndex" Value="10"/>
693
703
<Setter TargetName="Surface" Property="Background" Value="{DynamicResource ToolControlBackgroundBrush}"/>
694
<Setter TargetName="Surface" Property="BorderBrush" Value="{DynamicResource ToolControlBorderBrush}"/>
704
<Setter TargetName="Surface" Property="BorderBrush" Value="Transparent"/>
705
<Setter TargetName="SelectedOutline" Property="Visibility" Value="Visible"/>
706
<Setter TargetName="ContentConnector" Property="Background" Value="{DynamicResource ToolControlBackgroundBrush}"/>
707
<Setter TargetName="ContentConnector" Property="Visibility" Value="Visible"/>
695
708
<Setter Property="Foreground" Value="{DynamicResource MainColor}"/>
696
709
<Setter Property="FontWeight" Value="SemiBold"/>
697
710
</Trigger>
@@ -711,10 +724,10 @@
711
724
<Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions>
712
725
<ScrollViewer x:Name="HeaderScroller" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled"
713
726
CanContentScroll="False" PanningMode="HorizontalOnly" Focusable="False"
714
Margin="1,1,1,0" Panel.ZIndex="2">
727
Margin="0,1,0,0" Panel.ZIndex="2">
715
728
<TabPanel IsItemsHost="True" Background="Transparent" KeyboardNavigation.TabIndex="1"/>
716
729
</ScrollViewer>
717
<Border Grid.Row="1" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
730
<Border x:Name="ContentFrame" Grid.Row="1" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
718
731
BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"
719
732
CornerRadius="0,14,14,14" Margin="0,-1,0,0" Panel.ZIndex="1">
720
733
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent"
@@ -726,20 +739,19 @@
726
739
727
740
<Style x:Key="NavigationViewItemStyle" TargetType="TabItem">
728
741
<Setter Property="Padding" Value="14,11"/><Setter Property="Margin" Value="1,0,1,5"/>
729
<Setter Property="MinHeight" Value="42"/><Setter Property="HorizontalContentAlignment" Value="Stretch"/>
742
<Setter Property="MinHeight" Value="42"/><Setter Property="HorizontalContentAlignment" Value="Center"/>
730
743
<Setter Property="Foreground" Value="{DynamicResource ToolTextSecondaryBrush}"/>
731
744
<Setter Property="Background" Value="Transparent"/><Setter Property="FocusVisualStyle" Value="{x:Null}"/>
732
745
<Setter Property="Cursor" Value="Hand"/>
733
746
<Setter Property="Template"><Setter.Value><ControlTemplate TargetType="TabItem">
734
<Border x:Name="Surface" Background="{TemplateBinding Background}" CornerRadius="11">
747
<controls:UniformRoundedBorder x:Name="Surface" Background="{TemplateBinding Background}" CornerRadius="11">
735
748
<Grid>
736
<Grid.ColumnDefinitions><ColumnDefinition Width="4"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions>
737
749
<Border x:Name="SelectedMark" Width="3" Height="22" HorizontalAlignment="Left" VerticalAlignment="Center"
738
750
Background="Transparent" CornerRadius="2"/>
739
<ContentPresenter Grid.Column="1" ContentSource="Header" Margin="{TemplateBinding Padding}"
751
<ContentPresenter x:Name="HeaderContent" ContentSource="Header" Margin="{TemplateBinding Padding}"
740
752
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="Center"/>
741
753
</Grid>
742
</Border>
754
</controls:UniformRoundedBorder>
743
755
<ControlTemplate.Triggers>
744
756
<Trigger Property="IsMouseOver" Value="True"><Setter TargetName="Surface" Property="Background" Value="{DynamicResource ToolControlHoverBrush}"/></Trigger>
745
757
<Trigger Property="IsSelected" Value="True"><Setter TargetName="Surface" Property="Background" Value="{DynamicResource ToolAccentSoftBrush}"/><Setter TargetName="SelectedMark" Property="Background" Value="{DynamicResource MainColor}"/><Setter Property="Foreground" Value="{DynamicResource ToolTextPrimaryBrush}"/><Setter Property="FontWeight" Value="SemiBold"/></Trigger>
@@ -759,16 +771,19 @@
759
771
<ColumnDefinition Width="12"/>
760
772
<ColumnDefinition Width="*"/>
761
773
</Grid.ColumnDefinitions>
762
<Grid Margin="1">
763
<controls:RoundedClipBorder Background="{DynamicResource ToolSurfaceBrush}"
764
CornerRadius="14" Padding="6">
765
<ScrollViewer Background="Transparent" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"
766
CanContentScroll="True" PanningMode="VerticalOnly" Focusable="False">
767
<TabPanel IsItemsHost="True" Background="Transparent" KeyboardNavigation.TabIndex="1"/>
768
</ScrollViewer>
769
</controls:RoundedClipBorder>
770
<Border BorderBrush="{DynamicResource ToolControlBorderBrush}" BorderThickness="1"
771
CornerRadius="15" Background="Transparent" IsHitTestVisible="False" Panel.ZIndex="10"/>
774
<!-- The clip and outline share exactly the same bounds and radius. Keeping the host two
775
pixels inside the layout prevents either outline edge from landing on a clipped boundary. -->
776
<Grid x:Name="NavigationFrameHost" Margin="2" SnapsToDevicePixels="True" UseLayoutRounding="True">
777
<controls:UniformRoundedBorder x:Name="NavigationClip" Background="{DynamicResource ToolSurfaceBrush}"
778
BorderThickness="0" CornerRadius="15" Padding="6">
779
<ScrollViewer x:Name="NavigationScroller" Background="Transparent" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"
780
CanContentScroll="True" PanningMode="VerticalOnly" Focusable="False">
781
<TabPanel IsItemsHost="True" Background="Transparent" KeyboardNavigation.TabIndex="1"/>
782
</ScrollViewer>
783
</controls:UniformRoundedBorder>
784
<controls:UniformRoundedBorder x:Name="NavigationFrame" BorderBrush="{DynamicResource ToolControlHoverBorderBrush}"
785
BorderThickness="1" CornerRadius="15" Background="Transparent"
786
RenderOptions.EdgeMode="Aliased" IsHitTestVisible="False" Panel.ZIndex="10"/>
772
787
</Grid>
773
788
<Border Grid.Column="2" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
774
789
BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
@@ -21,12 +21,12 @@ public partial class ConsoleProfile : XFEProfile
21
21
[ProfileProperty]
22
22
private bool localHostOnly = true;
23
23
/// <summary>
24
/// 是否由工具箱主动连接调试程序服务器(第二调试模式)
24
/// 是否由工具箱主动连接调试程序服务器
25
25
/// </summary>
26
26
[ProfileProperty]
27
27
private bool connectToRemoteServer = false;
28
28
/// <summary>
29
/// 第二调试模式下的远程调试程序服务器地址
29
/// 远程调试模式下的远程调试程序服务器地址
30
30
/// </summary>
31
31
[ProfileProperty]
32
32
private string remoteServerAddress = "ws://localhost:3280/";
@@ -81,7 +81,7 @@
81
81
<Border Background="{DynamicResource MainColor}" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="1"/>
82
82
<StackPanel Grid.Row="1">
83
83
<controls:SwitchButton Margin="0,14,0,4" Command="{Binding SwitchedCommand}" CommandParameter="{Binding RelativeSource={x:Static RelativeSource.Self}}" Tag="XFEToolBox.Client.Profiles.CrossVersionProfiles.ConsoleProfile.ConnectToRemoteServer">
84
<TextBlock Text="第二调试模式:工具箱主动连接调试程序服务器"/>
84
<TextBlock Text="远程调试模式(连接调试程序服务器)"/>
85
85
</controls:SwitchButton>
86
86
<Grid Margin="0,8,0,4">
87
87
<Grid.ColumnDefinitions>
@@ -1,21 +1,32 @@
1
1
{
2
"startedAt": "2026-08-17T20:08:53.5334231+00:00",
3
"duration": "00:00:00.7109143",
2
"startedAt": "2026-08-17T21:40:51.2224379+00:00",
3
"duration": "00:00:01.2041096",
4
4
"results": [
5
5
{
6
6
"id": "XFEToolBox.Client.Wpf.Test.Program.FramelessMaximizedWindowStaysInsideMonitorWorkArea#0",
7
7
"displayName": "Program.FramelessMaximizedWindowStaysInsideMonitorWorkArea",
8
8
"outcome": 0,
9
"bodyDuration": "00:00:00.7006523",
10
"totalDuration": "00:00:00.7020166",
9
"bodyDuration": "00:00:00.3877852",
10
"totalDuration": "00:00:00.3879686",
11
"attempts": 1,
12
"message": null,
13
"stackTrace": null,
14
"output": ""
15
},
16
{
17
"id": "XFEToolBox.Client.Wpf.Test.Program.TabAndNavigationOutlinesStayInsideTheirLayoutBounds#0",
18
"displayName": "Program.TabAndNavigationOutlinesStayInsideTheirLayoutBounds",
19
"outcome": 0,
20
"bodyDuration": "00:00:00.8030758",
21
"totalDuration": "00:00:00.8043573",
11
22
"attempts": 1,
12
23
"message": null,
13
24
"stackTrace": null,
14
25
"output": ""
15
26
}
16
27
],
17
"total": 1,
18
"passed": 1,
28
"total": 2,
29
"passed": 2,
19
30
"failed": 0,
20
31
"skipped": 0
21
32
}
@@ -1,3 +1,4 @@
1
<testsuite name="XFEExtension.NetCore.XUnit" tests="1" failures="0" skipped="0" time="0.710914">
2
<testcase name="Program.FramelessMaximizedWindowStaysInsideMonitorWorkArea" time="0.702017" />
1
<testsuite name="XFEExtension.NetCore.XUnit" tests="2" failures="0" skipped="0" time="1.204110">
2
<testcase name="Program.FramelessMaximizedWindowStaysInsideMonitorWorkArea" time="0.387969" />
3
<testcase name="Program.TabAndNavigationOutlinesStayInsideTheirLayoutBounds" time="0.804357" />
3
4
</testsuite>