Files
IOT_SIM/runtime/Sim.WinForms/Forms/Form.cs
T
xuyueandClaude Opus 5.5 d60868d912 停靠布局、背景图片、无边框窗体;运行区按比例缩小、可放大
- Dock:和 WinForms 一样按 Controls 倒序依次停靠,扣掉 Panel 边框、GroupBox 标题占的地方;
  改大小、显示隐藏、加入移出控件时重新布局;SuspendLayout / ResumeLayout / PerformLayout
- 设计器里每次修改后按同样的规则排好停靠的控件(designer/layout.ts);属性窗口加"停靠"
- BackgroundImage + BackgroundImageLayout(平铺、居中、拉伸、缩放):窗体、面板、图片框,
  设计器里从项目资源选;无边框窗体没有标题栏,大小不含边框
- 背景色沿用父控件:没设背景色的标签、面板、分组框等画父控件的背景色(不是背景图片),
  和 WinForms 一样;分组框标题处边框断开、透出背景;文本框的 BackColor 生效
- 图片框可以放子控件,把鼠标按下、松开发给程序
- 运行区:窗体比运行区大时整体按比例缩小并显示比例;"放大"占满整个网页(窗体在左、实训台在右),Esc 还原
- Paint 事件:VS 双击面板生成的空 panel1_Paint 能编译(平台由浏览器绘制,不触发)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:14:34 +08:00

212 lines
6.7 KiB
C#

