长时间执行的代码,加个等待窗口,让用户等待,增加友好度,一般这在web领域叫遮罩层。不过狐表Exe的UI框架里并没有集成这个,只能自己变通实现试下。

这个Loading不是挺优雅的吗?

1代码简单,控制开关也简单。
支持圆角和透明窗体效果。
可以自定义加载中的gif动画,添加你的自己的logo,(需要你有PS功底)。
提供通用简约的GIF图,能用于任何项目场合,不搞怪异化。
无内存泄露【重要】。试过连续打开关闭500次,内存在10M内波动,不会无限增加,程序不崩。

http://www.foxtable.com/bbs/dispbbs.asp?BoardID=2&ID=152101&replyID=&skin=1
http://www.foxtable.com/bbs/dispbbs.asp?BoardID=2&ID=73365&replyID=&skin=1
http://www.foxtable.com/bbs/dispbbs.asp?BoardID=2&ID=174427&replyID=&skin=1
我选了第三个帖子去二次改造,感觉这个代码最少,好理解维护。不过它的方法存在一个严重缺陷:
内存泄露严重,开循环200次打开直接程序崩了


其他是小问题:
没有圆角
没有半透明
标题好丑
代码有点乱
等待窗口结束后,焦点会跳到其他程序(很烦人)
Windows底部菜单栏会多了个窗口提示
图片素材放到Images文件夹里,与待会全局代码的路径相匹配


