停靠布局、背景图片、无边框窗体;运行区按比例缩小、可放大
- 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>
This commit is contained in:
1 parent
42e258ddcd
commit
d60868d912
17 files changed
+600
-49
No files matched your search
@@ -0,0 +1,14 @@
|
||||
namespace System.Drawing;
|
||||
|
||||
/// <summary>
|
||||
/// 绘图面。平台的控件由浏览器绘制,学生程序不能自己画(没有 DrawLine 等方法);
|
||||
/// 只为让 VS 生成的空 Paint 事件处理程序(panel1_Paint)能编译。
|
||||
/// </summary>
|
||||
public sealed class Graphics : IDisposable
|
||||
{
|
||||
private Graphics()
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose() => GC.SuppressFinalize(this);
|
||||
}
|
||||
@@ -43,7 +43,21 @@ public class Panel : ScrollableControl
|
||||
public BorderStyle BorderStyle
|
||||
{
|
||||
get => _borderStyle;
|
||||
set => SetProp(ref _borderStyle, value, nameof(BorderStyle));
|
||||
set
|
||||
{
|
||||
if (SetProp(ref _borderStyle, value, nameof(BorderStyle))) PerformLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>边框占掉的宽度:单线 1 像素,立体 2 像素</summary>
|
||||
public override Rectangle DisplayRectangle
|
||||
{
|
||||
get
|
||||
{
|
||||
var inset = _borderStyle switch { BorderStyle.FixedSingle => 1, BorderStyle.Fixed3D => 2, _ => 0 };
|
||||
var rect = base.DisplayRectangle;
|
||||
return new Rectangle(rect.X, rect.Y, Math.Max(0, rect.Width - 2 * inset), Math.Max(0, rect.Height - 2 * inset));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void WriteProps(JsonObject props)
|
||||
@@ -58,4 +72,14 @@ public class GroupBox : Control
|
||||
protected override Size DefaultSize => new(200, 100);
|
||||
|
||||
public FlatStyle FlatStyle { get; set; } = FlatStyle.Standard;
|
||||
|
||||
/// <summary>分组框的边框和标题占掉的地方(宋体 9pt 时和 WinForms 一致)</summary>
|
||||
public override Rectangle DisplayRectangle
|
||||
{
|
||||
get
|
||||
{
|
||||
var rect = base.DisplayRectangle;
|
||||
return new Rectangle(rect.X + 3, rect.Y + 17, Math.Max(0, rect.Width - 6), Math.Max(0, rect.Height - 20));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,10 @@ public class Control : Component, IWin32Window
|
||||
private Font? _font;
|
||||
private bool _autoSize;
|
||||
private DockStyle _dock;
|
||||
private Padding _padding;
|
||||
private int _layoutSuspendCount;
|
||||
private Image? _backgroundImage;
|
||||
private ImageLayout _backgroundImageLayout = ImageLayout.Tile;
|
||||
private AnchorStyles _anchor = AnchorStyles.Top | AnchorStyles.Left;
|
||||
private Control? _parent;
|
||||
|
||||
@@ -131,7 +135,13 @@ public class Control : Component, IWin32Window
|
||||
public Size Size
|
||||
{
|
||||
get => _size;
|
||||
set => SetProp(ref _size, value, nameof(Size));
|
||||
set
|
||||
{
|
||||
if (!SetProp(ref _size, value, nameof(Size))) return;
|
||||
// 大小变了:自己的停靠子控件重新排;自己是停靠的,父容器也要重新排
|
||||
PerformLayout();
|
||||
if (_dock != DockStyle.None) _parent?.PerformLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public int Left { get => _location.X; set => Location = new Point(value, _location.Y); }
|
||||
@@ -162,7 +172,9 @@ public class Control : Component, IWin32Window
|
||||
get => _visible;
|
||||
set
|
||||
{
|
||||
if (SetProp(ref _visible, value, nameof(Visible))) OnVisibleChanged(EventArgs.Empty);
|
||||
if (!SetProp(ref _visible, value, nameof(Visible))) return;
|
||||
if (_dock != DockStyle.None) _parent?.PerformLayout();
|
||||
OnVisibleChanged(EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +214,40 @@ public class Control : Component, IWin32Window
|
||||
public DockStyle Dock
|
||||
{
|
||||
get => _dock;
|
||||
set => SetProp(ref _dock, value, nameof(Dock));
|
||||
set
|
||||
{
|
||||
if (SetProp(ref _dock, value, nameof(Dock))) _parent?.PerformLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public Padding Padding
|
||||
{
|
||||
get => _padding;
|
||||
set
|
||||
{
|
||||
if (_padding.Equals(value)) return;
|
||||
_padding = value;
|
||||
PerformLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>背景图片;BackgroundImageLayout 决定平铺(默认)、居中、拉伸或按比例缩放</summary>
|
||||
public virtual Image? BackgroundImage
|
||||
{
|
||||
get => _backgroundImage;
|
||||
set
|
||||
{
|
||||
LegacyThreads.CheckPoint();
|
||||
if (ReferenceEquals(_backgroundImage, value)) return;
|
||||
_backgroundImage = value;
|
||||
NotifyChanged(nameof(BackgroundImage), value);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual ImageLayout BackgroundImageLayout
|
||||
{
|
||||
get => _backgroundImageLayout;
|
||||
set => SetProp(ref _backgroundImageLayout, value, nameof(BackgroundImageLayout));
|
||||
}
|
||||
|
||||
public AnchorStyles Anchor
|
||||
@@ -216,7 +261,6 @@ public class Control : Component, IWin32Window
|
||||
public bool TabStop { get; set; } = true;
|
||||
public object? Tag { get; set; }
|
||||
public Padding Margin { get; set; } = new(3);
|
||||
public Padding Padding { get; set; }
|
||||
public ImeMode ImeMode { get; set; } = ImeMode.NoControl;
|
||||
public virtual RightToLeft RightToLeft { get; set; } = RightToLeft.No;
|
||||
public virtual bool AllowDrop { get; set; }
|
||||
@@ -246,6 +290,8 @@ public class Control : Component, IWin32Window
|
||||
public event EventHandler? LostFocus;
|
||||
public event EventHandler? Enter;
|
||||
public event EventHandler? Leave;
|
||||
/// <summary>重绘。平台由浏览器绘制控件,不会触发这个事件(VS 双击 Panel 生成的空处理程序照常能编译)</summary>
|
||||
public event PaintEventHandler? Paint;
|
||||
|
||||
protected virtual void OnClick(EventArgs e) => Click?.Invoke(this, e);
|
||||
protected virtual void OnDoubleClick(EventArgs e) => DoubleClick?.Invoke(this, e);
|
||||
@@ -259,6 +305,7 @@ public class Control : Component, IWin32Window
|
||||
protected virtual void OnEnabledChanged(EventArgs e) => EnabledChanged?.Invoke(this, 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);
|
||||
protected virtual void OnEnter(EventArgs e) => Enter?.Invoke(this, e);
|
||||
protected virtual void OnLeave(EventArgs e) => Leave?.Invoke(this, e);
|
||||
|
||||
@@ -305,10 +352,65 @@ public class Control : Component, IWin32Window
|
||||
public void Refresh() { }
|
||||
public void Invalidate() { }
|
||||
public void Update() { }
|
||||
public void SuspendLayout() { }
|
||||
public void ResumeLayout() { }
|
||||
public void ResumeLayout(bool performLayout) { }
|
||||
public void PerformLayout() { }
|
||||
public void SuspendLayout() => _layoutSuspendCount++;
|
||||
public void ResumeLayout() => ResumeLayout(true);
|
||||
|
||||
public void ResumeLayout(bool performLayout)
|
||||
{
|
||||
if (_layoutSuspendCount > 0) _layoutSuspendCount--;
|
||||
if (performLayout) PerformLayout();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停靠布局(和 WinForms 一样):按 Controls 集合从后往前,Top / Bottom / Left / Right 依次贴边并占掉那一条,
|
||||
/// Fill 占剩下的全部。VS 在 Designer.cs 里写的位置大小就是这样排出来的;代码里改停靠、大小、可见时重新排。
|
||||
/// </summary>
|
||||
public void PerformLayout()
|
||||
{
|
||||
if (_layoutSuspendCount > 0) return;
|
||||
var rect = DisplayRectangle;
|
||||
for (var i = Controls.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var child = Controls[i];
|
||||
if (!child._visible || child._dock == DockStyle.None) continue;
|
||||
var size = child._size;
|
||||
Rectangle bounds;
|
||||
switch (child._dock)
|
||||
{
|
||||
case DockStyle.Top:
|
||||
bounds = new Rectangle(rect.X, rect.Y, rect.Width, size.Height);
|
||||
rect = new Rectangle(rect.X, rect.Y + size.Height, rect.Width, Math.Max(0, rect.Height - size.Height));
|
||||
break;
|
||||
case DockStyle.Bottom:
|
||||
bounds = new Rectangle(rect.X, rect.Bottom - size.Height, rect.Width, size.Height);
|
||||
rect = new Rectangle(rect.X, rect.Y, rect.Width, Math.Max(0, rect.Height - size.Height));
|
||||
break;
|
||||
case DockStyle.Left:
|
||||
bounds = new Rectangle(rect.X, rect.Y, size.Width, rect.Height);
|
||||
rect = new Rectangle(rect.X + size.Width, rect.Y, Math.Max(0, rect.Width - size.Width), rect.Height);
|
||||
break;
|
||||
case DockStyle.Right:
|
||||
bounds = new Rectangle(rect.Right - size.Width, rect.Y, size.Width, rect.Height);
|
||||
rect = new Rectangle(rect.X, rect.Y, Math.Max(0, rect.Width - size.Width), rect.Height);
|
||||
break;
|
||||
default:
|
||||
bounds = rect;
|
||||
break;
|
||||
}
|
||||
child.SetBoundsFromLayout(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>停靠布局给出的位置大小:不再反过来触发父容器的布局</summary>
|
||||
private void SetBoundsFromLayout(Rectangle bounds)
|
||||
{
|
||||
SetProp(ref _location, bounds.Location, nameof(Location));
|
||||
if (SetProp(ref _size, bounds.Size, nameof(Size))) PerformLayout();
|
||||
}
|
||||
|
||||
/// <summary>容器里用来停靠子控件的区域:客户区去掉内边距(分组框、带边框的面板再去掉边框)</summary>
|
||||
public virtual Rectangle DisplayRectangle =>
|
||||
new(_padding.Left, _padding.Top, Math.Max(0, ClientSize.Width - _padding.Horizontal), Math.Max(0, ClientSize.Height - _padding.Vertical));
|
||||
|
||||
public bool InvokeRequired => !SimHost.Ui.IsUiThread;
|
||||
|
||||
@@ -409,6 +511,8 @@ public class Control : Component, IWin32Window
|
||||
props["Font"] = PropValue.From(_font);
|
||||
props["AutoSize"] = _autoSize;
|
||||
props["Dock"] = _dock.ToString();
|
||||
props["BackgroundImage"] = PropValue.From(_backgroundImage);
|
||||
props["BackgroundImageLayout"] = _backgroundImageLayout.ToString();
|
||||
}
|
||||
|
||||
/// <summary>浏览器按这个类型名选择渲染方式。学生自己继承的控件按最近的仿真库基类渲染。</summary>
|
||||
@@ -459,6 +563,7 @@ public class Control : Component, IWin32Window
|
||||
value.Parent?.Controls.Remove(value);
|
||||
_items.Add(value);
|
||||
value.SetParentInternal(Owner);
|
||||
if (value.Dock != DockStyle.None) Owner.PerformLayout();
|
||||
if (Owner.IsAttached)
|
||||
{
|
||||
SimHost.Send(new JsonObject
|
||||
@@ -481,6 +586,7 @@ public class Control : Component, IWin32Window
|
||||
if (value == null || !_items.Remove(value)) return;
|
||||
var wasAttached = Owner.IsAttached;
|
||||
value.SetParentInternal(null);
|
||||
if (value.Dock != DockStyle.None) Owner.PerformLayout();
|
||||
if (wasAttached) SimHost.Send(new JsonObject { ["type"] = "controlRemoved", ["id"] = value.SimId });
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ public enum ComboBoxStyle { Simple, DropDown, DropDownList }
|
||||
|
||||
public enum DockStyle { None, Top, Bottom, Left, Right, Fill }
|
||||
|
||||
public enum ImageLayout { None, Tile, Center, Stretch, Zoom }
|
||||
|
||||
[Flags]
|
||||
public enum AnchorStyles { None = 0, Top = 1, Bottom = 2, Left = 4, Right = 8 }
|
||||
|
||||
|
||||
@@ -41,3 +41,20 @@ public class FormClosedEventArgs : EventArgs
|
||||
}
|
||||
|
||||
public delegate void FormClosedEventHandler(object? sender, FormClosedEventArgs e);
|
||||
|
||||
/// <summary>重绘事件的参数。平台由浏览器绘制控件,不支持在 Graphics 上自己画</summary>
|
||||
public class PaintEventArgs : EventArgs, IDisposable
|
||||
{
|
||||
public PaintEventArgs(System.Drawing.Graphics graphics, System.Drawing.Rectangle clipRect)
|
||||
{
|
||||
Graphics = graphics;
|
||||
ClipRectangle = clipRect;
|
||||
}
|
||||
|
||||
public System.Drawing.Graphics Graphics { get; }
|
||||
public System.Drawing.Rectangle ClipRectangle { get; }
|
||||
|
||||
public void Dispose() => GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public delegate void PaintEventHandler(object? sender, PaintEventArgs e);
|
||||
@@ -33,13 +33,16 @@ public class Form : ContainerControl
|
||||
public override Size ClientSize
|
||||
{
|
||||
get => _clientSize;
|
||||
set => SetProp(ref _clientSize, value, nameof(ClientSize));
|
||||
set
|
||||
{
|
||||
if (SetProp(ref _clientSize, value, nameof(ClientSize))) PerformLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public new Size Size
|
||||
{
|
||||
get => _clientSize + NonClientSize;
|
||||
set => ClientSize = value - NonClientSize;
|
||||
get => _formBorderStyle == FormBorderStyle.None ? _clientSize : _clientSize + NonClientSize;
|
||||
set => ClientSize = _formBorderStyle == FormBorderStyle.None ? value : value - NonClientSize;
|
||||
}
|
||||
|
||||
public FormBorderStyle FormBorderStyle
|
||||
|
||||
Reference in new issue
Block a user