优雅处理后端服务请求:Windows窗体繁忙提示实现指南

作者:微信公众号:【架构师老卢】
1-26 14:35
34

概述:本文介绍了一种解决在 Windows 窗体上处理后端服务请求时,需要显示繁忙光标并防止用户点击其他按钮的方法。通过覆盖部分透明的窗体,实现在处理请求期间的界面繁忙提示,避免了禁用整个窗体的缺陷。通过实例和代码演示了如何使用和实现这种功能。

我们可能遇到过很多次这样的情况:当用户在 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;
            }
        }
    }
}
相关留言评论
昵称:
邮箱:
阅读排行