'通过WaitShow()和WaitClose()来打开和关闭遮罩层,'默认30秒自动关闭 '如果非常耗时,建议自定义时间WaitShow(50000),表示50秒,并配合Try使用,Finally里放WaitClose() '思路: '1先配置好等待窗口和里面的GIF动态图 '2通过WaitShow()启动新线程,以模式窗口形式展示 '3过程中执行你的长耗时业务代码 '4最后WaitClose()释放窗口内存,线程也会被自动销毁 ' ========================================================= ' 不抢焦点的遮罩层窗体 ' 作用: ' 1. 显示加载 GIF,但【永远不获取输入焦点】 ' 2. 避免与 InputBox / MessageBox 等模态窗口产生焦点竞争 ' 3. 防止 Windows 因“跨线程抢焦点”而把焦点丢给其他程序 ' ' 技术点: ' - 通过 WS_EX_NOACTIVATE 扩展样式 ' - 窗体显示时不会激活(不会成为前台窗口) ' ' ⚠️ 这是解决“焦点跳到资源管理器/其他程序”的关键 ' ========================================================= Public Class NoActivateForm Inherits System.Windows.Forms.Form ' 重写 CreateParams,注入 WS_EX_NOACTIVATE ' 该样式会告诉 Windows: ' - 这个窗体可以显示 ' - 但不参与焦点/输入激活 Protected Overrides ReadOnly Property CreateParams As System.Windows.Forms.CreateParams Get Dim cp As System.Windows.Forms.CreateParams = MyBase.CreateParams cp.ExStyle = cp.ExStyle Or &H8000000 ' WS_EX_NOACTIVATE Return cp End Get End Property End Class ' ========================================================= ' 遮罩层核心状态字段 ' ========================================================= ' 遮罩层专用 UI 线程 ' - 独立于主线程 ' - 负责 GIF 动画和窗体消息泵 Private overlayThread As System.Threading.Thread ' 遮罩层窗体实例(不抢焦点版) Private overlayForm As NoActivateForm ' 自动关闭定时器 ' - 默认 30 秒 ' - 不依赖主线程 Private autoCloseTimer As System.Threading.Timer ' 遮罩层“就绪信号” ' --------------------------------------------------------- ' 作用: ' - 解决 WaitShow() 后立刻 WaitClose() 时的竞态问题 ' - 确保窗体已经真正 Shown / Handle 已创建 ' ' 如果没有这个信号: ' - Close 可能发生在 Application.Run 之前 ' - 会导致“概率性关不掉” Private overlayReady As New System.Threading.ManualResetEvent(False) ' 同步锁,保护线程/窗体生命周期 Private ReadOnly syncObj As New Object() ' ========================================================= ' 对外 API(保持接口稳定,业务代码无需改动) ' ========================================================= ' 显示遮罩层 ' --------------------------------------------------------- ' closeTimeMilliseconds: ' - 默认 30000ms(30 秒) ' - 到时间后自动关闭,防止异常情况下永远不消失 Public Sub WaitShow(Optional closeTimeMilliseconds As Integer = 30000) SyncLock syncObj ' 每次 Show 前重置就绪状态 overlayReady.Reset() ' 如果遮罩层线程已经存在且存活,则不重复创建 ' 防止多次 WaitShow 导致多个遮罩层 If overlayThread IsNot Nothing AndAlso overlayThread.IsAlive Then Exit Sub ' 创建独立 UI 线程 overlayThread = New System.Threading.Thread( Sub() RunOverlayForm(closeTimeMilliseconds) End Sub ) ' 后台线程: ' - 主程序退出时,CLR 会自动终止 ' - 防止残留僵尸线程 overlayThread.IsBackground = True ' 必须 STA: ' - WinForms / GDI / 图片加载都依赖 STA overlayThread.SetApartmentState(System.Threading.ApartmentState.STA) overlayThread.Start() End SyncLock End Sub ' 关闭遮罩层 Public Sub WaitClose() ' 等待遮罩层真正就绪 ' - 最多等 1 秒,防止极端情况下死等 ' - 解决 Show → Close 紧挨着调用的问题 overlayReady.WaitOne(1000) SyncLock syncObj CloseInternal() End SyncLock End Sub ' ========================================================= ' 内部实现(不直接给业务层调用) ' ========================================================= ' 遮罩层线程入口 ' --------------------------------------------------------- ' 在【独立 UI 线程】中: ' - 创建窗体 ' - 启动 Application.Run 消息泵 Private Sub RunOverlayForm(closeTimeMilliseconds As Integer) ' 创建不抢焦点窗体 overlayForm = New NoActivateForm() With overlayForm .FormBorderStyle = System.Windows.Forms.FormBorderStyle.None .ShowInTaskbar = False .StartPosition = System.Windows.Forms.FormStartPosition.Manual ' 使用绿色作为透明色(配合 GIF) .BackColor = System.Drawing.Color.Green .TransparencyKey = System.Drawing.Color.Green .Width = 120 .Height = 130 ' TopMost 只用于视觉遮挡 ' 实际不会抢焦点(因为 WS_EX_NOACTIVATE) .TopMost = True End With ' 屏幕居中偏上,避免遮挡 MessageBox / InputBox Dim screen As System.Drawing.Rectangle = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea overlayForm.Location = New System.Drawing.Point( (screen.Width - overlayForm.Width) \ 2, (screen.Height - overlayForm.Height) \ 2 - 140 ) ' 加载 GIF 动画 Dim pic As New System.Windows.Forms.PictureBox() pic.Dock = System.Windows.Forms.DockStyle.Fill pic.Image = System.Drawing.Image.FromFile(ProjectPath & "Images\loading.gif") pic.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom overlayForm.Controls.Add(pic) ' 窗体真正显示后,标记为 ready ' ⚠️ 这是 WaitShow / WaitClose 稳定性的关键 AddHandler overlayForm.Shown, Sub() overlayReady.Set() End Sub ' 自动关闭定时器 ' - 完全独立 ' - 不依赖主线程 autoCloseTimer = New System.Threading.Timer( Sub() CloseInternal() End Sub, Nothing, closeTimeMilliseconds, System.Threading.Timeout.Infinite ) ' 启动独立 UI 消息泵 System.Windows.Forms.Application.Run(overlayForm) End Sub ' 真正的关闭逻辑 ' --------------------------------------------------------- ' 注意: ' - 可能被多次调用 ' - 可能在不同线程调用 ' - 必须足够健壮 Private Sub CloseInternal() Try ' 停止并释放定时器 If autoCloseTimer IsNot Nothing Then autoCloseTimer.Dispose() autoCloseTimer = Nothing End If ' 安全关闭窗体 If overlayForm IsNot Nothing AndAlso Not overlayForm.IsDisposed Then overlayForm.Invoke( New System.Windows.Forms.MethodInvoker( Sub() overlayForm.Close() End Sub ) ) End If Catch ' 在以下情况下可能进入: ' - 主线程卡死 ' - 消息泵已结束 ' - 程序正在退出 ' ' 这里刻意忽略,交给进程退出回收 End Try End Sub
如果你确定代码执行时间较短,且逻辑简单不容易报错
WaitShow '30秒内自动关闭
'你的长时间运行代码,例如保存
DataTables("数据权限表").Save
WaitClose- 如果预计代码时间长,且逻辑复杂容易易出错,推荐配合Try使用,Finally里放WaitClose(),防止因为业务代码异常而导致遮罩层不关闭。
- 当然如果你有把握,中间过程代码不会出错的,不用Try,直接使用也没问题。如果真的出现异常,你可以告诉客户在底部菜单栏右键强制关闭。它的关闭,并不会影响原来主线程在做的事情。
Try
WaitShow(60000)'手动设置60秒后关闭
'你的长时间运行代码,例如保存
DataTables("数据权限表").Save
Catch ex As Exception
MessageBox.Show(ex.Message)
Finally
WaitClose
End Try这个需要你有PS基础,我已经提供了我这次使用的gif图的原PSD文件。需要在PS里打开,并且启动PS的`时间轴`功能。你要去百度学习一下PS的时间轴玩法和GIF保存方法
例如我加一个迈宝伦科技官网的logo去中间


成品效果:

gif是支持透明背景的,圆角也支持,在PS做好即可。唯一要注意就是生成GIF图时,圆角要设置相应的杂边颜色,不然会有难看的白色锯齿

以下是正确保存方法


在刚才的全局代码里有一个
... ' 使用绿色作为透明色(配合 GIF) .BackColor = System.Drawing.Color.Green .TransparencyKey = System.Drawing.Color.Green ...
这个是狐表窗口实现边框透明的关键,设置绿色边框,并对绿色进行透明化。所以你如果GIF里刚好包含了RGB(0,128,0)的绿色,也会被抠图走,穿孔很丑了。
如果你的Gif图一定要用这种绿色,我推荐你可以换别的颜色组合,例如Color.Red。就跟好莱坞拍戏用绿幕,演员就别穿颜色相近的绿衣服一个道理。
文件下载:
等待窗口演示.zip
版本说明:程序需要20220818或以上的商业版,或者用狐表官网最新的试用版。如果你没有这个版本也没关系,可以下载源码,只要里面的GIF素材和PS素材,配合上面分享的代码,复制就能用,不一定非得开源码看。