diff --git a/runtime/Sim.WinForms/Forms/Control.cs b/runtime/Sim.WinForms/Forms/Control.cs
index 3f1738d..4984262 100644
--- a/runtime/Sim.WinForms/Forms/Control.cs
+++ b/runtime/Sim.WinForms/Forms/Control.cs
@@ -11,7 +11,7 @@ namespace System.Windows.Forms;
///
/// 仿真控件的基类。属性保存在本进程里;控件所在窗体显示后,属性变化以 patch 消息发给浏览器渲染。
-/// Font、BackColor、ForeColor 是"环境属性":没有显式设置时沿用父控件的值,只把显式设置的值发给浏览器。
+/// Font、BackColor、ForeColor 是"环境属性":没有显式设置时沿用父控件的值(文本框等的颜色除外,见 OwnBackColor),只把显式设置的值发给浏览器。
///
public class Control : Component, IWin32Window
{
@@ -178,24 +178,42 @@ public class Control : Component, IWin32Window
}
}
+ /// 和 WinForms 一样:父控件(分组框、面板)禁用时子控件也是禁用的,读出 false、不响应操作;浏览器按同样的规则画成灰色
public bool Enabled
{
- get => _enabled;
+ get => _enabled && (_parent?.Enabled ?? true);
set
{
- if (SetProp(ref _enabled, value, nameof(Enabled))) OnEnabledChanged(EventArgs.Empty);
+ var old = Enabled;
+ SetProp(ref _enabled, value, nameof(Enabled));
+ if (Enabled != old) OnEnabledChanged(EventArgs.Empty);
}
}
+ ///
+ /// 没有显式设置时控件自己的颜色;null 表示沿用父控件。文本框、列表框、下拉框、日期选择是白底黑字(SystemColors.Window / WindowText),
+ /// 不沿用父控件:分组框设成白字时,里面的文本框还是黑字。
+ ///
+ private protected virtual Color? OwnBackColor => null;
+
+ private protected virtual Color? OwnForeColor => null;
+
+ /// 能不能把 BackColor 设成透明(含半透明):窗体、文本框、列表框、下拉框、日期选择、数据表格不行,和 WinForms 一样报错
+ private protected virtual bool SupportsTransparentBackColor => true;
+
public virtual Color BackColor
{
- get => !_backColor.IsEmpty ? _backColor : _parent?.BackColor ?? DefaultBackColor;
- set => SetProp(ref _backColor, value, nameof(BackColor));
+ get => !_backColor.IsEmpty ? _backColor : OwnBackColor ?? _parent?.BackColor ?? DefaultBackColor;
+ set
+ {
+ if (!value.IsEmpty && value.A < 255 && !SupportsTransparentBackColor) throw new ArgumentException("控件不支持透明的背景色。");
+ SetProp(ref _backColor, value, nameof(BackColor));
+ }
}
public virtual Color ForeColor
{
- get => !_foreColor.IsEmpty ? _foreColor : _parent?.ForeColor ?? DefaultForeColor;
+ get => !_foreColor.IsEmpty ? _foreColor : OwnForeColor ?? _parent?.ForeColor ?? DefaultForeColor;
set => SetProp(ref _foreColor, value, nameof(ForeColor));
}
@@ -302,7 +320,14 @@ public class Control : Component, IWin32Window
protected virtual void OnMouseLeave(EventArgs e) => MouseLeave?.Invoke(this, e);
protected virtual void OnTextChanged(EventArgs e) => TextChanged?.Invoke(this, e);
protected virtual void OnVisibleChanged(EventArgs e) => VisibleChanged?.Invoke(this, e);
- protected virtual void OnEnabledChanged(EventArgs e) => EnabledChanged?.Invoke(this, e);
+ protected virtual void OnEnabledChanged(EventArgs e)
+ {
+ EnabledChanged?.Invoke(this, e);
+ // 自己没被禁用的子控件实际状态也跟着变,同样触发 EnabledChanged(WinForms 的 OnParentEnabledChanged)
+ for (var i = 0; i < Controls.Count; i++)
+ if (Controls[i]._enabled) Controls[i].OnEnabledChanged(e);
+ }
+
protected virtual void OnGotFocus(EventArgs e) => GotFocus?.Invoke(this, e);
protected virtual void OnLostFocus(EventArgs e) => LostFocus?.Invoke(this, e);
protected virtual void OnPaint(PaintEventArgs e) => Paint?.Invoke(this, e);
diff --git a/runtime/Sim.WinForms/Forms/DataGridView.cs b/runtime/Sim.WinForms/Forms/DataGridView.cs
index 0a8662f..5b1249b 100644
--- a/runtime/Sim.WinForms/Forms/DataGridView.cs
+++ b/runtime/Sim.WinForms/Forms/DataGridView.cs
@@ -468,6 +468,8 @@ public class DataGridView : Control, ISupportInitialize
private bool _initializing;
private bool _rowsDirty;
+ private protected override bool SupportsTransparentBackColor => false;
+
public DataGridView()
{
Columns = new DataGridViewColumnCollection(this);
diff --git a/runtime/Sim.WinForms/Forms/DateTimePicker.cs b/runtime/Sim.WinForms/Forms/DateTimePicker.cs
index 3fb0f49..f4583e3 100644
--- a/runtime/Sim.WinForms/Forms/DateTimePicker.cs
+++ b/runtime/Sim.WinForms/Forms/DateTimePicker.cs
@@ -22,6 +22,10 @@ public class DateTimePicker : Control
protected override Size DefaultSize => new(200, 21);
+ private protected override Color? OwnBackColor => SystemColors.Window;
+ private protected override Color? OwnForeColor => SystemColors.WindowText;
+ private protected override bool SupportsTransparentBackColor => false;
+
public DateTime Value
{
get => _value;
diff --git a/runtime/Sim.WinForms/Forms/Form.cs b/runtime/Sim.WinForms/Forms/Form.cs
index 7c14bb8..a23f9fa 100644
--- a/runtime/Sim.WinForms/Forms/Form.cs
+++ b/runtime/Sim.WinForms/Forms/Form.cs
@@ -23,6 +23,8 @@ public class Form : ContainerControl
private bool _modal;
private DialogResult _dialogResult;
+ private protected override bool SupportsTransparentBackColor => false;
+
public Form()
{
base.Visible = false;
diff --git a/runtime/Sim.WinForms/Forms/ListControl.cs b/runtime/Sim.WinForms/Forms/ListControl.cs
index 765bc36..c0b78f6 100644
--- a/runtime/Sim.WinForms/Forms/ListControl.cs
+++ b/runtime/Sim.WinForms/Forms/ListControl.cs
@@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
+using System.Drawing;
using System.Linq;
namespace System.Windows.Forms;
@@ -10,6 +11,10 @@ public abstract class ListControl : Control
/// 选中项的序号。列表项增删时直接调整(和 WinForms 一样不触发 SelectedIndexChanged)
private protected int SelectedIndexValue = -1;
+ private protected override Color? OwnBackColor => SystemColors.Window;
+ private protected override Color? OwnForeColor => SystemColors.WindowText;
+ private protected override bool SupportsTransparentBackColor => false;
+
public abstract int SelectedIndex { get; set; }
public string DisplayMember { get; set; } = "";
diff --git a/runtime/Sim.WinForms/Forms/TextBox.cs b/runtime/Sim.WinForms/Forms/TextBox.cs
index 5826fc1..bd6d7c4 100644
--- a/runtime/Sim.WinForms/Forms/TextBox.cs
+++ b/runtime/Sim.WinForms/Forms/TextBox.cs
@@ -10,6 +10,10 @@ public abstract class TextBoxBase : Control
private bool _readOnly;
private BorderStyle _borderStyle = BorderStyle.Fixed3D;
+ private protected override Color? OwnBackColor => _readOnly ? SystemColors.Control : SystemColors.Window;
+ private protected override Color? OwnForeColor => SystemColors.WindowText;
+ private protected override bool SupportsTransparentBackColor => false;
+
public bool Multiline
{
get => _multiline;
diff --git a/server/test/runner.test.ts b/server/test/runner.test.ts
index 77d74d9..6f02c04 100644
--- a/server/test/runner.test.ts
+++ b/server/test/runner.test.ts
@@ -672,6 +672,87 @@ test('虚拟时钟:拨时间、倍速后 DateTime.Now / Today 跟着走,恢
expect(runner.messages.some((m) => m.type === 'console' && m.text.includes('today=2024-01-16'))).toBe(true)
}, 30000)
+test('颜色:分组框设成白字时,里面的标签沿用白字,文本框、列表框、下拉框还是白底黑字;只发显式设置的颜色', async () => {
+ const { runner } = await runMain(
+ 'colors',
+ `
+ var form = new Form();
+ var group = new GroupBox { ForeColor = Color.White, BackColor = Color.Navy };
+ var label = new Label();
+ var text = new TextBox { Name = "text" };
+ var readOnly = new TextBox { ReadOnly = true };
+ var list = new ListBox();
+ var combo = new ComboBox();
+ var red = new TextBox { Name = "red", ForeColor = Color.Red };
+ group.Controls.AddRange(new Control[] { label, text, readOnly, list, combo, red });
+ form.Controls.Add(group);
+ Console.WriteLine(string.Join(",", new Control[] { label, text, list, combo, red }.Select(c => c.ForeColor.Name)));
+ Console.WriteLine(string.Join(",", new Control[] { label, text, readOnly, list, combo }.Select(c => c.BackColor.Name)));
+ Application.Run(form);`,
+ 'using System.Drawing;\nusing System.Windows.Forms;',
+ )
+ const { form } = await runner.waitFor('formShown')
+ expect(findByName(form, 'text').props.ForeColor).toBeNull()
+ expect(findByName(form, 'red').props.ForeColor).toBe('#ff0000')
+ const output = runner.messages.flatMap((m) => (m.type === 'console' ? [m.text] : [])).join('')
+ expect(output).toBe('White,WindowText,WindowText,WindowText,Red\nNavy,Window,Control,Window,Window\n')
+}, 30000)
+
+test('禁用:分组框禁用后里面的控件 Enabled 读出 false、触发 EnabledChanged、点击不响应;重新启用后恢复', async () => {
+ const { runner } = await runMain(
+ 'enabled',
+ `
+ var form = new Form();
+ var group = new GroupBox { Name = "group" };
+ var button = new Button { Name = "button" };
+ var off = new Button { Enabled = false };
+ group.Controls.Add(button);
+ group.Controls.Add(off);
+ form.Controls.Add(group);
+ button.EnabledChanged += (s, e) => Console.WriteLine("button changed=" + button.Enabled);
+ off.EnabledChanged += (s, e) => Console.WriteLine("off changed");
+ button.Click += (s, e) => Console.WriteLine("click");
+ var toggle = new Button { Name = "toggle" };
+ toggle.Click += (s, e) => { group.Enabled = !group.Enabled; Console.WriteLine("group=" + group.Enabled + " button=" + button.Enabled + " off=" + off.Enabled); };
+ form.Controls.Add(toggle);
+ Application.Run(form);`,
+ 'using System.Windows.Forms;',
+ )
+ const { form } = await runner.waitFor('formShown')
+ const click = (name: string) => runner.send({ type: 'action', id: findByName(form, name).id, action: 'click' })
+ click('toggle')
+ await runner.waitFor('console', (m) => m.text.startsWith('group=False'))
+ click('button')
+ click('toggle')
+ await runner.waitFor('console', (m) => m.text.startsWith('group=True'))
+ click('button')
+ await runner.waitFor('console', (m) => m.text === 'click\n')
+ const output = runner.messages.flatMap((m) => (m.type === 'console' ? [m.text] : [])).join('')
+ // 浏览器只收到分组框自己的 Enabled,子控件按父控件画成禁用
+ expect(findByName(form, 'button').props.Enabled).toBe(true)
+ expect(output).toBe(
+ 'button changed=False\ngroup=False button=False off=False\nbutton changed=True\ngroup=True button=True off=False\nclick\n',
+ )
+}, 30000)
+
+test('透明背景:文本框、窗体等设透明背景色和 WinForms 一样报错,标签、面板可以', async () => {
+ const { runner } = await runMain(
+ 'transparent',
+ `
+ foreach (var control in new Control[] { new TextBox(), new ComboBox(), new ListBox(), new Form(), new Label(), new Panel() })
+ {
+ try { control.BackColor = Color.Transparent; Console.WriteLine(control.GetType().Name + " ok"); }
+ catch (ArgumentException ex) { Console.WriteLine(control.GetType().Name + " " + ex.Message); }
+ }`,
+ 'using System.Drawing;\nusing System.Windows.Forms;',
+ )
+ await runner.finished()
+ const output = runner.messages.flatMap((m) => (m.type === 'console' ? [m.text] : [])).join('')
+ expect(output).toBe(
+ 'TextBox 控件不支持透明的背景色。\nComboBox 控件不支持透明的背景色。\nListBox 控件不支持透明的背景色。\nForm 控件不支持透明的背景色。\nLabel ok\nPanel ok\n',
+ )
+}, 30000)
+
test('TrackBar:拖动时先 ValueChanged 再 Scroll,超出范围的值截到两端;代码设超出范围的 Value 报错', async () => {
const { runner } = await runMain(
'trackbar',
diff --git a/web/src/designer/PropertyGrid.vue b/web/src/designer/PropertyGrid.vue
index 5865b1f..be06b8d 100644
--- a/web/src/designer/PropertyGrid.vue
+++ b/web/src/designer/PropertyGrid.vue
@@ -1,7 +1,7 @@