我们可能遇到过很多次这样的情况:当用户在 Windows 窗体上单击按钮时,我们需要调用后端服务来处理请求。服务调用可能需要一些时间,在此期间,我们希望显示繁忙的光标,并且不希望用户能够单击 UI 上的其他按钮。
一种简单的方法是将 Form 的 Enabled 属性设置为 false。整个表单将被禁用,用户无法单击任何内容。但是,这种方法的一个缺点是,即使窗口窗体标题栏被禁用,您也无法最小化/关闭窗口。
在本文中,我们将讨论另一种方法:在原始 Windows 窗体之上覆盖部分透明的 Windows 窗体。覆盖窗口具有原始 Windows 窗体工作区的确切位置和大小。
覆盖表单 (BusyForm) 名为 BusyForm 的覆盖表单具有以下功能:
它的标题栏上没有控制框(窗体右上角的最小化/最大化/关闭按钮)。 它没有边界。 它不会显示在 Windows 任务栏中。 它的起始位置设置为手动,因此我们可以通过编程方式更改其位置。 当它被激活时,我们只需调用原始表单的 activated 方法。 它只有一个构造函数,该构造函数采用宿主窗体作为参数。 它有一个繁忙的财产。将此属性设置为 true 时,它将订阅主机窗体的 LocationChanged、SizeChanged 和 VisibilityChanged 事件。然后,它调整其位置和大小以覆盖主机窗体的工作区,并将其可见性设置为 true。 以下是 BusyForm 代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace BusyForm
{
public partial class BusyForm : Form
{
private Form _host;
private bool _busy;
private EventHandler? _resizeHandler;
public BusyForm(Form hostForm)
{
_host = hostForm;
this.Visible = false;
InitializeComponent();
}
public bool Busy
{
get
{
return _busy;
}
set
{
_busy = value;
if (_busy)
{
HookToHost();
}
else
{
UnhookToHost();
}
this.Visible = _busy;
}
}
private void HookToHost()
{
if (_resizeHandler == null)
{
_resizeHandler = new EventHandler(this.HostResizeHandler);
_host.LocationChanged += _resizeHandler;
_host.SizeChanged += _resizeHandler;
_host.VisibleChanged += _resizeHandler;
_host.AddOwnedForm(this);
HostResizeHandler(this, EventArgs.Empty);
}
}
private void HostResizeHandler(object? sender, EventArgs e)
{
Rectangle rect;
Point location;
var parentForm = _host.FindForm().Parent;
if (parentForm != null)
{
rect = parentForm.FindForm().ClientRectangle;
location = parentForm.FindForm().PointToScreen(new Point(0, 0));
}
else
{
rect = _host.ClientRectangle;
location = _host.PointToScreen(new Point(0, 0));
}
this.Top = location.Y;
this.Left = location.X;
this.Width = rect.Width;
this.Height = rect.Height;
}
private void UnhookToHost()
{
if (_resizeHandler != null)
{
_host.SizeChanged -= _resizeHandler;
_host.LocationChanged -= _resizeHandler;
_host.VisibleChanged -= _resizeHandler;
_resizeHandler = null;
_host.RemoveOwnedForm(this);
}
}
private void BusyForm_Activated(object sender, EventArgs e)
{
_host.Activate();
this.Cursor = Cursors.WaitCursor;
}
}
}
通过将 BusyForm 添加到宿主拥有的窗体集合中,BusyForm 将始终显示在宿主窗体的顶部。
以下是 BusyForm 设计器代码:
namespace BusyForm
{
partial class BusyForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
SuspendLayout();
//
// BusyForm
//
AutoScaleDimensions = new SizeF(13F, 32F);
AutoScaleMode = AutoScaleMode.Font;
CausesValidation = false;
ClientSize = new Size(1300, 720);
ControlBox = false;
Cursor = Cursors.WaitCursor;
FormBorderStyle = FormBorderStyle.None;
Margin = new Padding(5);
Name = "BusyForm";
Opacity = 0.5D;
ShowIcon = false;
ShowInTaskbar = false;
StartPosition = FormStartPosition.Manual;
Text = "BusyForm";
Activated += BusyForm_Activated;
ResumeLayout(false);
}
#endregion
}
}
表单库 我们希望使这个 BusyForm 易于使用,并向开发人员隐藏详细信息。我们将 BusyForm 封装到一个抽象的 FormBase 类中。
FormBase 有一个名为 Busy 的公共属性。将此属性设置为 true 将在主机窗体顶部显示 BusyForm。将其设置为 false 将删除 BusyForm。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace BusyForm
{
public abstract partial class FormBase : Form
{
private bool _busy;
private BusyForm? _busyForm;
public bool Busy
{
get { return _busy; }
set
{
_busy = value;
if (value)
{
if (_busyForm == null)
{
_busyForm = new BusyForm(this);
}
_busyForm.Busy = true;
}
else
{
if (_busyForm != null)
{
_busyForm.Busy = false;
_busyForm.Dispose();
_busyForm = null;
}
}
}
}
public FormBase()
{
InitializeComponent();
}
}
}
使用 BusyForm 对于任何需要使用此功能的 Windows 窗体,请使其继承自 FormBase 类。当它需要使窗体不可单击并使用等待光标时,只需将 Busy 属性设置为 true。使用 try/catch/finally 块,并在 finally 块中将 Busy 属性设置回 false。
namespace BusyFormDemo
{
public partial class NormalForm : FormBase
{
public NormalForm()
{
InitializeComponent();
}
private async void btnMakeMeBusy_Click(object sender, EventArgs e)
{
this.Busy = true;
try
{
await Task.Delay(3000);
}
finally
{
this.Busy = false;
}
}
}
}