using System.Collections.Generic;
using System.Drawing;
using System.Text.Json.Nodes;
using Sim.Host;
namespace System.Windows.Forms;
/// <summary>
/// 窗体。Show() 时先触发 Load,再把整个控件树发给浏览器;Close() 依次触发 FormClosing、FormClosed。
/// </summary>
public class Form : ContainerControl
{
// Windows 10 下可调边框窗体的标题栏和边框尺寸,Size = ClientSize + 边框
private static readonly Size NonClientSize = new(16, 39);
private Size _clientSize = new(284, 261);
private FormBorderStyle _formBorderStyle = FormBorderStyle.Sizable;
private bool _maximizeBox = true;
private bool _minimizeBox = true;
private bool _controlBox = true;
private bool _loaded;
private bool _closed;
private bool _modal;
private DialogResult _dialogResult;
public Form()
{
base.Visible = false;
}
internal bool IsOpenInBrowser { get; private set; }
public override Size ClientSize
{
get => _clientSize;
set
{
if (SetProp(ref _clientSize, value, nameof(ClientSize))) PerformLayout();
}
}
public new Size Size
{
get => _formBorderStyle == FormBorderStyle.None ? _clientSize : _clientSize + NonClientSize;
set => ClientSize = _formBorderStyle == FormBorderStyle.None ? value : value - NonClientSize;
}
public FormBorderStyle FormBorderStyle
{
get => _formBorderStyle;
set => SetProp(ref _formBorderStyle, value, nameof(FormBorderStyle));
}
public bool MaximizeBox
{
get => _maximizeBox;
set => SetProp(ref _maximizeBox, value, nameof(MaximizeBox));
}
public bool MinimizeBox
{
get => _minimizeBox;
set => SetProp(ref _minimizeBox, value, nameof(MinimizeBox));
}
public bool ControlBox
{
get => _controlBox;
set => SetProp(ref _controlBox, value, nameof(ControlBox));
}
public FormStartPosition StartPosition { get; set; } = FormStartPosition.WindowsDefaultLocation;
public FormWindowState WindowState { get; set; }
public bool ShowInTaskbar { get; set; } = true;
public bool TopMost { get; set; }
public bool KeyPreview { get; set; }
public double Opacity { get; set; } = 1;
public IButtonControl? AcceptButton { get; set; }
public IButtonControl? CancelButton { get; set; }
public Form? Owner { get; set; }
public bool Modal => _modal;
public DialogResult DialogResult
{
get => _dialogResult;
set
{
_dialogResult = value;
// 模态窗体设置 DialogResult 后自动关闭
if (_modal && value != DialogResult.None) Close();
}
}
public event EventHandler? Load;
public event EventHandler? Shown;
public event EventHandler? Activated;
public event FormClosingEventHandler? FormClosing;
public event FormClosedEventHandler? FormClosed;
protected virtual void OnLoad(EventArgs e) => Load?.Invoke(this, e);
protected virtual void OnShown(EventArgs e) => Shown?.Invoke(this, e);
protected virtual void OnActivated(EventArgs e) => Activated?.Invoke(this, e);
protected virtual void OnFormClosing(FormClosingEventArgs e) => FormClosing?.Invoke(this, e);
protected virtual void OnFormClosed(FormClosedEventArgs e) => FormClosed?.Invoke(this, e);
public new void Show()
{
if (_closed) throw new ObjectDisposedException(Name, "无法访问已释放的对象。");
if (IsOpenInBrowser)
{
Visible = true;
return;
}
SimHost.Ui.Send(() => Open(modal: false));
}
public void Show(IWin32Window? owner) => Show();
public DialogResult ShowDialog() => ShowDialog(null);
public DialogResult ShowDialog(IWin32Window? owner)
{
if (IsOpenInBrowser && Visible) throw new InvalidOperationException("窗体已可见,不能显示为模式对话框。");
// 模态窗体关闭后不释放,可以再次 ShowDialog(Load 只在第一次触发)
_closed = false;
_dialogResult = DialogResult.None;
_modal = true;
Open(modal: true);
// 嵌套消息循环:等到窗体关闭(或被隐藏)后 ShowDialog 才返回
SimHost.Ui.RunUntil(() => _closed || !Visible);
_modal = false;
return _dialogResult == DialogResult.None ? DialogResult.Cancel : _dialogResult;
}
private void Open(bool modal)
{
if (!_loaded)
{
_loaded = true;
OnLoad(EventArgs.Empty);
if (_closed) return; // Load 里调用了 Close()
RaiseUserControlLoad(this);
}
base.Visible = true;
IsOpenInBrowser = true;
Application.OpenFormsList.Add(this);
SimHost.Send(new JsonObject { ["type"] = "formShown", ["form"] = Snapshot(), ["modal"] = modal });
OnShown(EventArgs.Empty);
OnActivated(EventArgs.Empty);
}
private static void RaiseUserControlLoad(Control parent)
{
foreach (var child in parent.Controls)
{
if (child is UserControl userControl) userControl.RaiseLoadOnce();
RaiseUserControlLoad(child);
}
}
public void Close() => CloseCore(CloseReason.UserClosing);
internal void CloseCore(CloseReason reason)
{
if (_closed) return;
if (!IsOpenInBrowser)
{
_closed = true;
return;
}
var closing = new FormClosingEventArgs(reason, false);
OnFormClosing(closing);
if (closing.Cancel) return;
_closed = true;
if (_modal && _dialogResult == DialogResult.None) _dialogResult = DialogResult.Cancel;
OnFormClosed(new FormClosedEventArgs(reason));
IsOpenInBrowser = false;
Application.OpenFormsList.Remove(this);
SimHost.Send(new JsonObject { ["type"] = "formClosed", ["formId"] = SimId });
SimHost.Ui.Wake();
if (!_modal) Dispose();
}
internal bool IsClosed => _closed;
public void Activate() { }
public void CenterToScreen() { }
protected override void OnUserAction(UserAction action)
{
if (action.Action == "close")
{
CloseCore(CloseReason.UserClosing);
return;
}
base.OnUserAction(action);
}
protected override void WriteProps(JsonObject props)
{
base.WriteProps(props);
props["Size"] = PropValue.From(Size);
props["ClientSize"] = PropValue.From(_clientSize);
props["FormBorderStyle"] = _formBorderStyle.ToString();
props["MaximizeBox"] = _maximizeBox;
props["MinimizeBox"] = _minimizeBox;
props["ControlBox"] = _controlBox;
props["StartPosition"] = StartPosition.ToString();
}
}