We probably encountered this scenario many times: when user clicks a button on Windows Forms, we need to call backend service to process the request. The service call may take some time and during the period we would want to display a busy cursor and not want user to be able to click other buttons on the UI.
One easy way to do it is to set the Enabled property of the Form to false. The whole form would be disabled and user is not able to click anything. However, one drawback of this approach is even the windows form caption bar is disabled and you can not minimize/close the window.
In this article, we will discuss another approach: we overlay a partially transparent Windows form on top of the original Windows form. The overlay window has exact location and size of the original Windows form’s client area.
Overlay Form (BusyForm)
The overlay form, named as BusyForm, has following features:
- It does not have control box (minimize/maximize/close buttons on upper right of the form) on the caption bar.
- It does not have border.
- It would not show in Windows task bar.
- Its start position is set to manual, so we can change its location programmatically.
- When it is activated, we just call the original form’s activated method.
- It has only one constructor that takes a host form as parameter.
- It has a Busy property. When setting this property to true, it would subscribe to the LocationChanged, SizeChanged and VisibilityChanged events of the host form. It then adjusts its location and size to cover the host form’s client area and set its visibility to true.
Following is the BusyForm code:
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;
}
}
}
By adding the BusyForm to host’s owned form collection, the BusyForm would always show on top of the host form.
Following is the BusyForm designer code:
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
}
}
FormBase
We want to make this BusyForm easy to use and hide the details from the developers. We encapsulate the BusyForm into an abstract FormBase class.
FormBase has one public property called Busy. Set this property to true will display the BusyForm on top of the host form. Set it to false will remove the 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();
}
}
}
Use BusyForm
For any Windows form that need to utilize this feature, make it inherit from FormBase class. When it needs to make the form non-clickable and with wait cursor, just set the Busy property to true. Use try/catch/finally block and set the Busy property back to false in finally block.
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;
}
}
}
}
You can download the code from following Github repository:
https://github.com/jason-ge/BusyFormDemo
Haopy coding!