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 @@