返回提交历史
Added
XFEToolBox.Client.Wpf.Test/UnifiedControlDefaultsTests.cs
+67
-0
Modified
XFEToolBox.WpfCore/Controls/DataGridAssist.cs
+11
-6
Modified
XFEToolBox.WpfCore/Controls/ProgressRing.cs
+9
-7
Modified
XFEToolBox/Utilities/PopupHelper.cs
+28
-19
Modified
docs/tool-packages.md
+3
-1
XFEstudio/XFEToolBox
优化控件默认行为与样式兼容性,补充测试文档
本次变更: - DataGridAssist 优化样式应用逻辑,避免覆盖用户自定义列样式。 - ProgressRing 默认不活跃,默认值为 0,调整动画绘制逻辑。 - PopupHelper 增强滚动条样式兼容性,新增文本弹窗重载。 - 同步更新 tool-packages.md,补充新 API 及样式查找说明。 - 新增统一控件默认行为相关测试用例,验证样式与默认值。
715a04d
代码差异
5 个文件
+118
-33
@@ -0,0 +1,67 @@
1
using System.Windows;
2
using System.Windows.Controls;
3
using XFEToolBox.WpfCore.Controls;
4
5
namespace XFEToolBox.Client.Wpf.Test;
6
7
public static class UnifiedControlDefaultsTests
8
{
9
[Test]
10
public static void DataGridGeneratedColumnsReceiveUnifiedStylesWithoutOverwritingExplicitStyles()
11
{
12
RunSta(() =>
13
{
14
var elementStyle = new Style(typeof(CheckBox));
15
var editingStyle = new Style(typeof(CheckBox));
16
var grid = new DataGrid();
17
DataGridAssist.SetCheckBoxElementStyle(grid, elementStyle);
18
DataGridAssist.SetCheckBoxEditingStyle(grid, editingStyle);
19
DataGridAssist.SetUseUnifiedCellControls(grid, true);
20
21
var generatedColumn = new DataGridCheckBoxColumn();
22
grid.Columns.Add(generatedColumn);
23
Ensure(ReferenceEquals(generatedColumn.ElementStyle, elementStyle),
24
"DataGridCheckBoxColumn 仍在使用 WPF 系统默认显示样式。");
25
Ensure(ReferenceEquals(generatedColumn.EditingElementStyle, editingStyle),
26
"DataGridCheckBoxColumn 仍在使用 WPF 系统默认编辑样式。");
27
28
var explicitStyle = new Style(typeof(CheckBox));
29
var explicitColumn = new DataGridCheckBoxColumn { ElementStyle = explicitStyle };
30
grid.Columns.Add(explicitColumn);
31
Ensure(ReferenceEquals(explicitColumn.ElementStyle, explicitStyle),
32
"统一样式覆盖了页面作者显式指定的列样式。");
33
});
34
}
35
36
[Test]
37
public static void ProgressRingDefaultsToZeroAndInactive()
38
{
39
RunSta(() =>
40
{
41
var ring = new ProgressRing();
42
Ensure(Math.Abs(ring.Value) < double.Epsilon, "ProgressRing 默认 Value 不是 0。");
43
Ensure(!ring.IsActive, "ProgressRing 默认状态仍会启动进度动画。");
44
});
45
}
46
47
private static void RunSta(Action action)
48
{
49
Exception? failure = null;
50
var thread = new Thread(() =>
51
{
52
try { action(); }
53
catch (Exception exception) { failure = exception; }
54
});
55
thread.SetApartmentState(ApartmentState.STA);
56
thread.Start();
57
Ensure(thread.Join(TimeSpan.FromSeconds(10)), "WPF 控件测试超时。");
58
if (failure is not null)
59
throw new InvalidOperationException(failure.Message, failure);
60
}
61
62
private static void Ensure(bool condition, string message)
63
{
64
if (!condition)
65
throw new InvalidOperationException(message);
66
}
67
}
@@ -168,21 +168,26 @@ public static class DataGridAssist
168
168
169
169
private static void ApplyStylesIfUnset(DataGridBoundColumn column, Style? elementStyle, Style? editingStyle)
170
170
{
171
// 显式设置的列样式代表页面作者的主动选择,只替换 WPF 自动回退的系统默认样式。
172
if (column.ElementStyle is null && elementStyle is not null)
171
// DataGridCheckBoxColumn 等类型通过属性元数据提供非 null 的系统默认样式。
172
// ReadLocalValue 才能区分“页面作者显式设置”和“WPF 元数据回退”。
173
if (column.ReadLocalValue(DataGridBoundColumn.ElementStyleProperty) == DependencyProperty.UnsetValue
174
&& elementStyle is not null)
173
175
column.ElementStyle = elementStyle;
174
176
175
if (column.EditingElementStyle is null && editingStyle is not null)
177
if (column.ReadLocalValue(DataGridBoundColumn.EditingElementStyleProperty) == DependencyProperty.UnsetValue
178
&& editingStyle is not null)
176
179
column.EditingElementStyle = editingStyle;
177
180
}
178
181
179
182
private static void ApplyStylesIfUnset(DataGridComboBoxColumn column, Style? elementStyle, Style? editingStyle)
180
183
{
181
// DataGridComboBoxColumn 不继承 DataGridBoundColumn,但拥有独立的同名样式属性。
182
if (column.ElementStyle is null && elementStyle is not null)
184
// DataGridComboBoxColumn 不继承 DataGridBoundColumn,但拥有独立的同名依赖属性。
185
if (column.ReadLocalValue(DataGridComboBoxColumn.ElementStyleProperty) == DependencyProperty.UnsetValue
186
&& elementStyle is not null)
183
187
column.ElementStyle = elementStyle;
184
188
185
if (column.EditingElementStyle is null && editingStyle is not null)
189
if (column.ReadLocalValue(DataGridComboBoxColumn.EditingElementStyleProperty) == DependencyProperty.UnsetValue
190
&& editingStyle is not null)
186
191
column.EditingElementStyle = editingStyle;
187
192
}
188
193
}
@@ -16,7 +16,7 @@ public class ProgressRing : RangeBase
16
16
17
17
public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register(
18
18
nameof(IsActive), typeof(bool), typeof(ProgressRing),
19
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsRender, OnAnimationStateChanged));
19
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender, OnAnimationStateChanged));
20
20
21
21
public static readonly DependencyProperty IsIndeterminateProperty = DependencyProperty.Register(
22
22
nameof(IsIndeterminate), typeof(bool), typeof(ProgressRing),
@@ -27,6 +27,13 @@ public class ProgressRing : RangeBase
27
27
new FrameworkPropertyMetadata(3d, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender,
28
28
null, CoerceRingThickness));
29
29
30
static ProgressRing()
31
{
32
ValueProperty.OverrideMetadata(
33
typeof(ProgressRing),
34
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsRender));
35
}
36
30
37
public ProgressRing()
31
38
{
32
39
Loaded += (_, _) => UpdateAnimation();
@@ -69,10 +76,7 @@ public class ProgressRing : RangeBase
69
76
70
77
drawingContext.DrawEllipse(null, trackPen, center, radius, radius);
71
78
72
if (!IsActive)
73
drawingContext.PushOpacity(0.34);
74
75
if (IsIndeterminate)
79
if (IsActive && IsIndeterminate)
76
80
{
77
81
DrawArc(drawingContext, progressPen, center, radius, -90 + (double)GetValue(RotationAngleProperty), 96);
78
82
}
@@ -86,8 +90,6 @@ public class ProgressRing : RangeBase
86
90
DrawArc(drawingContext, progressPen, center, radius, -90, progress * 359.9);
87
91
}
88
92
89
if (!IsActive)
90
drawingContext.Pop();
91
93
}
92
94
93
95
protected override void OnMinimumChanged(double oldMinimum, double newMinimum)
@@ -20,29 +20,32 @@ public static class PopupHelper
20
20
return dialogPage;
21
21
}
22
22
23
private static ScrollViewer CreateTextContent(string text, Color textColor) => new()
23
private static ScrollViewer CreateTextContent(string text, Color textColor)
24
24
{
25
Content = new TextBlock
26
{
27
Text = text,
28
Foreground = new SolidColorBrush(textColor),
29
Margin = new Thickness(20, 20, 20, 0),
30
TextWrapping = TextWrapping.WrapWithOverflow
31
},
32
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
33
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
34
Resources = new ResourceDictionary
25
var content = new ScrollViewer
35
26
{
27
Content = new TextBlock
36
28
{
37
typeof(ScrollBar),
38
new Style
39
{
40
TargetType = typeof(ScrollBar),
41
BasedOn = (Style)Application.Current.FindResource("ConsoleScrollBar")
42
}
43
}
29
Text = text,
30
Foreground = new SolidColorBrush(textColor),
31
Margin = new Thickness(20, 20, 20, 0),
32
TextWrapping = TextWrapping.WrapWithOverflow
33
},
34
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
35
VerticalScrollBarVisibility = ScrollBarVisibility.Auto
36
};
37
38
// 精简工具宿主只加载 ToolThemeResources,不一定包含主程序遗留的 ConsoleScrollBar 键。
39
// 优先复用可用的工具箱样式;两者都不存在时保留隐式/系统 ScrollBar 样式,不能让弹窗崩溃。
40
var scrollBarStyle = Application.Current?.TryFindResource("ConsoleScrollBar") as Style
41
?? Application.Current?.TryFindResource("ToolBoxScrollBarStyle") as Style;
42
if (scrollBarStyle is not null)
43
{
44
content.Resources[typeof(ScrollBar)] = new Style(typeof(ScrollBar), scrollBarStyle);
44
45
}
45
};
46
47
return content;
48
}
46
49
47
50
public static MessageBoxResult? ShowConfirmDialog(object content, bool showCancelButton = false, string confirmText = "确定", string cancelText = "取消")
48
51
=> ShowConfirmDialog(content, new PopupWindowOptions(), showCancelButton, confirmText, cancelText);
@@ -62,6 +65,9 @@ public static class PopupHelper
62
65
63
66
public static MessageBoxResult? ShowConfirmDialog(string text, bool showCancelButton = false, string confirmText = "确定", string cancelText = "取消") => ShowConfirmDialog(text, Colors.Black, showCancelButton, confirmText, cancelText);
64
67
68
public static MessageBoxResult? ShowConfirmDialog(string text, PopupWindowOptions options, bool showCancelButton = false, string confirmText = "确定", string cancelText = "取消") =>
69
ShowConfirmDialog(CreateTextContent(text, Colors.Black), options, showCancelButton, confirmText, cancelText);
70
65
71
public static MessageBoxResult? ShowYesOrNoDialog(object content, bool showCancelButton = false, string yesText = "是", string noText = "否")
66
72
=> ShowYesOrNoDialog(content, new PopupWindowOptions(), showCancelButton, yesText, noText);
67
73
@@ -81,6 +87,9 @@ public static class PopupHelper
81
87
82
88
public static MessageBoxResult? ShowYesOrNoDialog(string text, bool showCancelButton = false, string yesText = "是", string noText = "否") => ShowYesOrNoDialog(text, Colors.Black, showCancelButton, yesText, noText);
83
89
90
public static MessageBoxResult? ShowYesOrNoDialog(string text, PopupWindowOptions options, bool showCancelButton = false, string yesText = "是", string noText = "否") =>
91
ShowYesOrNoDialog(CreateTextContent(text, Colors.Black), options, showCancelButton, yesText, noText);
92
84
93
public static MessageBoxResult? ShowDialog(object content, double width = 320, double height = 230) =>
85
94
ShowDialog(content, new PopupWindowOptions { Width = width, Height = height });
86
95
@@ -774,15 +774,17 @@ var result = PopupHelper.ShowDialog(
774
774
| `ShowConfirmDialog(object content, bool showCancelButton = false, string confirmText = "确定", string cancelText = "取消")` | 确定对话框,可选取消按钮 |
775
775
| `ShowConfirmDialog(string text, Color textColor, bool showCancelButton = false, string confirmText = "确定", string cancelText = "取消")` | 文本版本,自定义颜色 |
776
776
| `ShowConfirmDialog(string text, bool showCancelButton = false, string confirmText = "确定", string cancelText = "取消")` | 文本版本,默认黑色 |
777
| `ShowConfirmDialog(string text, PopupWindowOptions options, ...)` | 文本版本,并使用完整窗口配置 |
777
778
| `ShowYesOrNoDialog(object content, bool showCancelButton = false, string yesText = "是", string noText = "否")` | 是/否对话框,可选取消按钮 |
778
779
| `ShowYesOrNoDialog(string text, Color textColor, ...)` | 文本与颜色版本 |
779
780
| `ShowYesOrNoDialog(string text, bool showCancelButton = false, ...)` | 默认文本版本 |
781
| `ShowYesOrNoDialog(string text, PopupWindowOptions options, ...)` | 文本版本,并使用完整窗口配置 |
780
782
| `ShowDialog(object content, double width = 320, double height = 230)` | 使用默认外壳显示任意内容 |
781
783
| `ShowDialog(object content, PopupWindowOptions options)` | 完整配置版本 |
782
784
783
785
返回值均为 `MessageBoxResult?`。确认、是、否、取消分别使用 `OK`、`Yes`、`No`、`Cancel`;标题栏关闭或 `Esc` 返回 `None`,窗口在结果赋值前被外部关闭时也可能为 `null`。
784
786
785
> 当前源码中的 `string` 便捷重载仍会查找旧资源键 `ConsoleScrollBar`,而工具主题没有提供该键。在该兼容问题修复前,工具应使用 `object content` 重载并自行传入采用主题画刷的 `TextBlock` / `ScrollViewer`,上方示例即为安全写法。
787
文本便捷重载会优先复用工具箱滚动条样式;精简工具宿主未提供相应资源键时会自动回退到隐式或系统样式,不会因缺少资源而中断工具运行。
786
788
787
789
### `PopupWindowOptions`
788
790